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);
        }