|
// <copyright file="MemoryCacheStats.cs" company="Microsoft">
// Copyright (c) 2009 Microsoft Corporation. All rights reserved.
// </copyright>
using System.Collections.Specialized;
using System.Configuration;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Runtime.Caching.Configuration;
using System.Runtime.InteropServices;
using System.Security;
using System.Threading;
namespace System.Runtime.Caching {
internal sealed class MemoryCacheStatistics : IDisposable {
const int MEMORYSTATUS_INTERVAL_5_SECONDS = 5 * 1000;
const int MEMORYSTATUS_INTERVAL_30_SECONDS = 30 * 1000;
private int _configCacheMemoryLimitMegabytes;
private int _configPhysicalMemoryLimitPercentage;
private int _configPollingInterval;
private int _inCacheManagerThread;
private int _disposed;
private long _lastTrimCount;
private long _lastTrimDurationTicks; // used only for debugging
private int _lastTrimGen2Count;
private int _lastTrimPercent;
private DateTime _lastTrimTime;
private int _pollingInterval;
private GCHandleRef<Timer> _timerHandleRef;
private Object _timerLock;
private long _totalCountBeforeTrim;
private CacheMemoryMonitor _cacheMemoryMonitor;
private MemoryCache _memoryCache;
private PhysicalMemoryMonitor _physicalMemoryMonitor;
// private
private MemoryCacheStatistics() {
//hide default ctor
}
private void AdjustTimer() {
lock (_timerLock) {
if (_timerHandleRef == null)
return;
Timer timer = _timerHandleRef.Target;
// the order of these if statements is important
// When above the high pressure mark, interval should be 5 seconds or less
if (_physicalMemoryMonitor.IsAboveHighPressure() || _cacheMemoryMonitor.IsAboveHighPressure()) {
if (_pollingInterval > MEMORYSTATUS_INTERVAL_5_SECONDS) {
_pollingInterval = MEMORYSTATUS_INTERVAL_5_SECONDS;
timer.Change(_pollingInterval, _pollingInterval);
}
return;
}
// When above half the low pressure mark, interval should be 30 seconds or less
if ((_cacheMemoryMonitor.PressureLast > _cacheMemoryMonitor.PressureLow / 2)
|| (_physicalMemoryMonitor.PressureLast > _physicalMemoryMonitor.PressureLow / 2)) {
// DevDivBugs 104034: allow interval to fall back down when memory pressure goes away
int newPollingInterval = Math.Min(_configPollingInterval, MEMORYSTATUS_INTERVAL_30_SECONDS);
if (_pollingInterval != newPollingInterval) {
_pollingInterval = newPollingInterval;
timer.Change(_pollingInterval, _pollingInterval);
}
return;
}
// there is no pressure, interval should be the value from config
if (_pollingInterval != _configPollingInterval) {
_pollingInterval = _configPollingInterval;
timer.Change(_pollingInterval, _pollingInterval);
}
}
}
// timer callback
private void CacheManagerTimerCallback(object state) {
CacheManagerThread(0);
}
internal long GetLastSize() {
return this._cacheMemoryMonitor.PressureLast;
}
private int GetPercentToTrim() {
int gen2Count = GC.CollectionCount(2);
// has there been a Gen 2 Collection since the last trim?
if (gen2Count != _lastTrimGen2Count) {
return Math.Max(_physicalMemoryMonitor.GetPercentToTrim(_lastTrimTime, _lastTrimPercent), _cacheMemoryMonitor.GetPercentToTrim(_lastTrimTime, _lastTrimPercent));
}
else {
return 0;
}
}
private void InitializeConfiguration(NameValueCollection config) {
MemoryCacheElement element = null;
if (!_memoryCache.ConfigLess) {
MemoryCacheSection section = ConfigurationManager.GetSection("system.runtime.caching/memoryCache") as MemoryCacheSection;
if (section != null) {
element = section.NamedCaches[_memoryCache.Name];
}
}
if (element != null) {
_configCacheMemoryLimitMegabytes = element.CacheMemoryLimitMegabytes;
_configPhysicalMemoryLimitPercentage = element.PhysicalMemoryLimitPercentage;
double milliseconds = element.PollingInterval.TotalMilliseconds;
_configPollingInterval = (milliseconds < (double)Int32.MaxValue) ? (int) milliseconds : Int32.MaxValue;
}
else {
_configPollingInterval = ConfigUtil.DefaultPollingTimeMilliseconds;
_configCacheMemoryLimitMegabytes = 0;
_configPhysicalMemoryLimitPercentage = 0;
}
if (config != null) {
_configPollingInterval = ConfigUtil.GetIntValueFromTimeSpan(config, ConfigUtil.PollingInterval, _configPollingInterval);
_configCacheMemoryLimitMegabytes = ConfigUtil.GetIntValue(config, ConfigUtil.CacheMemoryLimitMegabytes, _configCacheMemoryLimitMegabytes, true, Int32.MaxValue);
_configPhysicalMemoryLimitPercentage = ConfigUtil.GetIntValue(config, ConfigUtil.PhysicalMemoryLimitPercentage, _configPhysicalMemoryLimitPercentage, true, 100);
}
}
private void InitDisposableMembers() {
bool dispose = true;
try {
_cacheMemoryMonitor = new CacheMemoryMonitor(_memoryCache, _configCacheMemoryLimitMegabytes);
Timer timer = new Timer(new TimerCallback(CacheManagerTimerCallback), null, _configPollingInterval, _configPollingInterval);
_timerHandleRef = new GCHandleRef<Timer>(timer);
dispose = false;
}
finally {
if (dispose) {
Dispose();
}
}
}
private void SetTrimStats(long trimDurationTicks, long totalCountBeforeTrim, long trimCount) {
_lastTrimDurationTicks = trimDurationTicks;
int gen2Count = GC.CollectionCount(2);
// has there been a Gen 2 Collection since the last trim?
if (gen2Count != _lastTrimGen2Count) {
_lastTrimTime = DateTime.UtcNow;
_totalCountBeforeTrim = totalCountBeforeTrim;
_lastTrimCount = trimCount;
}
else {
// we've done multiple trims between Gen 2 collections, so only add to the trim count
_lastTrimCount += trimCount;
}
_lastTrimGen2Count = gen2Count;
_lastTrimPercent = (int)((_lastTrimCount * 100L) / _totalCountBeforeTrim);
}
private void Update() {
_physicalMemoryMonitor.Update();
_cacheMemoryMonitor.Update();
}
// public/internal
internal long CacheMemoryLimit {
get {
return _cacheMemoryMonitor.MemoryLimit;
}
}
internal long PhysicalMemoryLimit {
get {
return _physicalMemoryMonitor.MemoryLimit;
}
}
internal TimeSpan PollingInterval {
get {
return TimeSpan.FromMilliseconds(_configPollingInterval);
}
}
internal MemoryCacheStatistics(MemoryCache memoryCache, NameValueCollection config) {
_memoryCache = memoryCache;
_lastTrimGen2Count = -1;
_lastTrimTime = DateTime.MinValue;
_timerLock = new Object();
InitializeConfiguration(config);
_pollingInterval = _configPollingInterval;
_physicalMemoryMonitor = new PhysicalMemoryMonitor(_configPhysicalMemoryLimitPercentage);
InitDisposableMembers();
}
[SecuritySafeCritical]
internal long CacheManagerThread(int minPercent) {
if (Interlocked.Exchange(ref _inCacheManagerThread, 1) != 0)
return 0;
try {
if (_disposed == 1) {
return 0;
}
#if DBG
Dbg.Trace("MemoryCacheStats", "**BEG** CacheManagerThread " + DateTime.Now.ToString("T", CultureInfo.InvariantCulture));
#endif
// The timer thread must always call Update so that the CacheManager
// knows the size of the cache.
Update();
AdjustTimer();
int percent = Math.Max(minPercent, GetPercentToTrim());
long beginTotalCount = _memoryCache.GetCount();
Stopwatch sw = Stopwatch.StartNew();
long trimmedOrExpired = _memoryCache.Trim(percent);
sw.Stop();
// 1) don't update stats if the trim happend because MAX_COUNT was exceeded
// 2) don't update stats unless we removed at least one entry
if (percent > 0 && trimmedOrExpired > 0) {
SetTrimStats(sw.Elapsed.Ticks, beginTotalCount, trimmedOrExpired);
}
#if DBG
Dbg.Trace("MemoryCacheStats", "**END** CacheManagerThread: "
+ ", percent=" + percent
+ ", beginTotalCount=" + beginTotalCount
+ ", trimmed=" + trimmedOrExpired
+ ", Milliseconds=" + sw.ElapsedMilliseconds);
#endif
#if PERF
SafeNativeMethods.OutputDebugString("CacheCommon.CacheManagerThread:"
+ " minPercent= " + minPercent
+ ", percent= " + percent
+ ", beginTotalCount=" + beginTotalCount
+ ", trimmed=" + trimmedOrExpired
+ ", Milliseconds=" + sw.ElapsedMilliseconds + "\n");
#endif
return trimmedOrExpired;
}
finally {
Interlocked.Exchange(ref _inCacheManagerThread, 0);
}
}
public void Dispose() {
if (Interlocked.Exchange(ref _disposed, 1) == 0) {
lock (_timerLock) {
GCHandleRef<Timer> timerHandleRef = _timerHandleRef;
if (timerHandleRef != null && Interlocked.CompareExchange(ref _timerHandleRef, null, timerHandleRef) == timerHandleRef) {
timerHandleRef.Dispose();
Dbg.Trace("MemoryCacheStats", "Stopped CacheMemoryTimers");
}
}
while (_inCacheManagerThread != 0) {
Thread.Sleep(100);
}
if (_cacheMemoryMonitor != null) {
_cacheMemoryMonitor.Dispose();
}
// Don't need to call GC.SuppressFinalize(this) for sealed types without finalizers.
}
}
[SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Grandfathered suppression from original caching code checkin")]
internal void UpdateConfig(NameValueCollection config) {
int pollingInterval = ConfigUtil.GetIntValueFromTimeSpan(config, ConfigUtil.PollingInterval, _configPollingInterval);
int cacheMemoryLimitMegabytes = ConfigUtil.GetIntValue(config, ConfigUtil.CacheMemoryLimitMegabytes, _configCacheMemoryLimitMegabytes, true, Int32.MaxValue);
int physicalMemoryLimitPercentage = ConfigUtil.GetIntValue(config, ConfigUtil.PhysicalMemoryLimitPercentage, _configPhysicalMemoryLimitPercentage, true, 100);
if (pollingInterval != _configPollingInterval) {
lock (_timerLock) {
_configPollingInterval = pollingInterval;
}
}
if (cacheMemoryLimitMegabytes == _configCacheMemoryLimitMegabytes
&& physicalMemoryLimitPercentage == _configPhysicalMemoryLimitPercentage) {
return;
}
try {
try {
}
finally {
// prevent ThreadAbortEx from interrupting
while (Interlocked.Exchange(ref _inCacheManagerThread, 1) != 0) {
Thread.Sleep(100);
}
}
if (_disposed == 0) {
if (cacheMemoryLimitMegabytes != _configCacheMemoryLimitMegabytes) {
_cacheMemoryMonitor.SetLimit(cacheMemoryLimitMegabytes);
_configCacheMemoryLimitMegabytes = cacheMemoryLimitMegabytes;
}
if (physicalMemoryLimitPercentage != _configPhysicalMemoryLimitPercentage) {
_physicalMemoryMonitor.SetLimit(physicalMemoryLimitPercentage);
_configPhysicalMemoryLimitPercentage = physicalMemoryLimitPercentage;
}
}
}
finally {
Interlocked.Exchange(ref _inCacheManagerThread, 0);
}
}
}
}
|