/// <summary> /// Initializes a new instance of the <see cref="T:Wibblr.Metrics.Core.EventCollector"/> class. /// This particular constructor is internal as it contains a parameter for the IClock, /// which is only used during testing. /// </summary> /// <param name="sink">Sink.</param> /// <param name="clock">Clock.</param> /// <param name="windowSize">Resolution.</param> /// <param name="flushInterval">Flush interval.</param> internal MetricsCollector(IMetricsSink sink, IClock clock, TimeSpan windowSize, TimeSpan flushInterval, bool ignoreEmptyBuckets) { if (windowSize.TotalMilliseconds < 100) { throw new ArgumentException("Must be 100ms or greater", nameof(windowSize)); } if (flushInterval.TotalMilliseconds < 200) { throw new ArgumentException("Must be 200ms or greater", nameof(flushInterval)); } if (flushInterval < windowSize) { throw new ArgumentException("Cannot be less than the window size", nameof(flushInterval)); } if (!windowSize.IsDivisorOf(TimeSpan.FromDays(1))) { throw new ArgumentException("Must be whole number of windows per day", nameof(windowSize)); } this.sink = sink; this.clock = clock; this.windowSize = windowSize; this.flushInterval = flushInterval; this.ignoreEmptyBuckets = true; clock.SetDelayedAction(Flush); clock.ExecuteAfterDelay(flushInterval); }
/// <summary> /// Flush this instance. /// </summary> private void Flush() { try { bool isFlushCancelled = clock.IsDelayedActionCancelled(); var tempCounters = new List <WindowedCounter>(); var tempBuckets = new List <WindowedBucket>(); var tempEvents = new List <TimestampedEvent>(); var tempProfiles = new List <Profile>(); // Any events that are recorded after this line will have a start time equal // or later to this. var currentTimePeriodStart = clock.Current.RoundDown(windowSize); lock (countersLock) { foreach (var c in counters.Keys.ToList()) { // Only take counts before the current timeperiod (unless // the delayedAction is cancelled, i.e. there will be no more flushes), // so that each counter is only written once for each window. if (isFlushCancelled || c.from < currentTimePeriodStart) { tempCounters.Add(new WindowedCounter { name = c.name, from = c.from, to = c.to, count = counters[c] }); counters.Remove(c); } } } // Flush even if there are no events, so that any queued events // in the sink are flushed. if (tempCounters != null) { sink.Flush(tempCounters); } lock (histogramsLock) { foreach (var h in histograms.Keys.ToList()) { if (isFlushCancelled || h.from < currentTimePeriodStart) { foreach (var bucket in histograms[h].Buckets()) { if (ignoreEmptyBuckets && bucket.count == 0) { continue; } tempBuckets.Add(new WindowedBucket { name = h.name, timeFrom = h.from, timeTo = h.to, valueFrom = bucket.from, valueTo = bucket.to, count = bucket.count }); } histograms.Remove(h); } } } if (tempBuckets != null) { sink.Flush(tempBuckets); } lock (eventsLock) { foreach (var name in events.Keys) { foreach (var timestamp in events[name]) { tempEvents.Add(new TimestampedEvent { name = name, timestamp = timestamp }); } } events.Clear(); } if (tempEvents != null) { sink.Flush(tempEvents); } lock (profileLock) { tempProfiles = new List <Profile>(profileData); } profileData.Clear(); if (tempProfiles != null) { sink.Flush(tempProfiles); } } catch (Exception e) { Console.Error.Write(e.Message); } try { sink.FlushComplete(); } catch (Exception e) { Console.Error.Write(e.Message); } try { // this will have no effect if the clock is in the process of cancelling clock.ExecuteAfterDelay(flushInterval); } catch (Exception e) { Console.Error.Write(e.Message); } }