private EventCounter GetOrAdd(T key, long customSize) { EventCounter c; lock (syncLock) { keysToCounters.TryGetValue(key, out c); if (c == null) { if (customSize < 0) { customSize = 0; } //Increment global counter first, so cleanup routine makes enough room bytesUsed += itemSize + customSize; if (byteCeiling > 0 && bytesUsed >= byteCeiling) { Cleanup(CleanupMode.MakeRoom); } c = new EventCounter(granularity, (int)_trackingDuration.Ticks / granularity); c.CustomSize = customSize; keysToCounters.Add(key, c); countersToKeys.Add(c, key); } else if (customSize >= 0 && c.CustomSize != customSize) { bytesUsed += customSize - c.CustomSize; } } return(c); }
/// <summary> /// Increment the counter for the given item. Pings cleanup as well. /// </summary> /// <param name="key"></param> /// <param name="customSize"> Sets the custom size offset for the specified key (used for cleanup purposes). If -1, existing value will remain unchanged. </param> public void Increment(T key, long customSize) { EventCounter c = GetOrAdd(key, customSize); if (precision == EventCountingStrategy.ThreadingPrecision.Fast) { c.Increment(); } else { c.IncrementExact(); } PingCleanup(); }
/// <summary> /// Performs cleanup on the dictionaries in either MakeRoom or Maintenance mode. Returns true if the goal was achieved, false if the cleanup was canceled because another cleanup was executing concurrently. /// </summary> /// <param name="mode"></param> private bool Cleanup(CleanupMode mode) { if (mode == CleanupMode.MakeRoom && byteCeiling < 1) { return(true); //We don't perform minimal cleanups unless a ceiling is specified. } //We have to track removed items so we can fire events later. List <KeyValuePair <T, int> > removed = CounterRemoved != null ? new List <KeyValuePair <T, int> >() : null; //We have to weak lock method-level, because otherwise a background thread could be cleaning when GetOrAdd is called, and we could have a deadlock //With syncLock locked in GetOrAdd, waiting on cleanupLock, and Cleanup locked on cleanupLock, waiting on GetOrAdd. //If we didn't have any method-level lock, we'd waste resources with simultaneous cleanup runs. //sortLock is redundant with cleanupLock, but remains in case I decide to pull the method level lock if (!Monitor.TryEnter(cleanupLock)) { return(false); //Failed to lock, another thread is cleaning. } try { //In high precision, we lock the entire long-running process if (precision == EventCountingStrategy.ThreadingPrecision.Accurate) { Monitor.Enter(syncLock); } try { EventCounter[] counters; //In fast mode, only lock for the copy and delete. We can sort outside after taking a snapshot. //We wont remove newly added ones, but thats ok. lock (syncLock) { if (mode == CleanupMode.MakeRoom && bytesUsed < byteCeiling) { return(true); //Nothing to do, there is still room } counters = new EventCounter[countersToKeys.Count]; countersToKeys.Keys.CopyTo(counters, 0); //Clone } //Lock for sorting so we have synchronized access to a.sortValue lock (sortLock) { //Pause values for (int i = 0; i < counters.Length; i++) { counters[i].sortValue = counters[i].GetValue(); } //Sort lowest counters to the top using Quicksort Array.Sort <EventCounter>(counters, delegate(EventCounter a, EventCounter b) { return(a.sortValue - b.sortValue); }); } //Go back into lock lock (syncLock) { long removedBytes = 0; long goal = byteCeiling / 10; //10% is our goal if we are removing minimally. //Remove items for (int i = 0; i < counters.Length; i++) { EventCounter c = counters[i]; if (mode == CleanupMode.MakeRoom && removedBytes >= goal) { return(true); //Done, we hit our goal! } if (mode == CleanupMode.Maintenance && c.sortValue > 0) { return(true); //Done, We hit the end of the zeros } if (mode == CleanupMode.Maintenance && c.GetValue() > 0) { continue; //Skip counters that incremented while we were working. } //Look up key T key; countersToKeys.TryGetValue(c, out key); if (key.Equals(default(T))) { continue; //Skip counters that have already been removed } //Remove counter countersToKeys.Remove(c); keysToCounters.Remove(key); //Increment our local byte removal counter removedBytes += c.CustomSize + itemSize; //Decrement global counter bytesUsed = bytesUsed - c.CustomSize - itemSize; //store removed keys if (removed != null) { removed.Add(new KeyValuePair <T, int>(key, c.GetValue())); } } } } finally { if (precision == EventCountingStrategy.ThreadingPrecision.Accurate) { Monitor.Exit(syncLock); } } } finally { Monitor.Exit(cleanupLock); //Fire CounterRemoved event (may still be in syncLock)! if (removed != null && this.CounterRemoved != null) { foreach (KeyValuePair <T, int> p in removed) { CounterRemoved(this, p.Key, p.Value); } } } return(true); }