Updates referencesource to .NET 4.7
[mono.git] / mcs / class / referencesource / System.Runtime.Caching / System / Caching / MemoryCacheStatistics.cs
1 // <copyright file="MemoryCacheStats.cs" company="Microsoft">
2 //   Copyright (c) 2009 Microsoft Corporation.  All rights reserved.
3 // </copyright>
4 using System.Collections.Specialized;
5 using System.Configuration;
6 using System.Diagnostics;
7 using System.Diagnostics.CodeAnalysis;
8 using System.Globalization;
9 using System.Runtime.Caching.Configuration;
10 using System.Runtime.InteropServices;
11 using System.Security;
12 using System.Threading;
13
14 namespace System.Runtime.Caching {
15     internal sealed class MemoryCacheStatistics : IDisposable {
16         const int MEMORYSTATUS_INTERVAL_5_SECONDS = 5 * 1000;
17         const int MEMORYSTATUS_INTERVAL_30_SECONDS = 30 * 1000;
18
19         private int _configCacheMemoryLimitMegabytes;
20         private int _configPhysicalMemoryLimitPercentage;
21         private int _configPollingInterval;
22         private int _inCacheManagerThread;
23         private int _disposed;
24         private long _lastTrimCount;
25         private long _lastTrimDurationTicks; // used only for debugging
26         private int _lastTrimGen2Count;
27         private int _lastTrimPercent;
28         private DateTime _lastTrimTime;        
29         private int _pollingInterval;
30         private GCHandleRef<Timer> _timerHandleRef;
31         private Object _timerLock;
32         private long _totalCountBeforeTrim;
33
34         private CacheMemoryMonitor _cacheMemoryMonitor;
35         private MemoryCache _memoryCache;
36         private PhysicalMemoryMonitor _physicalMemoryMonitor;        
37
38         // private
39
40         private MemoryCacheStatistics() {
41             //hide default ctor
42         }
43
44         private void AdjustTimer() {
45             lock (_timerLock) {
46
47                 if (_timerHandleRef == null)
48                     return;
49
50                 Timer timer = _timerHandleRef.Target;
51
52                 // the order of these if statements is important
53
54                 // When above the high pressure mark, interval should be 5 seconds or less
55                 if (_physicalMemoryMonitor.IsAboveHighPressure() || _cacheMemoryMonitor.IsAboveHighPressure()) {
56                     if (_pollingInterval > MEMORYSTATUS_INTERVAL_5_SECONDS) {
57                         _pollingInterval = MEMORYSTATUS_INTERVAL_5_SECONDS;
58                         timer.Change(_pollingInterval, _pollingInterval);
59                     }
60                     return;
61                 }
62
63                 // When above half the low pressure mark, interval should be 30 seconds or less
64                 if ((_cacheMemoryMonitor.PressureLast > _cacheMemoryMonitor.PressureLow / 2)
65                     || (_physicalMemoryMonitor.PressureLast > _physicalMemoryMonitor.PressureLow / 2)) {
66                     // DevDivBugs 104034: allow interval to fall back down when memory pressure goes away
67                     int newPollingInterval = Math.Min(_configPollingInterval, MEMORYSTATUS_INTERVAL_30_SECONDS);
68                     if (_pollingInterval != newPollingInterval) {
69                         _pollingInterval = newPollingInterval;
70                         timer.Change(_pollingInterval, _pollingInterval);
71                     }
72                     return;
73                 }
74
75                 // there is no pressure, interval should be the value from config
76                 if (_pollingInterval != _configPollingInterval) {
77                     _pollingInterval = _configPollingInterval;
78                     timer.Change(_pollingInterval, _pollingInterval);
79                 }
80             }
81         }
82
83         // timer callback
84         private void CacheManagerTimerCallback(object state) {
85             CacheManagerThread(0);
86         }
87
88         internal long GetLastSize() {
89             return this._cacheMemoryMonitor.PressureLast;
90         }
91
92         private int GetPercentToTrim() {
93             int gen2Count = GC.CollectionCount(2);
94             // has there been a Gen 2 Collection since the last trim?
95             if (gen2Count != _lastTrimGen2Count) {
96                 return Math.Max(_physicalMemoryMonitor.GetPercentToTrim(_lastTrimTime, _lastTrimPercent), _cacheMemoryMonitor.GetPercentToTrim(_lastTrimTime, _lastTrimPercent));
97             }
98             else {
99                 return 0;
100             }
101         }
102
103         private void InitializeConfiguration(NameValueCollection config) {
104             MemoryCacheElement element = null;
105             if (!_memoryCache.ConfigLess) {
106                 MemoryCacheSection section = ConfigurationManager.GetSection("system.runtime.caching/memoryCache") as MemoryCacheSection;
107                 if (section != null) {
108                     element = section.NamedCaches[_memoryCache.Name];
109                 }
110             }
111
112             if (element != null) {
113                 _configCacheMemoryLimitMegabytes = element.CacheMemoryLimitMegabytes;
114                 _configPhysicalMemoryLimitPercentage = element.PhysicalMemoryLimitPercentage;
115                 double milliseconds = element.PollingInterval.TotalMilliseconds;
116                 _configPollingInterval = (milliseconds < (double)Int32.MaxValue) ? (int) milliseconds : Int32.MaxValue;
117             }
118             else {
119                 _configPollingInterval = ConfigUtil.DefaultPollingTimeMilliseconds;
120                 _configCacheMemoryLimitMegabytes = 0;
121                 _configPhysicalMemoryLimitPercentage = 0;
122             }
123
124             if (config != null) {
125                 _configPollingInterval = ConfigUtil.GetIntValueFromTimeSpan(config, ConfigUtil.PollingInterval, _configPollingInterval);
126                 _configCacheMemoryLimitMegabytes = ConfigUtil.GetIntValue(config, ConfigUtil.CacheMemoryLimitMegabytes, _configCacheMemoryLimitMegabytes, true, Int32.MaxValue);
127                 _configPhysicalMemoryLimitPercentage = ConfigUtil.GetIntValue(config, ConfigUtil.PhysicalMemoryLimitPercentage, _configPhysicalMemoryLimitPercentage, true, 100);
128             }
129         }
130
131         private void InitDisposableMembers() {
132             bool dispose = true;
133             try {
134                 _cacheMemoryMonitor = new CacheMemoryMonitor(_memoryCache, _configCacheMemoryLimitMegabytes);
135                 Timer timer = new Timer(new TimerCallback(CacheManagerTimerCallback), null, _configPollingInterval, _configPollingInterval);
136                 _timerHandleRef = new GCHandleRef<Timer>(timer);
137                 dispose = false;
138             }
139             finally {
140                 if (dispose) {
141                     Dispose();
142                 }
143             }
144         }
145
146         private void SetTrimStats(long trimDurationTicks, long totalCountBeforeTrim, long trimCount) {
147             _lastTrimDurationTicks = trimDurationTicks;
148
149             int gen2Count = GC.CollectionCount(2);
150             // has there been a Gen 2 Collection since the last trim?
151             if (gen2Count != _lastTrimGen2Count) {
152                 _lastTrimTime = DateTime.UtcNow;
153                 _totalCountBeforeTrim = totalCountBeforeTrim;
154                 _lastTrimCount = trimCount;
155             }
156             else {
157                 // we've done multiple trims between Gen 2 collections, so only add to the trim count
158                 _lastTrimCount += trimCount;
159             }
160             _lastTrimGen2Count = gen2Count;
161
162             _lastTrimPercent = (int)((_lastTrimCount * 100L) / _totalCountBeforeTrim);
163         }
164
165         private void Update() {
166             _physicalMemoryMonitor.Update();
167             _cacheMemoryMonitor.Update();
168         }
169
170
171
172         // public/internal
173
174         internal long CacheMemoryLimit {
175             get {
176                 return _cacheMemoryMonitor.MemoryLimit;
177             }
178         }
179         
180         internal long PhysicalMemoryLimit {
181             get {
182                 return _physicalMemoryMonitor.MemoryLimit;
183             }
184         }
185
186         internal TimeSpan PollingInterval {
187             get {
188                 return TimeSpan.FromMilliseconds(_configPollingInterval);
189             }
190         }
191
192         internal MemoryCacheStatistics(MemoryCache memoryCache, NameValueCollection config) {
193             _memoryCache = memoryCache;
194             _lastTrimGen2Count = -1;
195             _lastTrimTime = DateTime.MinValue;
196             _timerLock = new Object();
197             InitializeConfiguration(config);
198             _pollingInterval = _configPollingInterval;
199             _physicalMemoryMonitor = new PhysicalMemoryMonitor(_configPhysicalMemoryLimitPercentage);
200             InitDisposableMembers();
201         }
202
203         [SecuritySafeCritical]
204         internal long CacheManagerThread(int minPercent) {
205             if (Interlocked.Exchange(ref _inCacheManagerThread, 1) != 0)
206                 return 0;
207             try {
208                 if (_disposed == 1) {
209                     return 0;
210                 }
211 #if DBG
212                 Dbg.Trace("MemoryCacheStats", "**BEG** CacheManagerThread " + DateTime.Now.ToString("T", CultureInfo.InvariantCulture));
213 #endif
214                 // The timer thread must always call Update so that the CacheManager
215                 // knows the size of the cache.
216                 Update();
217                 AdjustTimer();
218
219                 int percent = Math.Max(minPercent, GetPercentToTrim());
220                 long beginTotalCount = _memoryCache.GetCount();
221                 Stopwatch sw = Stopwatch.StartNew();
222                 long trimmedOrExpired = _memoryCache.Trim(percent);
223                 sw.Stop();
224                 // 1) don't update stats if the trim happend because MAX_COUNT was exceeded
225                 // 2) don't update stats unless we removed at least one entry
226                 if (percent > 0 && trimmedOrExpired > 0) {
227                     SetTrimStats(sw.Elapsed.Ticks, beginTotalCount, trimmedOrExpired);
228                 }
229
230 #if DBG
231                 Dbg.Trace("MemoryCacheStats", "**END** CacheManagerThread: "
232                             + ", percent=" + percent
233                             + ", beginTotalCount=" + beginTotalCount
234                             + ", trimmed=" + trimmedOrExpired
235                             + ", Milliseconds=" + sw.ElapsedMilliseconds);
236 #endif
237
238 #if PERF
239                 SafeNativeMethods.OutputDebugString("CacheCommon.CacheManagerThread:"
240                                                     + " minPercent= " + minPercent
241                                                     + ", percent= " + percent
242                                                     + ", beginTotalCount=" + beginTotalCount
243                                                     + ", trimmed=" + trimmedOrExpired
244                                                     + ", Milliseconds=" + sw.ElapsedMilliseconds + "\n");
245 #endif
246                 return trimmedOrExpired;
247             }
248             finally {
249                 Interlocked.Exchange(ref _inCacheManagerThread, 0);
250             }
251         }
252
253         public void Dispose() {
254             if (Interlocked.Exchange(ref _disposed, 1) == 0) {
255                 lock (_timerLock) {
256                     GCHandleRef<Timer> timerHandleRef = _timerHandleRef;
257                     if (timerHandleRef != null && Interlocked.CompareExchange(ref _timerHandleRef, null, timerHandleRef) == timerHandleRef) {
258                         timerHandleRef.Dispose();
259                         Dbg.Trace("MemoryCacheStats", "Stopped CacheMemoryTimers");
260                     }
261                 }
262                 while (_inCacheManagerThread != 0) {
263                     Thread.Sleep(100);
264                 }
265                 if (_cacheMemoryMonitor != null) {
266                     _cacheMemoryMonitor.Dispose();
267                 }
268                 // Don't need to call GC.SuppressFinalize(this) for sealed types without finalizers.
269             }
270         }
271
272         [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Grandfathered suppression from original caching code checkin")]
273         internal void UpdateConfig(NameValueCollection config) {
274             int pollingInterval = ConfigUtil.GetIntValueFromTimeSpan(config, ConfigUtil.PollingInterval, _configPollingInterval);
275             int cacheMemoryLimitMegabytes = ConfigUtil.GetIntValue(config, ConfigUtil.CacheMemoryLimitMegabytes, _configCacheMemoryLimitMegabytes, true, Int32.MaxValue);
276             int physicalMemoryLimitPercentage = ConfigUtil.GetIntValue(config, ConfigUtil.PhysicalMemoryLimitPercentage, _configPhysicalMemoryLimitPercentage, true, 100);
277
278             if (pollingInterval != _configPollingInterval) {
279                 lock (_timerLock) {
280                     _configPollingInterval = pollingInterval;
281                 }
282             }
283
284             if (cacheMemoryLimitMegabytes == _configCacheMemoryLimitMegabytes 
285                 && physicalMemoryLimitPercentage == _configPhysicalMemoryLimitPercentage) {
286                 return;
287             }
288             
289             try {
290                 try {
291                 }
292                 finally {
293                     // prevent ThreadAbortEx from interrupting
294                     while (Interlocked.Exchange(ref _inCacheManagerThread, 1) != 0) {
295                         Thread.Sleep(100);
296                     }
297                 }
298                 if (_disposed == 0) {
299                     if (cacheMemoryLimitMegabytes != _configCacheMemoryLimitMegabytes) {
300                         _cacheMemoryMonitor.SetLimit(cacheMemoryLimitMegabytes);
301                         _configCacheMemoryLimitMegabytes = cacheMemoryLimitMegabytes;
302                     }
303                     if (physicalMemoryLimitPercentage != _configPhysicalMemoryLimitPercentage) {
304                         _physicalMemoryMonitor.SetLimit(physicalMemoryLimitPercentage);
305                         _configPhysicalMemoryLimitPercentage = physicalMemoryLimitPercentage;
306                     }
307                 }
308             }
309             finally {
310                 Interlocked.Exchange(ref _inCacheManagerThread, 0);
311             }
312         }
313     }
314 }
315