260 lines
10 KiB
C#
260 lines
10 KiB
C#
|
// <copyright file="CacheMemoryMonitor.cs" company="Microsoft">
|
||
|
// Copyright (c) 2009 Microsoft Corporation. All rights reserved.
|
||
|
// </copyright>
|
||
|
using System;
|
||
|
using System.Runtime.Caching.Configuration;
|
||
|
using System.Runtime.Caching.Hosting;
|
||
|
using System.Diagnostics.CodeAnalysis;
|
||
|
using System.Security;
|
||
|
using System.Security.Permissions;
|
||
|
using System.Threading;
|
||
|
|
||
|
namespace System.Runtime.Caching {
|
||
|
// CacheMemoryMonitor uses the internal System.SizedReference type to determine
|
||
|
// the size of the cache itselt, and helps us know when to drop entries to avoid
|
||
|
// exceeding the cache's memory limit. The limit is configurable (see ConfigUtil.cs).
|
||
|
internal sealed class CacheMemoryMonitor : MemoryMonitor, IDisposable {
|
||
|
const long PRIVATE_BYTES_LIMIT_2GB = 800 * MEGABYTE;
|
||
|
const long PRIVATE_BYTES_LIMIT_3GB = 1800 * MEGABYTE;
|
||
|
const long PRIVATE_BYTES_LIMIT_64BIT = 1L * TERABYTE;
|
||
|
const int SAMPLE_COUNT = 2;
|
||
|
|
||
|
private static IMemoryCacheManager s_memoryCacheManager;
|
||
|
private static long s_autoPrivateBytesLimit = -1;
|
||
|
private static long s_effectiveProcessMemoryLimit = -1;
|
||
|
|
||
|
private MemoryCache _memoryCache;
|
||
|
private long[] _cacheSizeSamples;
|
||
|
private DateTime[] _cacheSizeSampleTimes;
|
||
|
private int _idx;
|
||
|
private SRef _sizedRef;
|
||
|
private int _gen2Count;
|
||
|
private long _memoryLimit;
|
||
|
|
||
|
internal long MemoryLimit {
|
||
|
get { return _memoryLimit; }
|
||
|
}
|
||
|
|
||
|
private CacheMemoryMonitor() {
|
||
|
// hide default ctor
|
||
|
}
|
||
|
|
||
|
internal CacheMemoryMonitor(MemoryCache memoryCache, int cacheMemoryLimitMegabytes) {
|
||
|
_memoryCache = memoryCache;
|
||
|
_gen2Count = GC.CollectionCount(2);
|
||
|
_cacheSizeSamples = new long[SAMPLE_COUNT];
|
||
|
_cacheSizeSampleTimes = new DateTime[SAMPLE_COUNT];
|
||
|
InitMemoryCacheManager();
|
||
|
InitDisposableMembers(cacheMemoryLimitMegabytes);
|
||
|
}
|
||
|
|
||
|
private void InitDisposableMembers(int cacheMemoryLimitMegabytes) {
|
||
|
bool dispose = true;
|
||
|
try {
|
||
|
_sizedRef = new SRef(_memoryCache);
|
||
|
SetLimit(cacheMemoryLimitMegabytes);
|
||
|
InitHistory();
|
||
|
dispose = false;
|
||
|
}
|
||
|
finally {
|
||
|
if (dispose) {
|
||
|
Dispose();
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Auto-generate the private bytes limit:
|
||
|
// - On 64bit, the auto value is MIN(60% physical_ram, 1 TB)
|
||
|
// - On x86, for 2GB, the auto value is MIN(60% physical_ram, 800 MB)
|
||
|
// - On x86, for 3GB, the auto value is MIN(60% physical_ram, 1800 MB)
|
||
|
//
|
||
|
// - If it's not a hosted environment (e.g. console app), the 60% in the above
|
||
|
// formulas will become 100% because in un-hosted environment we don't launch
|
||
|
// other processes such as compiler, etc.
|
||
|
private static long AutoPrivateBytesLimit {
|
||
|
get {
|
||
|
long memoryLimit = s_autoPrivateBytesLimit;
|
||
|
if (memoryLimit == -1) {
|
||
|
|
||
|
bool is64bit = (IntPtr.Size == 8);
|
||
|
|
||
|
long totalPhysical = TotalPhysical;
|
||
|
long totalVirtual = TotalVirtual;
|
||
|
if (totalPhysical != 0) {
|
||
|
long recommendedPrivateByteLimit;
|
||
|
if (is64bit) {
|
||
|
recommendedPrivateByteLimit = PRIVATE_BYTES_LIMIT_64BIT;
|
||
|
}
|
||
|
else {
|
||
|
// Figure out if it's 2GB or 3GB
|
||
|
|
||
|
if (totalVirtual > 2 * GIGABYTE) {
|
||
|
recommendedPrivateByteLimit = PRIVATE_BYTES_LIMIT_3GB;
|
||
|
}
|
||
|
else {
|
||
|
recommendedPrivateByteLimit = PRIVATE_BYTES_LIMIT_2GB;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// use 60% of physical RAM
|
||
|
long usableMemory = totalPhysical * 3 / 5;
|
||
|
memoryLimit = Math.Min(usableMemory, recommendedPrivateByteLimit);
|
||
|
}
|
||
|
else {
|
||
|
// If GlobalMemoryStatusEx fails, we'll use these as our auto-gen private bytes limit
|
||
|
memoryLimit = is64bit ? PRIVATE_BYTES_LIMIT_64BIT : PRIVATE_BYTES_LIMIT_2GB;
|
||
|
}
|
||
|
Interlocked.Exchange(ref s_autoPrivateBytesLimit, memoryLimit);
|
||
|
}
|
||
|
|
||
|
return memoryLimit;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public void Dispose() {
|
||
|
SRef sref = _sizedRef;
|
||
|
if (sref != null && Interlocked.CompareExchange(ref _sizedRef, null, sref) == sref) {
|
||
|
sref.Dispose();
|
||
|
}
|
||
|
IMemoryCacheManager memoryCacheManager = s_memoryCacheManager;
|
||
|
if (memoryCacheManager != null) {
|
||
|
memoryCacheManager.ReleaseCache(_memoryCache);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
internal static long EffectiveProcessMemoryLimit {
|
||
|
get {
|
||
|
long memoryLimit = s_effectiveProcessMemoryLimit;
|
||
|
if (memoryLimit == -1) {
|
||
|
memoryLimit = AutoPrivateBytesLimit;
|
||
|
Interlocked.Exchange(ref s_effectiveProcessMemoryLimit, memoryLimit);
|
||
|
}
|
||
|
return memoryLimit;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
protected override int GetCurrentPressure() {
|
||
|
// Call GetUpdatedTotalCacheSize to update the total
|
||
|
// cache size, if there has been a recent Gen 2 Collection.
|
||
|
// This update must happen, otherwise the CacheManager won't
|
||
|
// know the total cache size.
|
||
|
int gen2Count = GC.CollectionCount(2);
|
||
|
SRef sref = _sizedRef;
|
||
|
if (gen2Count != _gen2Count && sref != null) {
|
||
|
// update _gen2Count
|
||
|
_gen2Count = gen2Count;
|
||
|
|
||
|
// the SizedRef is only updated after a Gen2 Collection
|
||
|
|
||
|
// increment the index (it's either 1 or 0)
|
||
|
Dbg.Assert(SAMPLE_COUNT == 2);
|
||
|
_idx = _idx ^ 1;
|
||
|
// remember the sample time
|
||
|
_cacheSizeSampleTimes[_idx] = DateTime.UtcNow;
|
||
|
// remember the sample value
|
||
|
_cacheSizeSamples[_idx] = sref.ApproximateSize;
|
||
|
#if DBG
|
||
|
Dbg.Trace("MemoryCacheStats", "SizedRef.ApproximateSize=" + _cacheSizeSamples[_idx]);
|
||
|
#endif
|
||
|
IMemoryCacheManager memoryCacheManager = s_memoryCacheManager;
|
||
|
if (memoryCacheManager != null) {
|
||
|
memoryCacheManager.UpdateCacheSize(_cacheSizeSamples[_idx], _memoryCache);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// if there's no memory limit, then there's nothing more to do
|
||
|
if (_memoryLimit <= 0) {
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
long cacheSize = _cacheSizeSamples[_idx];
|
||
|
|
||
|
// use _memoryLimit as an upper bound so that pressure is a percentage (between 0 and 100, inclusive).
|
||
|
if (cacheSize > _memoryLimit) {
|
||
|
cacheSize = _memoryLimit;
|
||
|
}
|
||
|
|
||
|
// PerfCounter: Cache Percentage Process Memory Limit Used
|
||
|
// = memory used by this process / process memory limit at pressureHigh
|
||
|
// Set private bytes used in kilobytes because the counter is a DWORD
|
||
|
|
||
|
//
|
||
|
|
||
|
|
||
|
int result = (int)(cacheSize * 100 / _memoryLimit);
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
internal override int GetPercentToTrim(DateTime lastTrimTime, int lastTrimPercent) {
|
||
|
int percent = 0;
|
||
|
if (IsAboveHighPressure()) {
|
||
|
long cacheSize = _cacheSizeSamples[_idx];
|
||
|
if (cacheSize > _memoryLimit) {
|
||
|
percent = Math.Min(100, (int)((cacheSize - _memoryLimit) * 100L / cacheSize));
|
||
|
}
|
||
|
|
||
|
#if PERF
|
||
|
SafeNativeMethods.OutputDebugString(String.Format("CacheMemoryMonitor.GetPercentToTrim: percent={0:N}, lastTrimPercent={1:N}\n",
|
||
|
percent,
|
||
|
lastTrimPercent));
|
||
|
#endif
|
||
|
|
||
|
}
|
||
|
return percent;
|
||
|
}
|
||
|
|
||
|
internal void SetLimit(int cacheMemoryLimitMegabytes) {
|
||
|
long cacheMemoryLimit = cacheMemoryLimitMegabytes;
|
||
|
cacheMemoryLimit = cacheMemoryLimit << MEGABYTE_SHIFT;
|
||
|
|
||
|
//
|
||
|
_memoryLimit = 0;
|
||
|
|
||
|
// VSWhidbey 546381: never override what the user specifies as the limit;
|
||
|
// only call AutoPrivateBytesLimit when the user does not specify one.
|
||
|
if (cacheMemoryLimit == 0 && _memoryLimit == 0) {
|
||
|
// Zero means we impose a limit
|
||
|
_memoryLimit = EffectiveProcessMemoryLimit;
|
||
|
}
|
||
|
else if (cacheMemoryLimit != 0 && _memoryLimit != 0) {
|
||
|
// Take the min of "cache memory limit" and the host's "process memory limit".
|
||
|
_memoryLimit = Math.Min(_memoryLimit, cacheMemoryLimit);
|
||
|
}
|
||
|
else if (cacheMemoryLimit != 0) {
|
||
|
// _memoryLimit is 0, but "cache memory limit" is non-zero, so use it as the limit
|
||
|
_memoryLimit = cacheMemoryLimit;
|
||
|
}
|
||
|
|
||
|
Dbg.Trace("MemoryCacheStats", "CacheMemoryMonitor.SetLimit: _memoryLimit=" + (_memoryLimit >> MEGABYTE_SHIFT) + "Mb");
|
||
|
|
||
|
if (_memoryLimit > 0) {
|
||
|
_pressureHigh = 100;
|
||
|
_pressureLow = 80;
|
||
|
}
|
||
|
else {
|
||
|
_pressureHigh = 99;
|
||
|
_pressureLow = 97;
|
||
|
}
|
||
|
|
||
|
Dbg.Trace("MemoryCacheStats", "CacheMemoryMonitor.SetLimit: _pressureHigh=" + _pressureHigh +
|
||
|
", _pressureLow=" + _pressureLow);
|
||
|
}
|
||
|
|
||
|
[SecuritySafeCritical]
|
||
|
[PermissionSet(SecurityAction.Assert, Unrestricted = true)]
|
||
|
[SuppressMessage("Microsoft.Security", "CA2106:SecureAsserts", Justification = "Grandfathered suppression from original caching code checkin")]
|
||
|
private static void InitMemoryCacheManager() {
|
||
|
if (s_memoryCacheManager == null) {
|
||
|
IMemoryCacheManager memoryCacheManager = null;
|
||
|
IServiceProvider host = ObjectCache.Host;
|
||
|
if (host != null) {
|
||
|
memoryCacheManager = host.GetService(typeof(IMemoryCacheManager)) as IMemoryCacheManager;
|
||
|
}
|
||
|
if (memoryCacheManager != null) {
|
||
|
Interlocked.CompareExchange(ref s_memoryCacheManager, memoryCacheManager, null);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|