diff options
Diffstat (limited to 'src/mscorlib/shared/System/Diagnostics/Tracing/EventCounter.cs')
-rw-r--r-- | src/mscorlib/shared/System/Diagnostics/Tracing/EventCounter.cs | 436 |
1 files changed, 436 insertions, 0 deletions
diff --git a/src/mscorlib/shared/System/Diagnostics/Tracing/EventCounter.cs b/src/mscorlib/shared/System/Diagnostics/Tracing/EventCounter.cs new file mode 100644 index 0000000000..b1f946464e --- /dev/null +++ b/src/mscorlib/shared/System/Diagnostics/Tracing/EventCounter.cs @@ -0,0 +1,436 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Threading; +#if ES_BUILD_PCL + using System.Threading.Tasks; +#endif + +#if ES_BUILD_STANDALONE +namespace Microsoft.Diagnostics.Tracing +#else +namespace System.Diagnostics.Tracing +#endif +{ + /// <summary> + /// Provides the ability to collect statistics through EventSource + /// </summary> + public class EventCounter + { + /// <summary> + /// Initializes a new instance of the <see cref="EventCounter"/> class. + /// </summary> + /// <param name="name">The name.</param> + /// <param name="eventSource">The event source.</param> + public EventCounter(string name, EventSource eventSource) + { + if (name == null) + { + throw new ArgumentNullException(nameof(name)); + } + + if (eventSource == null) + { + throw new ArgumentNullException(nameof(eventSource)); + } + + InitializeBuffer(); + _name = name; + EventCounterGroup.AddEventCounter(eventSource, this); + } + + /// <summary> + /// Writes the metric. + /// </summary> + /// <param name="value">The value.</param> + public void WriteMetric(float value) + { + Enqueue(value); + } + + #region private implementation + + private readonly string _name; + + #region Buffer Management + + // Values buffering + private const int BufferedSize = 10; + private const float UnusedBufferSlotValue = float.NegativeInfinity; + private const int UnsetIndex = -1; + private volatile float[] _bufferedValues; + private volatile int _bufferedValuesIndex; + + private void InitializeBuffer() + { + _bufferedValues = new float[BufferedSize]; + for (int i = 0; i < _bufferedValues.Length; i++) + { + _bufferedValues[i] = UnusedBufferSlotValue; + } + } + + private void Enqueue(float value) + { + // It is possible that two threads read the same bufferedValuesIndex, but only one will be able to write the slot, so that is okay. + int i = _bufferedValuesIndex; + while (true) + { + float result = Interlocked.CompareExchange(ref _bufferedValues[i], value, UnusedBufferSlotValue); + i++; + if (_bufferedValues.Length <= i) + { + // It is possible that two threads both think the buffer is full, but only one get to actually flush it, the other + // will eventually enter this code path and potentially calling Flushing on a buffer that is not full, and that's okay too. + lock (_bufferedValues) + { + Flush(); + } + i = 0; + } + + if (result == UnusedBufferSlotValue) + { + // CompareExchange succeeded + _bufferedValuesIndex = i; + return; + } + } + } + + private void Flush() + { + for (int i = 0; i < _bufferedValues.Length; i++) + { + var value = Interlocked.Exchange(ref _bufferedValues[i], UnusedBufferSlotValue); + if (value != UnusedBufferSlotValue) + { + OnMetricWritten(value); + } + } + + _bufferedValuesIndex = 0; + } + + #endregion // Buffer Management + + #region Statistics Calculation + + // Statistics + private int _count; + private float _sum; + private float _sumSquared; + private float _min; + private float _max; + + private void OnMetricWritten(float value) + { + _sum += value; + _sumSquared += value * value; + if (_count == 0 || value > _max) + { + _max = value; + } + + if (_count == 0 || value < _min) + { + _min = value; + } + + _count++; + } + + internal EventCounterPayload GetEventCounterPayload() + { + lock (_bufferedValues) + { + Flush(); + EventCounterPayload result = new EventCounterPayload(); + result.Name = _name; + result.Count = _count; + result.Mean = _sum / _count; + result.StandardDerivation = (float)Math.Sqrt(_sumSquared / _count - _sum * _sum / _count / _count); + result.Min = _min; + result.Max = _max; + ResetStatistics(); + return result; + } + } + + private void ResetStatistics() + { + _count = 0; + _sum = 0; + _sumSquared = 0; + _min = 0; + _max = 0; + } + + #endregion // Statistics Calculation + + #endregion // private implementation + } + + #region internal supporting classes + + [EventData] + internal class EventCounterPayload : IEnumerable<KeyValuePair<string, object>> + { + public string Name { get; set; } + + public float Mean { get; set; } + + public float StandardDerivation { get; set; } + + public int Count { get; set; } + + public float Min { get; set; } + + public float Max { get; set; } + + public float IntervalSec { get; internal set; } + + #region Implementation of the IEnumerable interface + + public IEnumerator<KeyValuePair<string, object>> GetEnumerator() + { + return ForEnumeration.GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return ForEnumeration.GetEnumerator(); + } + + private IEnumerable<KeyValuePair<string, object>> ForEnumeration + { + get + { + yield return new KeyValuePair<string, object>("Name", Name); + yield return new KeyValuePair<string, object>("Mean", Mean); + yield return new KeyValuePair<string, object>("StandardDerivation", StandardDerivation); + yield return new KeyValuePair<string, object>("Count", Count); + yield return new KeyValuePair<string, object>("Min", Min); + yield return new KeyValuePair<string, object>("Max", Max); + } + } + + #endregion // Implementation of the IEnumerable interface + } + + internal class EventCounterGroup : IDisposable + { + private readonly EventSource _eventSource; + private readonly int _eventSourceIndex; + private readonly List<EventCounter> _eventCounters; + + internal EventCounterGroup(EventSource eventSource, int eventSourceIndex) + { + _eventSource = eventSource; + _eventSourceIndex = eventSourceIndex; + _eventCounters = new List<EventCounter>(); + RegisterCommandCallback(); + } + + private void Add(EventCounter eventCounter) + { + _eventCounters.Add(eventCounter); + } + + #region EventSource Command Processing + + private void RegisterCommandCallback() + { + _eventSource.EventCommandExecuted += OnEventSourceCommand; + } + + private void OnEventSourceCommand(object sender, EventCommandEventArgs e) + { + if (e.Command == EventCommand.Enable || e.Command == EventCommand.Update) + { + string valueStr; + float value; + if (e.Arguments.TryGetValue("EventCounterIntervalSec", out valueStr) && float.TryParse(valueStr, out value)) + { + EnableTimer(value); + } + } + } + + #endregion // EventSource Command Processing + + #region Global EventCounterGroup Array management + + private static EventCounterGroup[] s_eventCounterGroups; + + internal static void AddEventCounter(EventSource eventSource, EventCounter eventCounter) + { + int eventSourceIndex = EventListener.EventSourceIndex(eventSource); + + EventCounterGroup.EnsureEventSourceIndexAvailable(eventSourceIndex); + EventCounterGroup eventCounterGroup = GetEventCounterGroup(eventSource); + eventCounterGroup.Add(eventCounter); + } + + private static void EnsureEventSourceIndexAvailable(int eventSourceIndex) + { + if (EventCounterGroup.s_eventCounterGroups == null) + { + EventCounterGroup.s_eventCounterGroups = new EventCounterGroup[eventSourceIndex + 1]; + } + else if (eventSourceIndex >= EventCounterGroup.s_eventCounterGroups.Length) + { + EventCounterGroup[] newEventCounterGroups = new EventCounterGroup[eventSourceIndex + 1]; + Array.Copy(EventCounterGroup.s_eventCounterGroups, 0, newEventCounterGroups, 0, EventCounterGroup.s_eventCounterGroups.Length); + EventCounterGroup.s_eventCounterGroups = newEventCounterGroups; + } + } + + private static EventCounterGroup GetEventCounterGroup(EventSource eventSource) + { + int eventSourceIndex = EventListener.EventSourceIndex(eventSource); + EventCounterGroup result = EventCounterGroup.s_eventCounterGroups[eventSourceIndex]; + if (result == null) + { + result = new EventCounterGroup(eventSource, eventSourceIndex); + EventCounterGroup.s_eventCounterGroups[eventSourceIndex] = result; + } + + return result; + } + + #endregion // Global EventCounterGroup Array management + + #region Timer Processing + + private DateTime _timeStampSinceCollectionStarted; + private int _pollingIntervalInMilliseconds; + private Timer _pollingTimer; + + private void EnableTimer(float pollingIntervalInSeconds) + { + if (pollingIntervalInSeconds == 0) + { + if (_pollingTimer != null) + { + _pollingTimer.Dispose(); + _pollingTimer = null; + } + + _pollingIntervalInMilliseconds = 0; + } + else if (_pollingIntervalInMilliseconds == 0 || pollingIntervalInSeconds < _pollingIntervalInMilliseconds) + { + _pollingIntervalInMilliseconds = (int)(pollingIntervalInSeconds * 1000); + if (_pollingTimer != null) + { + _pollingTimer.Dispose(); + _pollingTimer = null; + } + + _timeStampSinceCollectionStarted = DateTime.Now; + _pollingTimer = new Timer(OnTimer, null, _pollingIntervalInMilliseconds, _pollingIntervalInMilliseconds); + } + } + + private void OnTimer(object state) + { + if (_eventSource.IsEnabled()) + { + DateTime now = DateTime.Now; + TimeSpan elapsed = now - _timeStampSinceCollectionStarted; + lock (_pollingTimer) + { + foreach (var eventCounter in _eventCounters) + { + EventCounterPayload payload = eventCounter.GetEventCounterPayload(); + payload.IntervalSec = (float)elapsed.TotalSeconds; + _eventSource.Write("EventCounters", new EventSourceOptions() { Level = EventLevel.LogAlways }, new { Payload = payload }); + } + + + _timeStampSinceCollectionStarted = now; + } + } + else + { + _pollingTimer.Dispose(); + _pollingTimer = null; + EventCounterGroup.s_eventCounterGroups[_eventSourceIndex] = null; + } + } + + #region PCL timer hack + +#if ES_BUILD_PCL + internal delegate void TimerCallback(object state); + + internal sealed class Timer : CancellationTokenSource, IDisposable + { + private int _period; + private TimerCallback _callback; + private object _state; + + internal Timer(TimerCallback callback, object state, int dueTime, int period) + { + _callback = callback; + _state = state; + _period = period; + Schedule(dueTime); + } + + private void Schedule(int dueTime) + { + Task.Delay(dueTime, Token).ContinueWith(OnTimer, null, CancellationToken.None, TaskContinuationOptions.ExecuteSynchronously | TaskContinuationOptions.OnlyOnRanToCompletion, TaskScheduler.Default); + } + + private void OnTimer(Task t, object s) + { + Schedule(_period); + _callback(_state); + } + + public new void Dispose() { base.Cancel(); } + } +#endif + #endregion // PCL timer hack + + #endregion // Timer Processing + + #region Implementation of the IDisposable interface + + private bool _disposed = false; + + public void Dispose() + { + Dispose(true); + } + + protected virtual void Dispose(bool disposing) + { + if (_disposed) + { + return; + } + + if (disposing) + { + if (_pollingTimer != null) + { + _pollingTimer.Dispose(); + _pollingTimer = null; + } + } + + _disposed = true; + } + + #endregion // Implementation of the IDisposable interface + } + + #endregion // internal supporting classes +} |