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;
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() {
50 // the order of these if statements is important
52 // When above the high pressure mark, interval should be 5 seconds or less
53 if (_physicalMemoryMonitor.IsAboveHighPressure() || _cacheMemoryMonitor.IsAboveHighPressure()) {
54 if (_pollingInterval > MEMORYSTATUS_INTERVAL_5_SECONDS) {
55 _pollingInterval = MEMORYSTATUS_INTERVAL_5_SECONDS;
56 _timer.Change(_pollingInterval, _pollingInterval);
61 // When above half the low pressure mark, interval should be 30 seconds or less
62 if ((_cacheMemoryMonitor.PressureLast > _cacheMemoryMonitor.PressureLow / 2)
63 || (_physicalMemoryMonitor.PressureLast > _physicalMemoryMonitor.PressureLow / 2)) {
64 // DevDivBugs 104034: allow interval to fall back down when memory pressure goes away
65 int newPollingInterval = Math.Min(_configPollingInterval, MEMORYSTATUS_INTERVAL_30_SECONDS);
66 if (_pollingInterval != newPollingInterval) {
67 _pollingInterval = newPollingInterval;
68 _timer.Change(_pollingInterval, _pollingInterval);
73 // there is no pressure, interval should be the value from config
74 if (_pollingInterval != _configPollingInterval) {
75 _pollingInterval = _configPollingInterval;
76 _timer.Change(_pollingInterval, _pollingInterval);
82 private void CacheManagerTimerCallback(object state) {
83 CacheManagerThread(0);
86 private int GetPercentToTrim() {
87 int gen2Count = GC.CollectionCount(2);
88 // has there been a Gen 2 Collection since the last trim?
89 if (gen2Count != _lastTrimGen2Count) {
90 return Math.Max(_physicalMemoryMonitor.GetPercentToTrim(_lastTrimTime, _lastTrimPercent), _cacheMemoryMonitor.GetPercentToTrim(_lastTrimTime, _lastTrimPercent));
97 private void InitializeConfiguration(NameValueCollection config) {
98 MemoryCacheElement element = null;
99 if (!_memoryCache.ConfigLess) {
100 MemoryCacheSection section = ConfigurationManager.GetSection("system.runtime.caching/memoryCache") as MemoryCacheSection;
101 if (section != null) {
102 element = section.NamedCaches[_memoryCache.Name];
106 if (element != null) {
107 _configCacheMemoryLimitMegabytes = element.CacheMemoryLimitMegabytes;
108 _configPhysicalMemoryLimitPercentage = element.PhysicalMemoryLimitPercentage;
109 double milliseconds = element.PollingInterval.TotalMilliseconds;
110 _configPollingInterval = (milliseconds < (double)Int32.MaxValue) ? (int) milliseconds : Int32.MaxValue;
113 _configPollingInterval = ConfigUtil.DefaultPollingTimeMilliseconds;
114 _configCacheMemoryLimitMegabytes = 0;
115 _configPhysicalMemoryLimitPercentage = 0;
118 if (config != null) {
119 _configPollingInterval = ConfigUtil.GetIntValueFromTimeSpan(config, ConfigUtil.PollingInterval, _configPollingInterval);
120 _configCacheMemoryLimitMegabytes = ConfigUtil.GetIntValue(config, ConfigUtil.CacheMemoryLimitMegabytes, _configCacheMemoryLimitMegabytes, true, Int32.MaxValue);
121 _configPhysicalMemoryLimitPercentage = ConfigUtil.GetIntValue(config, ConfigUtil.PhysicalMemoryLimitPercentage, _configPhysicalMemoryLimitPercentage, true, 100);
125 private void InitDisposableMembers() {
128 _cacheMemoryMonitor = new CacheMemoryMonitor(_memoryCache, _configCacheMemoryLimitMegabytes);
129 _timer = new Timer(new TimerCallback(CacheManagerTimerCallback), null, _configPollingInterval, _configPollingInterval);
139 private void SetTrimStats(long trimDurationTicks, long totalCountBeforeTrim, long trimCount) {
140 _lastTrimDurationTicks = trimDurationTicks;
142 int gen2Count = GC.CollectionCount(2);
143 // has there been a Gen 2 Collection since the last trim?
144 if (gen2Count != _lastTrimGen2Count) {
145 _lastTrimTime = DateTime.UtcNow;
146 _totalCountBeforeTrim = totalCountBeforeTrim;
147 _lastTrimCount = trimCount;
150 // we've done multiple trims between Gen 2 collections, so only add to the trim count
151 _lastTrimCount += trimCount;
153 _lastTrimGen2Count = gen2Count;
155 _lastTrimPercent = (int)((_lastTrimCount * 100L) / _totalCountBeforeTrim);
158 private void Update() {
159 _physicalMemoryMonitor.Update();
160 _cacheMemoryMonitor.Update();
167 internal long CacheMemoryLimit {
169 return _cacheMemoryMonitor.MemoryLimit;
173 internal long PhysicalMemoryLimit {
175 return _physicalMemoryMonitor.MemoryLimit;
179 internal TimeSpan PollingInterval {
181 return TimeSpan.FromMilliseconds(_configPollingInterval);
185 internal MemoryCacheStatistics(MemoryCache memoryCache, NameValueCollection config) {
186 _memoryCache = memoryCache;
187 _lastTrimGen2Count = -1;
188 _lastTrimTime = DateTime.MinValue;
189 _timerLock = new Object();
190 InitializeConfiguration(config);
191 _pollingInterval = _configPollingInterval;
192 _physicalMemoryMonitor = new PhysicalMemoryMonitor(_configPhysicalMemoryLimitPercentage);
193 InitDisposableMembers();
196 [SecuritySafeCritical]
197 internal long CacheManagerThread(int minPercent) {
198 if (Interlocked.Exchange(ref _inCacheManagerThread, 1) != 0)
201 if (_disposed == 1) {
205 Dbg.Trace("MemoryCacheStats", "**BEG** CacheManagerThread " + DateTime.Now.ToString("T", CultureInfo.InvariantCulture));
207 // The timer thread must always call Update so that the CacheManager
208 // knows the size of the cache.
212 int percent = Math.Max(minPercent, GetPercentToTrim());
213 long beginTotalCount = _memoryCache.GetCount();
214 Stopwatch sw = Stopwatch.StartNew();
215 long trimmedOrExpired = _memoryCache.Trim(percent);
217 // 1) don't update stats if the trim happend because MAX_COUNT was exceeded
218 // 2) don't update stats unless we removed at least one entry
219 if (percent > 0 && trimmedOrExpired > 0) {
220 SetTrimStats(sw.Elapsed.Ticks, beginTotalCount, trimmedOrExpired);
224 Dbg.Trace("MemoryCacheStats", "**END** CacheManagerThread: "
225 + ", percent=" + percent
226 + ", beginTotalCount=" + beginTotalCount
227 + ", trimmed=" + trimmedOrExpired
228 + ", Milliseconds=" + sw.ElapsedMilliseconds);
232 SafeNativeMethods.OutputDebugString("CacheCommon.CacheManagerThread:"
233 + " minPercent= " + minPercent
234 + ", percent= " + percent
235 + ", beginTotalCount=" + beginTotalCount
236 + ", trimmed=" + trimmedOrExpired
237 + ", Milliseconds=" + sw.ElapsedMilliseconds + "\n");
239 return trimmedOrExpired;
242 Interlocked.Exchange(ref _inCacheManagerThread, 0);
246 public void Dispose() {
247 if (Interlocked.Exchange(ref _disposed, 1) == 0) {
249 Timer timer = _timer;
250 if (timer != null && Interlocked.CompareExchange(ref _timer, null, timer) == timer) {
252 Dbg.Trace("MemoryCacheStats", "Stopped CacheMemoryTimers");
255 while (_inCacheManagerThread != 0) {
258 if (_cacheMemoryMonitor != null) {
259 _cacheMemoryMonitor.Dispose();
261 // Don't need to call GC.SuppressFinalize(this) for sealed types without finalizers.
265 [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Grandfathered suppression from original caching code checkin")]
266 internal void UpdateConfig(NameValueCollection config) {
267 int pollingInterval = ConfigUtil.GetIntValueFromTimeSpan(config, ConfigUtil.PollingInterval, _configPollingInterval);
268 int cacheMemoryLimitMegabytes = ConfigUtil.GetIntValue(config, ConfigUtil.CacheMemoryLimitMegabytes, _configCacheMemoryLimitMegabytes, true, Int32.MaxValue);
269 int physicalMemoryLimitPercentage = ConfigUtil.GetIntValue(config, ConfigUtil.PhysicalMemoryLimitPercentage, _configPhysicalMemoryLimitPercentage, true, 100);
271 if (pollingInterval != _configPollingInterval) {
273 _configPollingInterval = pollingInterval;
277 if (cacheMemoryLimitMegabytes == _configCacheMemoryLimitMegabytes
278 && physicalMemoryLimitPercentage == _configPhysicalMemoryLimitPercentage) {
286 // prevent ThreadAbortEx from interrupting
287 while (Interlocked.Exchange(ref _inCacheManagerThread, 1) != 0) {
291 if (_disposed == 0) {
292 if (cacheMemoryLimitMegabytes != _configCacheMemoryLimitMegabytes) {
293 _cacheMemoryMonitor.SetLimit(cacheMemoryLimitMegabytes);
294 _configCacheMemoryLimitMegabytes = cacheMemoryLimitMegabytes;
296 if (physicalMemoryLimitPercentage != _configPhysicalMemoryLimitPercentage) {
297 _physicalMemoryMonitor.SetLimit(physicalMemoryLimitPercentage);
298 _configPhysicalMemoryLimitPercentage = physicalMemoryLimitPercentage;
303 Interlocked.Exchange(ref _inCacheManagerThread, 0);