2016-08-03 10:59:49 +00:00
|
|
|
// <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;
|
2016-11-10 13:04:39 +00:00
|
|
|
private GCHandleRef<Timer> _timerHandleRef;
|
2016-08-03 10:59:49 +00:00
|
|
|
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) {
|
|
|
|
|
2016-11-10 13:04:39 +00:00
|
|
|
if (_timerHandleRef == null)
|
2016-08-03 10:59:49 +00:00
|
|
|
return;
|
|
|
|
|
2016-11-10 13:04:39 +00:00
|
|
|
Timer timer = _timerHandleRef.Target;
|
|
|
|
|
2016-08-03 10:59:49 +00:00
|
|
|
// 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;
|
2016-11-10 13:04:39 +00:00
|
|
|
timer.Change(_pollingInterval, _pollingInterval);
|
2016-08-03 10:59:49 +00:00
|
|
|
}
|
|
|
|
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;
|
2016-11-10 13:04:39 +00:00
|
|
|
timer.Change(_pollingInterval, _pollingInterval);
|
2016-08-03 10:59:49 +00:00
|
|
|
}
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// there is no pressure, interval should be the value from config
|
|
|
|
if (_pollingInterval != _configPollingInterval) {
|
|
|
|
_pollingInterval = _configPollingInterval;
|
2016-11-10 13:04:39 +00:00
|
|
|
timer.Change(_pollingInterval, _pollingInterval);
|
2016-08-03 10:59:49 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// timer callback
|
|
|
|
private void CacheManagerTimerCallback(object state) {
|
|
|
|
CacheManagerThread(0);
|
|
|
|
}
|
|
|
|
|
2017-08-21 15:34:15 +00:00
|
|
|
internal long GetLastSize() {
|
|
|
|
return this._cacheMemoryMonitor.PressureLast;
|
|
|
|
}
|
|
|
|
|
2016-08-03 10:59:49 +00:00
|
|
|
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);
|
2016-11-10 13:04:39 +00:00
|
|
|
Timer timer = new Timer(new TimerCallback(CacheManagerTimerCallback), null, _configPollingInterval, _configPollingInterval);
|
|
|
|
_timerHandleRef = new GCHandleRef<Timer>(timer);
|
2016-08-03 10:59:49 +00:00
|
|
|
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) {
|
2016-11-10 13:04:39 +00:00
|
|
|
GCHandleRef<Timer> timerHandleRef = _timerHandleRef;
|
|
|
|
if (timerHandleRef != null && Interlocked.CompareExchange(ref _timerHandleRef, null, timerHandleRef) == timerHandleRef) {
|
|
|
|
timerHandleRef.Dispose();
|
2016-08-03 10:59:49 +00:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|