[bcl] Update Reference Source to .NET Framework 4.6.2
[mono.git] / mcs / class / referencesource / System.Runtime.Caching / System / Caching / CacheMemoryMonitor.cs
1 // <copyright file="CacheMemoryMonitor.cs" company="Microsoft">
2 //   Copyright (c) 2009 Microsoft Corporation.  All rights reserved.
3 // </copyright>
4 using System;
5 using System.Runtime.Caching.Configuration;
6 using System.Runtime.Caching.Hosting;
7 using System.Diagnostics.CodeAnalysis;
8 using System.Security;
9 using System.Security.Permissions;
10 using System.Threading;
11
12 namespace System.Runtime.Caching {
13     // CacheMemoryMonitor uses the internal System.SizedReference type to determine
14     // the size of the cache itselt, and helps us know when to drop entries to avoid
15     // exceeding the cache's memory limit.  The limit is configurable (see ConfigUtil.cs).
16     internal sealed class CacheMemoryMonitor : MemoryMonitor, IDisposable {
17         const long PRIVATE_BYTES_LIMIT_2GB = 800 * MEGABYTE;
18         const long PRIVATE_BYTES_LIMIT_3GB = 1800 * MEGABYTE;
19         const long PRIVATE_BYTES_LIMIT_64BIT = 1L * TERABYTE;
20         const int SAMPLE_COUNT = 2;
21
22         private static IMemoryCacheManager s_memoryCacheManager;
23         private static long s_autoPrivateBytesLimit = -1;
24         private static long s_effectiveProcessMemoryLimit = -1;
25
26         private MemoryCache _memoryCache;
27         private long[] _cacheSizeSamples;
28         private DateTime[] _cacheSizeSampleTimes;
29         private int _idx;
30         private SRefMultiple _sizedRefMultiple;
31         private int _gen2Count;
32         private long _memoryLimit;
33
34         internal long MemoryLimit {
35             get { return _memoryLimit; }
36         }
37
38         private CacheMemoryMonitor() {
39             // hide default ctor
40         }
41
42         internal CacheMemoryMonitor(MemoryCache memoryCache, int cacheMemoryLimitMegabytes) {
43             _memoryCache = memoryCache;
44             _gen2Count = GC.CollectionCount(2);
45             _cacheSizeSamples = new long[SAMPLE_COUNT];
46             _cacheSizeSampleTimes = new DateTime[SAMPLE_COUNT];
47             InitMemoryCacheManager();
48             InitDisposableMembers(cacheMemoryLimitMegabytes);
49         }
50         
51         private void InitDisposableMembers(int cacheMemoryLimitMegabytes) {
52             bool dispose = true;
53             try {
54                 _sizedRefMultiple = new SRefMultiple(_memoryCache.AllSRefTargets);
55                 SetLimit(cacheMemoryLimitMegabytes);
56                 InitHistory();
57                 dispose = false;
58             }
59             finally {
60                 if (dispose) {
61                     Dispose();
62                 }
63             }
64         }
65
66         // Auto-generate the private bytes limit:
67         // - On 64bit, the auto value is MIN(60% physical_ram, 1 TB)
68         // - On x86, for 2GB, the auto value is MIN(60% physical_ram, 800 MB)
69         // - On x86, for 3GB, the auto value is MIN(60% physical_ram, 1800 MB)
70         //
71         // - If it's not a hosted environment (e.g. console app), the 60% in the above
72         //   formulas will become 100% because in un-hosted environment we don't launch
73         //   other processes such as compiler, etc.
74         private static long AutoPrivateBytesLimit {
75             get {
76                 long memoryLimit = s_autoPrivateBytesLimit;
77                 if (memoryLimit == -1) {
78
79                     bool is64bit = (IntPtr.Size == 8);
80
81                     long totalPhysical = TotalPhysical;
82                     long totalVirtual = TotalVirtual;
83                     if (totalPhysical != 0) {
84                         long recommendedPrivateByteLimit;
85                         if (is64bit) {
86                             recommendedPrivateByteLimit = PRIVATE_BYTES_LIMIT_64BIT;
87                         }
88                         else {
89                             // Figure out if it's 2GB or 3GB
90
91                             if (totalVirtual > 2 * GIGABYTE) {
92                                 recommendedPrivateByteLimit = PRIVATE_BYTES_LIMIT_3GB;
93                             }
94                             else {
95                                 recommendedPrivateByteLimit = PRIVATE_BYTES_LIMIT_2GB;
96                             }
97                         }
98
99                         // use 60% of physical RAM
100                         long usableMemory = totalPhysical * 3 / 5;
101                         memoryLimit = Math.Min(usableMemory, recommendedPrivateByteLimit);
102                     }
103                     else {
104                         // If GlobalMemoryStatusEx fails, we'll use these as our auto-gen private bytes limit
105                         memoryLimit = is64bit ? PRIVATE_BYTES_LIMIT_64BIT : PRIVATE_BYTES_LIMIT_2GB;
106                     }
107                     Interlocked.Exchange(ref s_autoPrivateBytesLimit, memoryLimit);
108                 }
109
110                 return memoryLimit;
111             }
112         }
113
114         public void Dispose() {
115             SRefMultiple sref = _sizedRefMultiple;
116             if (sref != null && Interlocked.CompareExchange(ref _sizedRefMultiple, null, sref) == sref) {
117                 sref.Dispose();
118             }
119             IMemoryCacheManager memoryCacheManager = s_memoryCacheManager;
120             if (memoryCacheManager != null) {
121                 memoryCacheManager.ReleaseCache(_memoryCache);
122             }
123         }
124
125         internal static long EffectiveProcessMemoryLimit {
126             get {
127                 long memoryLimit = s_effectiveProcessMemoryLimit;
128                 if (memoryLimit == -1) {
129                     memoryLimit = AutoPrivateBytesLimit;
130                     Interlocked.Exchange(ref s_effectiveProcessMemoryLimit, memoryLimit);
131                 }
132                 return memoryLimit;
133             }
134         }
135
136         protected override int GetCurrentPressure() {
137             // Call GetUpdatedTotalCacheSize to update the total
138             // cache size, if there has been a recent Gen 2 Collection.
139             // This update must happen, otherwise the CacheManager won't 
140             // know the total cache size.
141             int gen2Count = GC.CollectionCount(2);
142             SRefMultiple sref = _sizedRefMultiple;
143             if (gen2Count != _gen2Count && sref != null) {
144                 // update _gen2Count
145                 _gen2Count = gen2Count;
146
147                 // the SizedRef is only updated after a Gen2 Collection
148
149                 // increment the index (it's either 1 or 0)
150                 Dbg.Assert(SAMPLE_COUNT == 2);
151                 _idx = _idx ^ 1;
152                 // remember the sample time
153                 _cacheSizeSampleTimes[_idx] = DateTime.UtcNow;
154                 // remember the sample value
155                 _cacheSizeSamples[_idx] = sref.ApproximateSize;
156 #if DBG
157                 Dbg.Trace("MemoryCacheStats", "SizedRef.ApproximateSize=" + _cacheSizeSamples[_idx]);
158 #endif
159                 IMemoryCacheManager memoryCacheManager = s_memoryCacheManager;
160                 if (memoryCacheManager != null) {
161                     memoryCacheManager.UpdateCacheSize(_cacheSizeSamples[_idx], _memoryCache);
162                 }
163             }
164
165             // if there's no memory limit, then there's nothing more to do
166             if (_memoryLimit <= 0) {
167                 return 0;
168             }
169
170             long cacheSize = _cacheSizeSamples[_idx];
171
172             // use _memoryLimit as an upper bound so that pressure is a percentage (between 0 and 100, inclusive).
173             if (cacheSize > _memoryLimit) {
174                 cacheSize = _memoryLimit;
175             }
176
177             // PerfCounter: Cache Percentage Process Memory Limit Used
178             //    = memory used by this process / process memory limit at pressureHigh
179             // Set private bytes used in kilobytes because the counter is a DWORD
180
181             // 
182
183
184             int result = (int)(cacheSize * 100 / _memoryLimit);
185             return result;
186         }
187
188         internal override int GetPercentToTrim(DateTime lastTrimTime, int lastTrimPercent) {
189             int percent = 0;
190             if (IsAboveHighPressure()) {
191                 long cacheSize = _cacheSizeSamples[_idx];
192                 if (cacheSize > _memoryLimit) {
193                     percent = Math.Min(100, (int)((cacheSize - _memoryLimit) * 100L / cacheSize));
194                 }
195
196 #if PERF
197                 SafeNativeMethods.OutputDebugString(String.Format("CacheMemoryMonitor.GetPercentToTrim: percent={0:N}, lastTrimPercent={1:N}\n",
198                                                     percent,
199                                                     lastTrimPercent));
200 #endif
201
202             }
203             return percent;
204         }
205
206         internal void SetLimit(int cacheMemoryLimitMegabytes) {
207             long cacheMemoryLimit = cacheMemoryLimitMegabytes;
208             cacheMemoryLimit = cacheMemoryLimit << MEGABYTE_SHIFT;
209
210             // 
211             _memoryLimit = 0;
212
213             // VSWhidbey 546381: never override what the user specifies as the limit;
214             // only call AutoPrivateBytesLimit when the user does not specify one.
215             if (cacheMemoryLimit == 0 && _memoryLimit == 0) {
216                 // Zero means we impose a limit
217                 _memoryLimit = EffectiveProcessMemoryLimit;
218             }
219             else if (cacheMemoryLimit != 0 && _memoryLimit != 0) {
220                 // Take the min of "cache memory limit" and the host's "process memory limit".
221                 _memoryLimit = Math.Min(_memoryLimit, cacheMemoryLimit);
222             }
223             else if (cacheMemoryLimit != 0) {
224                 // _memoryLimit is 0, but "cache memory limit" is non-zero, so use it as the limit
225                 _memoryLimit = cacheMemoryLimit;
226             }
227
228             Dbg.Trace("MemoryCacheStats", "CacheMemoryMonitor.SetLimit: _memoryLimit=" + (_memoryLimit >> MEGABYTE_SHIFT) + "Mb");
229
230             if (_memoryLimit > 0) {
231                 _pressureHigh = 100;
232                 _pressureLow = 80;
233             }
234             else {
235                 _pressureHigh = 99;
236                 _pressureLow = 97;
237             }
238
239             Dbg.Trace("MemoryCacheStats", "CacheMemoryMonitor.SetLimit: _pressureHigh=" + _pressureHigh +
240                         ", _pressureLow=" + _pressureLow);
241         }
242
243         [SecuritySafeCritical]
244         [PermissionSet(SecurityAction.Assert, Unrestricted = true)]
245         [SuppressMessage("Microsoft.Security", "CA2106:SecureAsserts", Justification = "Grandfathered suppression from original caching code checkin")]
246         private static void InitMemoryCacheManager() {
247             if (s_memoryCacheManager == null) {
248                 IMemoryCacheManager memoryCacheManager = null;
249                 IServiceProvider host = ObjectCache.Host;
250                 if (host != null) {
251                     memoryCacheManager = host.GetService(typeof(IMemoryCacheManager)) as IMemoryCacheManager;
252                 }
253                 if (memoryCacheManager != null) {
254                     Interlocked.CompareExchange(ref s_memoryCacheManager, memoryCacheManager, null);
255                 }
256             }
257         }
258     }
259 }