1 // <copyright file="MemoryCacheStats.cs" company="Microsoft">
2 // Copyright (c) 2009 Microsoft Corporation. All rights reserved.
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;
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;
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;
34 private CacheMemoryMonitor _cacheMemoryMonitor;
35 private MemoryCache _memoryCache;
36 private PhysicalMemoryMonitor _physicalMemoryMonitor;
40 private MemoryCacheStatistics() {
44 private void AdjustTimer() {
47 if (_timerHandleRef == null)
50 Timer timer = _timerHandleRef.Target;
52 // the order of these if statements is important
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);
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);
75 // there is no pressure, interval should be the value from config
76 if (_pollingInterval != _configPollingInterval) {
77 _pollingInterval = _configPollingInterval;
78 timer.Change(_pollingInterval, _pollingInterval);
84 private void CacheManagerTimerCallback(object state) {
85 CacheManagerThread(0);
88 internal long GetLastSize() {
89 return this._cacheMemoryMonitor.PressureLast;
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));
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];
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;
119 _configPollingInterval = ConfigUtil.DefaultPollingTimeMilliseconds;
120 _configCacheMemoryLimitMegabytes = 0;
121 _configPhysicalMemoryLimitPercentage = 0;
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);
131 private void InitDisposableMembers() {
134 _cacheMemoryMonitor = new CacheMemoryMonitor(_memoryCache, _configCacheMemoryLimitMegabytes);
135 Timer timer = new Timer(new TimerCallback(CacheManagerTimerCallback), null, _configPollingInterval, _configPollingInterval);
136 _timerHandleRef = new GCHandleRef<Timer>(timer);
146 private void SetTrimStats(long trimDurationTicks, long totalCountBeforeTrim, long trimCount) {
147 _lastTrimDurationTicks = trimDurationTicks;
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;
157 // we've done multiple trims between Gen 2 collections, so only add to the trim count
158 _lastTrimCount += trimCount;
160 _lastTrimGen2Count = gen2Count;
162 _lastTrimPercent = (int)((_lastTrimCount * 100L) / _totalCountBeforeTrim);
165 private void Update() {
166 _physicalMemoryMonitor.Update();
167 _cacheMemoryMonitor.Update();
174 internal long CacheMemoryLimit {
176 return _cacheMemoryMonitor.MemoryLimit;
180 internal long PhysicalMemoryLimit {
182 return _physicalMemoryMonitor.MemoryLimit;
186 internal TimeSpan PollingInterval {
188 return TimeSpan.FromMilliseconds(_configPollingInterval);
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();
203 [SecuritySafeCritical]
204 internal long CacheManagerThread(int minPercent) {
205 if (Interlocked.Exchange(ref _inCacheManagerThread, 1) != 0)
208 if (_disposed == 1) {
212 Dbg.Trace("MemoryCacheStats", "**BEG** CacheManagerThread " + DateTime.Now.ToString("T", CultureInfo.InvariantCulture));
214 // The timer thread must always call Update so that the CacheManager
215 // knows the size of the cache.
219 int percent = Math.Max(minPercent, GetPercentToTrim());
220 long beginTotalCount = _memoryCache.GetCount();
221 Stopwatch sw = Stopwatch.StartNew();
222 long trimmedOrExpired = _memoryCache.Trim(percent);
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);
231 Dbg.Trace("MemoryCacheStats", "**END** CacheManagerThread: "
232 + ", percent=" + percent
233 + ", beginTotalCount=" + beginTotalCount
234 + ", trimmed=" + trimmedOrExpired
235 + ", Milliseconds=" + sw.ElapsedMilliseconds);
239 SafeNativeMethods.OutputDebugString("CacheCommon.CacheManagerThread:"
240 + " minPercent= " + minPercent
241 + ", percent= " + percent
242 + ", beginTotalCount=" + beginTotalCount
243 + ", trimmed=" + trimmedOrExpired
244 + ", Milliseconds=" + sw.ElapsedMilliseconds + "\n");
246 return trimmedOrExpired;
249 Interlocked.Exchange(ref _inCacheManagerThread, 0);
253 public void Dispose() {
254 if (Interlocked.Exchange(ref _disposed, 1) == 0) {
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");
262 while (_inCacheManagerThread != 0) {
265 if (_cacheMemoryMonitor != null) {
266 _cacheMemoryMonitor.Dispose();
268 // Don't need to call GC.SuppressFinalize(this) for sealed types without finalizers.
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);
278 if (pollingInterval != _configPollingInterval) {
280 _configPollingInterval = pollingInterval;
284 if (cacheMemoryLimitMegabytes == _configCacheMemoryLimitMegabytes
285 && physicalMemoryLimitPercentage == _configPhysicalMemoryLimitPercentage) {
293 // prevent ThreadAbortEx from interrupting
294 while (Interlocked.Exchange(ref _inCacheManagerThread, 1) != 0) {
298 if (_disposed == 0) {
299 if (cacheMemoryLimitMegabytes != _configCacheMemoryLimitMegabytes) {
300 _cacheMemoryMonitor.SetLimit(cacheMemoryLimitMegabytes);
301 _configCacheMemoryLimitMegabytes = cacheMemoryLimitMegabytes;
303 if (physicalMemoryLimitPercentage != _configPhysicalMemoryLimitPercentage) {
304 _physicalMemoryMonitor.SetLimit(physicalMemoryLimitPercentage);
305 _configPhysicalMemoryLimitPercentage = physicalMemoryLimitPercentage;
310 Interlocked.Exchange(ref _inCacheManagerThread, 0);