|
// <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 SRefMultiple _sizedRefMultiple;
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];
if (memoryCache.UseMemoryCacheManager)
InitMemoryCacheManager(); // This magic thing connects us to ObjectCacheHost magically. :/
InitDisposableMembers(cacheMemoryLimitMegabytes);
}
private void InitDisposableMembers(int cacheMemoryLimitMegabytes) {
bool dispose = true;
try {
_sizedRefMultiple = new SRefMultiple(_memoryCache.AllSRefTargets);
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() {
SRefMultiple sref = _sizedRefMultiple;
if (sref != null && Interlocked.CompareExchange(ref _sizedRefMultiple, 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);
SRefMultiple sref = _sizedRefMultiple;
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);
}
}
}
}
}
|