File: CacheMemoryMonitor.cs

package info (click to toggle)
mono 6.12.0.199%2Bdfsg-6
  • links: PTS, VCS
  • area: main
  • in suites: trixie
  • size: 1,296,836 kB
  • sloc: cs: 11,181,803; xml: 2,850,076; ansic: 699,709; cpp: 123,344; perl: 59,361; javascript: 30,841; asm: 21,853; makefile: 20,405; sh: 15,009; python: 4,839; pascal: 925; sql: 859; sed: 16; php: 1
file content (260 lines) | stat: -rw-r--r-- 10,969 bytes parent folder | download | duplicates (6)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
// <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);
                }
            }
        }
    }
}