private bool AlertProxyMonitors(long current, long limit, RecycleLimitNotificationFrequency frequency) { bool requestGC = false; KeyValuePair <RecycleLimitMonitor, string>[] proxies = null; lock (this) { if (_proxyMonitors.Count == 0) { StopTimer(); return(requestGC); } proxies = _proxyMonitors.ToArray <KeyValuePair <RecycleLimitMonitor, string> >(); } foreach (KeyValuePair <RecycleLimitMonitor, string> pair in proxies) { try { var ac = _appManager.GetLockableAppDomainContext(pair.Value); if (ac != null) { lock (ac) { requestGC |= pair.Key.RaiseRecycleLimitEvent(current, limit, frequency); } } } catch (Exception e) { // Unhandled Exceptions here will crash the process Misc.ReportUnhandledException(e, new string[] { SR.GetString(SR.Unhandled_Monitor_Exception, "RaiseRecycleLimitEvent", "RecycleLimitMonitor") }); } } return(requestGC); }
internal bool RaiseRecycleLimitEvent(long current, long limit, RecycleLimitNotificationFrequency frequency) { RecycleLimitInfo info = new RecycleLimitInfo(current, limit, frequency); IObserver <RecycleLimitInfo>[] observers; if (_isStarted) { lock (_observers) { observers = _observers.ToArray(); } foreach (IObserver <RecycleLimitInfo> obs in observers) { try { obs.OnNext(info); } catch (Exception e) { // Unhandled Exceptions here will crash the process Misc.ReportUnhandledException(e, new string[] { SR.GetString(SR.Unhandled_Monitor_Exception, "RaiseRecycleLimitEvent", "RecycleLimitMonitor") }); } } return(info.RequestGC); } return(false); }
public RecycleLimitInfo(long currentPrivateBytes, long recycleLimit, RecycleLimitNotificationFrequency recycleLimitNearFrequency) { _currentPB = currentPrivateBytes; _recycleLimit = recycleLimit; _recycleLimitNearFrequency = recycleLimitNearFrequency; _requestGC = false; }
private void CollectInfrequently(long privateBytes) { // not thread-safe, only invoke from timer callback Debug.Assert(_inPBytesMonitorThread == 1); // The Server GC on x86 can traverse ~200mb per CPU per second, and the maximum heap size // is about 3400mb, so the worst case scenario on x86 would take about 8 seconds to collect // on a dual CPU box. // // The Server GC on x64 can traverse ~300mb per CPU per second, so a 6000 MB heap will take // about 10 seconds to collect on a dual CPU box. The worst case scenario on x64 would make // you want to return your hardware for a refund. long timeSinceInducedGC = DateTime.UtcNow.Subtract(_inducedGCFinishTime).Ticks; bool infrequent = (timeSinceInducedGC > _inducedGCMinInterval); RecycleLimitNotificationFrequency frequency = RecycleLimitNotificationFrequency.Medium; // if we haven't collected recently, or if the trim percent is low (less than 50%), // we need to collect again if (infrequent || _howFrequent < 5) { // if we're inducing GC too frequently, increase the trim percentage, but don't go above 50% if (!infrequent) { _howFrequent = Math.Min(5, _howFrequent + 1); frequency = RecycleLimitNotificationFrequency.High; } // if we're inducing GC infrequently, we may want to decrease the trim percentage else if (_howFrequent > 1 && timeSinceInducedGC > 2 * _inducedGCMinInterval) { _howFrequent = Math.Max(1, _howFrequent - 1); frequency = RecycleLimitNotificationFrequency.Low; } Stopwatch sw1 = Stopwatch.StartNew(); bool requestGC = AlertProxyMonitors(privateBytes, _limit, frequency); sw1.Stop(); // if (!requestGC || _appManager.ShutdownInProgress) { return; } // collect and record statistics Stopwatch sw2 = Stopwatch.StartNew(); GC.Collect(); sw2.Stop(); _inducedGCCount++; // only used for debugging _inducedGCFinishTime = DateTime.UtcNow; _inducedGCDurationTicks = sw2.Elapsed.Ticks; _inducedGCPostPrivateBytes = NextSample(); _inducedGCPrivateBytesChange = privateBytes - _inducedGCPostPrivateBytes; // target 3.3% Time in GC, but don't induce a GC more than once every 5 seconds // Notes on calculation below: If G is duration of garbage collection and T is duration // between starting the next collection, then G/T is % Time in GC. If we target 3.3%, // then G/T = 3.3% = 33/1000, so T = G * 1000/33. _inducedGCMinInterval = Math.Max(_inducedGCDurationTicks * 1000 / 33, 5 * TimeSpan.TicksPerSecond); // no more frequently than every 60 seconds if change is less than 1% if (_inducedGCPrivateBytesChange * 100 <= privateBytes) { _inducedGCMinInterval = Math.Max(_inducedGCMinInterval, 60 * TimeSpan.TicksPerSecond); } #if DBG Debug.Trace("RecycleLimitMonitorSingleton", "GC.COLLECT STATS " + "TrimCaches(" + frequency + ")" + ", trimDurationSeconds=" + (sw1.Elapsed.Ticks / TimeSpan.TicksPerSecond) + ", requestGC=" + requestGC + ", #secondsSinceInducedGC=" + (timeSinceInducedGC / TimeSpan.TicksPerSecond) + ", InducedGCCount=" + _inducedGCCount + ", gcDurationSeconds=" + (_inducedGCDurationTicks / TimeSpan.TicksPerSecond) + ", PrePrivateBytes=" + privateBytes + ", PostPrivateBytes=" + _inducedGCPostPrivateBytes + ", PrivateBytesChange=" + _inducedGCPrivateBytesChange + ", gcMinIntervalSeconds=" + (_inducedGCMinInterval / TimeSpan.TicksPerSecond)); #endif #if PERF SafeNativeMethods.OutputDebugString(" ** COLLECT **: " + percent + "%, " + (sw1.Elapsed.Ticks / TimeSpan.TicksPerSecond) + " seconds" + ", infrequent=" + infrequent + ", removed=" + trimmedOrExpired + ", sinceIGC=" + (timeSinceInducedGC / TimeSpan.TicksPerSecond) + ", IGCCount=" + _inducedGCCount + ", IGCDuration=" + (_inducedGCDurationTicks / TimeSpan.TicksPerSecond) + ", preBytes=" + privateBytes + ", postBytes=" + _inducedGCPostPrivateBytes + ", byteChange=" + _inducedGCPrivateBytesChange + ", IGCMinInterval=" + (_inducedGCMinInterval / TimeSpan.TicksPerSecond) + "\n"); #endif } }