</ItemGroup>
<ItemGroup>
<Compile Include="$(MSBuildThisFileDirectory)System\Diagnostics\Tracing\ActivityTracker.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Diagnostics\Tracing\BaseCounter.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Diagnostics\Tracing\CounterGroup.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Diagnostics\Tracing\CounterPayload.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Diagnostics\Tracing\EventActivityOptions.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Diagnostics\Tracing\EventCounter.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Diagnostics\Tracing\EventDescriptor.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Diagnostics\Tracing\EventSourceException.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Diagnostics\Tracing\FrameworkEventSource.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Diagnostics\Tracing\IEventProvider.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Diagnostics\Tracing\IncrementingEventCounter.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Diagnostics\Tracing\IncrementingPollingCounter.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Diagnostics\Tracing\PollingCounter.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Diagnostics\Tracing\StubEnvironment.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Diagnostics\Tracing\Winmeta.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Diagnostics\Tracing\TraceLogging\ArrayTypeInfo.cs" />
--- /dev/null
+// 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.Diagnostics;
+using System.Collections;
+using System.Collections.Generic;
+using System.Text;
+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>
+ /// BaseCounter is an abstract class that serves as the parent class for various Counter* classes,
+ /// namely EventCounter, PollingCounter, IncrementingEventCounter, and IncrementingPollingCounter.
+ /// </summary>
+ public abstract class BaseCounter : IDisposable
+ {
+ /// <summary>
+ /// All Counters live as long as the EventSource that they are attached to unless they are
+ /// explicitly Disposed.
+ /// </summary>
+ /// <param name="name">The name.</param>
+ /// <param name="eventSource">The event source.</param>
+ public BaseCounter(string name, EventSource eventSource)
+ {
+ if (name == null)
+ {
+ throw new ArgumentNullException(nameof(_name));
+ }
+
+ if (eventSource == null)
+ {
+ throw new ArgumentNullException(nameof(eventSource));
+ }
+
+ _group = CounterGroup.GetCounterGroup(eventSource);
+ _group.Add(this);
+ _eventSource = eventSource;
+ _name = name;
+ _metaData = new Dictionary<string, string>();
+ }
+
+ /// <summary>
+ /// Removes the counter from set that the EventSource will report on. After being disposed, this
+ /// counter will do nothing and its resource will be reclaimed if all references to it are removed.
+ /// If an EventCounter is not explicitly disposed it will be cleaned up automatically when the
+ /// EventSource it is attached to dies.
+ /// </summary>
+ public void Dispose()
+ {
+ if (_group != null)
+ {
+ _group.Remove(this);
+ _group = null;
+ }
+ }
+
+ /// <summary>
+ /// Adds a key-value metadata to the EventCounter that will be included as a part of the payload
+ /// </summary>
+ internal void AddMetaData(string key, string value)
+ {
+ lock (MyLock)
+ {
+ _metaData.Add(key, value);
+ }
+ }
+
+ internal string DisplayName { get; set; }
+
+ #region private implementation
+
+ internal readonly string _name;
+
+ private CounterGroup _group;
+ private Dictionary<string, string> _metaData;
+ internal EventSource _eventSource;
+
+ internal abstract void WritePayload(float intervalSec);
+
+ // arbitrarily we use name as the lock object.
+ internal object MyLock { get { return _name; } }
+
+ internal void ReportOutOfBandMessage(string message)
+ {
+ _eventSource.ReportOutOfBandMessage(message, true);
+ }
+
+ internal string GetMetaDataString()
+ {
+ StringBuilder sb = new StringBuilder("");
+ foreach(KeyValuePair<string, string> kvPair in _metaData)
+ {
+ sb.Append($"{kvPair.Key}:{kvPair.Value},");
+ }
+ return sb.Length == 0 ? "" : sb.ToString(0, sb.Length - 1); // Get rid of the last ","
+ }
+
+ #endregion // private implementation
+ }
+}
--- /dev/null
+// 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.Diagnostics;
+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
+{
+ internal class CounterGroup
+ {
+ private readonly EventSource _eventSource;
+ private readonly List<BaseCounter> _counters;
+
+ internal CounterGroup(EventSource eventSource)
+ {
+ _eventSource = eventSource;
+ _counters = new List<BaseCounter>();
+ RegisterCommandCallback();
+ }
+
+ internal void Add(BaseCounter eventCounter)
+ {
+ lock (this) // Lock the CounterGroup
+ _counters.Add(eventCounter);
+ }
+
+ internal void Remove(BaseCounter eventCounter)
+ {
+ lock (this) // Lock the CounterGroup
+ _counters.Remove(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))
+ {
+ // Recursion through EventSource callbacks possible. When we enable the timer
+ // we synchonously issue a EventSource.Write event, which in turn can call back
+ // to user code (in an EventListener) while holding this lock. This is dangerous
+ // because it means this code might inadvertantly participate in a lock loop.
+ // The scenario seems very unlikely so we ignore that problem for now.
+ lock (this) // Lock the CounterGroup
+ {
+ EnableTimer(value);
+ }
+ }
+ }
+ }
+
+ #endregion // EventSource Command Processing
+
+ #region Global CounterGroup Array management
+
+ // We need eventCounters to 'attach' themselves to a particular EventSource.
+ // this table provides the mapping from EventSource -> CounterGroup
+ // which represents this 'attached' information.
+ private static WeakReference<CounterGroup>[] s_counterGroups;
+ private static readonly object s_counterGroupsLock = new object();
+
+ private static void EnsureEventSourceIndexAvailable(int eventSourceIndex)
+ {
+ Debug.Assert(Monitor.IsEntered(s_counterGroupsLock));
+ if (CounterGroup.s_counterGroups == null)
+ {
+ CounterGroup.s_counterGroups = new WeakReference<CounterGroup>[eventSourceIndex + 1];
+ }
+ else if (eventSourceIndex >= CounterGroup.s_counterGroups.Length)
+ {
+ WeakReference<CounterGroup>[] newCounterGroups = new WeakReference<CounterGroup>[eventSourceIndex + 1];
+ Array.Copy(CounterGroup.s_counterGroups, 0, newCounterGroups, 0, CounterGroup.s_counterGroups.Length);
+ CounterGroup.s_counterGroups = newCounterGroups;
+ }
+ }
+
+ internal static CounterGroup GetCounterGroup(EventSource eventSource)
+ {
+ lock (s_counterGroupsLock)
+ {
+ int eventSourceIndex = EventListener.EventSourceIndex(eventSource);
+ EnsureEventSourceIndexAvailable(eventSourceIndex);
+ WeakReference<CounterGroup> weakRef = CounterGroup.s_counterGroups[eventSourceIndex];
+ CounterGroup ret = null;
+ if (weakRef == null || !weakRef.TryGetTarget(out ret))
+ {
+ ret = new CounterGroup(eventSource);
+ CounterGroup.s_counterGroups[eventSourceIndex] = new WeakReference<CounterGroup>(ret);
+ }
+ return ret;
+ }
+ }
+
+ #endregion // Global CounterGroup Array management
+
+ #region Timer Processing
+
+ private DateTime _timeStampSinceCollectionStarted;
+ private int _pollingIntervalInMilliseconds;
+ private Timer _pollingTimer;
+
+ private void DisposeTimer()
+ {
+ Debug.Assert(Monitor.IsEntered(this));
+ if (_pollingTimer != null)
+ {
+ _pollingTimer.Dispose();
+ _pollingTimer = null;
+ }
+ }
+
+ private void EnableTimer(float pollingIntervalInSeconds)
+ {
+ Debug.Assert(Monitor.IsEntered(this));
+ if (pollingIntervalInSeconds <= 0)
+ {
+ DisposeTimer();
+ _pollingIntervalInMilliseconds = 0;
+ }
+ else if (_pollingIntervalInMilliseconds == 0 || pollingIntervalInSeconds * 1000 < _pollingIntervalInMilliseconds)
+ {
+ Debug.WriteLine("Polling interval changed at " + DateTime.UtcNow.ToString("mm.ss.ffffff"));
+ _pollingIntervalInMilliseconds = (int)(pollingIntervalInSeconds * 1000);
+ DisposeTimer();
+ _timeStampSinceCollectionStarted = DateTime.UtcNow;
+ // Don't capture the current ExecutionContext and its AsyncLocals onto the timer causing them to live forever
+ bool restoreFlow = false;
+ try
+ {
+ if (!ExecutionContext.IsFlowSuppressed())
+ {
+ ExecutionContext.SuppressFlow();
+ restoreFlow = true;
+ }
+
+ _pollingTimer = new Timer(s => ((CounterGroup)s).OnTimer(null), this, _pollingIntervalInMilliseconds, _pollingIntervalInMilliseconds);
+ }
+ finally
+ {
+ // Restore the current ExecutionContext
+ if (restoreFlow)
+ ExecutionContext.RestoreFlow();
+ }
+ }
+ // Always fire the timer event (so you see everything up to this time).
+ OnTimer(null);
+ }
+
+ private void OnTimer(object state)
+ {
+ Debug.WriteLine("Timer fired at " + DateTime.UtcNow.ToString("mm.ss.ffffff"));
+ lock (this) // Lock the CounterGroup
+ {
+ if (_eventSource.IsEnabled())
+ {
+ DateTime now = DateTime.UtcNow;
+ TimeSpan elapsed = now - _timeStampSinceCollectionStarted;
+
+ foreach (var counter in _counters)
+ {
+ counter.WritePayload((float)elapsed.TotalSeconds);
+ }
+ _timeStampSinceCollectionStarted = now;
+ }
+ else
+ {
+ DisposeTimer();
+ }
+ }
+ }
+
+ #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
+
+ }
+}
\ No newline at end of file
--- /dev/null
+// 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.Diagnostics;
+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
+{
+ // TODO: This should be removed as we make the new payloads public
+ [EventData]
+ internal class EventCounterPayload : IEnumerable<KeyValuePair<string, object>>
+ {
+ public string Name { get; set; }
+
+ public float Mean { get; set; }
+
+ public float StandardDeviation { 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>("StandardDeviation", StandardDeviation);
+ 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
+ }
+
+ [EventData]
+ internal class CounterPayload : IEnumerable<KeyValuePair<string, object>>
+ {
+ public string Name { get; set; }
+
+ public string DisplayName { get; set; }
+
+ public float Mean { get; set; }
+
+ public float StandardDeviation { get; set; }
+
+ public int Count { get; set; }
+
+ public float Min { get; set; }
+
+ public float Max { get; set; }
+
+ public float IntervalSec { get; internal set; }
+
+ public string MetaData { get; 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>("DisplayName", DisplayName);
+ yield return new KeyValuePair<string, object>("Mean", Mean);
+ yield return new KeyValuePair<string, object>("StandardDeviation", StandardDeviation);
+ yield return new KeyValuePair<string, object>("Count", Count);
+ yield return new KeyValuePair<string, object>("Min", Min);
+ yield return new KeyValuePair<string, object>("Max", Max);
+ yield return new KeyValuePair<string, object>("IntervalSec", IntervalSec);
+ yield return new KeyValuePair<string, object>("Series", $"Interval={IntervalSec}");
+ yield return new KeyValuePair<string, object>("CounterType", "Mean");
+ yield return new KeyValuePair<string, object>("MetaData", MetaData);
+ }
+ }
+
+ #endregion // Implementation of the IEnumerable interface
+ }
+
+ [EventData]
+ internal class IncrementingCounterPayload : IEnumerable<KeyValuePair<string, object>>
+ {
+ public string Name { get; set; }
+
+ public string DisplayName { get; set; }
+
+ public TimeSpan DisplayRateTimeScale { get; set; }
+
+ public float Increment { get; set; }
+
+ public float IntervalSec { get; internal set; }
+
+ public string MetaData { get; 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>("DisplayName", DisplayName);
+ yield return new KeyValuePair<string, object>("DisplayRateTimeScale", DisplayRateTimeScale.ToString("c"));
+ yield return new KeyValuePair<string, object>("Increment", Increment);
+ yield return new KeyValuePair<string, object>("IntervalSec", IntervalSec);
+ yield return new KeyValuePair<string, object>("Series", $"Interval={IntervalSec}");
+ yield return new KeyValuePair<string, object>("CounterType", "Sum");
+ yield return new KeyValuePair<string, object>("MetaData", MetaData);
+ }
+ }
+
+ #endregion // Implementation of the IEnumerable interface
+ }
+}
\ No newline at end of file
/// See https://github.com/dotnet/corefx/blob/master/src/System.Diagnostics.Tracing/tests/BasicEventSourceTest/TestEventCounter.cs
/// which shows tests, which are also useful in seeing actual use.
/// </summary>
- public partial class EventCounter : IDisposable
+ public partial class EventCounter : BaseCounter
{
/// <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)
+ public EventCounter(string name, EventSource eventSource) : base(name, eventSource)
{
- if (name == null)
- {
- throw new ArgumentNullException(nameof(name));
- }
-
- if (eventSource == null)
- {
- throw new ArgumentNullException(nameof(eventSource));
- }
-
- InitializeBuffer();
- _name = name;
- _group = EventCounterGroup.GetEventCounterGroup(eventSource);
- _group.Add(this);
_min = float.PositiveInfinity;
_max = float.NegativeInfinity;
+
+ InitializeBuffer();
}
/// <summary>
Enqueue(value);
}
- /// <summary>
- /// Removes the counter from set that the EventSource will report on. After being disposed, this
- /// counter will do nothing and its resource will be reclaimed if all references to it are removed.
- /// If an EventCounter is not explicitly disposed it will be cleaned up automatically when the
- /// EventSource it is attached to dies.
- /// </summary>
- public void Dispose()
- {
- var group = _group;
- if (group != null)
- {
- group.Remove(this);
- _group = null;
- }
- }
-
- public override string ToString()
- {
- return "EventCounter '" + _name + "' Count " + _count + " Mean " + (((double)_sum) / _count).ToString("n3");
- }
- #region private implementation
-
- private readonly string _name;
- private EventCounterGroup _group;
-
- #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;
-
- // arbitrarily we use _bufferfValues as the lock object.
- private object MyLock { get { return _bufferedValues; } }
-
- 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 (MyLock) // Lock the counter
- Flush();
- i = 0;
- }
-
- if (result == UnusedBufferSlotValue)
- {
- // CompareExchange succeeded
- _bufferedValuesIndex = i;
- return;
- }
- }
- }
-
- private void Flush()
- {
- Debug.Assert(Monitor.IsEntered(MyLock));
- 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
+ public override string ToString() => $"EventCounter '{_name}' Count {_count} Mean {(((double)_sum) / _count).ToString("n3")}";
#region Statistics Calculation
private float _min;
private float _max;
- private void OnMetricWritten(float value)
+ internal void OnMetricWritten(float value)
{
Debug.Assert(Monitor.IsEntered(MyLock));
_sum += value;
_count++;
}
- internal EventCounterPayload GetEventCounterPayload()
+ internal override void WritePayload(float intervalSec)
{
- lock (MyLock) // Lock the counter
+ lock (MyLock)
{
Flush();
- EventCounterPayload result = new EventCounterPayload();
- result.Name = _name;
- result.Count = _count;
+ EventCounterPayload payload = new EventCounterPayload();
+ payload.Name = _name;
+ payload.Count = _count;
+ payload.IntervalSec = intervalSec;
if (0 < _count)
{
- result.Mean = _sum / _count;
- result.StandardDeviation = (float)Math.Sqrt(_sumSquared / _count - _sum * _sum / _count / _count);
+ payload.Mean = _sum / _count;
+ payload.StandardDeviation = (float)Math.Sqrt(_sumSquared / _count - _sum * _sum / _count / _count);
}
else
{
- result.Mean = 0;
- result.StandardDeviation = 0;
+ payload.Mean = 0;
+ payload.StandardDeviation = 0;
}
- result.Min = _min;
- result.Max = _max;
+ payload.Min = _min;
+ payload.Max = _max;
ResetStatistics();
- return result;
+ _eventSource.Write("EventCounters", new EventSourceOptions() { Level = EventLevel.LogAlways }, new EventCounterPayloadType(payload));
}
}
-
private void ResetStatistics()
{
Debug.Assert(Monitor.IsEntered(MyLock));
#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 StandardDeviation { 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>("StandardDeviation", StandardDeviation);
- 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
- {
- private readonly EventSource _eventSource;
- private readonly List<EventCounter> _eventCounters;
-
- internal EventCounterGroup(EventSource eventSource)
- {
- _eventSource = eventSource;
- _eventCounters = new List<EventCounter>();
- RegisterCommandCallback();
- }
-
- internal void Add(EventCounter eventCounter)
- {
- lock (this) // Lock the EventCounterGroup
- _eventCounters.Add(eventCounter);
- }
-
- internal void Remove(EventCounter eventCounter)
- {
- lock (this) // Lock the EventCounterGroup
- _eventCounters.Remove(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))
- {
- // Recursion through EventSource callbacks possible. When we enable the timer
- // we synchonously issue a EventSource.Write event, which in turn can call back
- // to user code (in an EventListener) while holding this lock. This is dangerous
- // because it mean this code might inadvertantly participate in a lock loop.
- // The scenario seems very unlikely so we ignore that problem for now.
- lock (this) // Lock the EventCounterGroup
- {
- EnableTimer(value);
- }
- }
- }
- }
-
- #endregion // EventSource Command Processing
-
- #region Global EventCounterGroup Array management
-
- // We need eventCounters to 'attach' themselves to a particular EventSource.
- // this table provides the mapping from EventSource -> EventCounterGroup
- // which represents this 'attached' information.
- private static WeakReference<EventCounterGroup>[] s_eventCounterGroups;
- private static readonly object s_eventCounterGroupsLock = new object();
+ // 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 static void EnsureEventSourceIndexAvailable(int eventSourceIndex)
+ private void InitializeBuffer()
{
- Debug.Assert(Monitor.IsEntered(s_eventCounterGroupsLock));
- if (EventCounterGroup.s_eventCounterGroups == null)
- {
- EventCounterGroup.s_eventCounterGroups = new WeakReference<EventCounterGroup>[eventSourceIndex + 1];
- }
- else if (eventSourceIndex >= EventCounterGroup.s_eventCounterGroups.Length)
+ _bufferedValues = new float[BufferedSize];
+ for (int i = 0; i < _bufferedValues.Length; i++)
{
- WeakReference<EventCounterGroup>[] newEventCounterGroups = new WeakReference<EventCounterGroup>[eventSourceIndex + 1];
- Array.Copy(EventCounterGroup.s_eventCounterGroups, 0, newEventCounterGroups, 0, EventCounterGroup.s_eventCounterGroups.Length);
- EventCounterGroup.s_eventCounterGroups = newEventCounterGroups;
+ _bufferedValues[i] = UnusedBufferSlotValue;
}
}
- internal static EventCounterGroup GetEventCounterGroup(EventSource eventSource)
+ protected void Enqueue(float value)
{
- lock (s_eventCounterGroupsLock)
+ // 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)
{
- int eventSourceIndex = EventListener.EventSourceIndex(eventSource);
- EnsureEventSourceIndexAvailable(eventSourceIndex);
- WeakReference<EventCounterGroup> weakRef = EventCounterGroup.s_eventCounterGroups[eventSourceIndex];
- EventCounterGroup ret = null;
- if (weakRef == null || !weakRef.TryGetTarget(out ret))
+ float result = Interlocked.CompareExchange(ref _bufferedValues[i], value, UnusedBufferSlotValue);
+ i++;
+ if (_bufferedValues.Length <= i)
{
- ret = new EventCounterGroup(eventSource);
- EventCounterGroup.s_eventCounterGroups[eventSourceIndex] = new WeakReference<EventCounterGroup>(ret);
+ // 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 (MyLock) // Lock the counter
+ Flush();
+ i = 0;
}
- return ret;
- }
- }
-
- #endregion // Global EventCounterGroup Array management
-
- #region Timer Processing
-
- private DateTime _timeStampSinceCollectionStarted;
- private int _pollingIntervalInMilliseconds;
- private Timer _pollingTimer;
- private void DisposeTimer()
- {
- Debug.Assert(Monitor.IsEntered(this));
- if (_pollingTimer != null)
- {
- _pollingTimer.Dispose();
- _pollingTimer = null;
- }
- }
-
- private void EnableTimer(float pollingIntervalInSeconds)
- {
- Debug.Assert(Monitor.IsEntered(this));
- if (pollingIntervalInSeconds <= 0)
- {
- DisposeTimer();
- _pollingIntervalInMilliseconds = 0;
- }
- else if (_pollingIntervalInMilliseconds == 0 || pollingIntervalInSeconds * 1000 < _pollingIntervalInMilliseconds)
- {
- Debug.WriteLine("Polling interval changed at " + DateTime.UtcNow.ToString("mm.ss.ffffff"));
- _pollingIntervalInMilliseconds = (int)(pollingIntervalInSeconds * 1000);
- DisposeTimer();
- _timeStampSinceCollectionStarted = DateTime.UtcNow;
- // Don't capture the current ExecutionContext and its AsyncLocals onto the timer causing them to live forever
- bool restoreFlow = false;
- try
- {
- if (!ExecutionContext.IsFlowSuppressed())
- {
- ExecutionContext.SuppressFlow();
- restoreFlow = true;
- }
-
- _pollingTimer = new Timer(s => ((EventCounterGroup)s).OnTimer(null), this, _pollingIntervalInMilliseconds, _pollingIntervalInMilliseconds);
- }
- finally
+ if (result == UnusedBufferSlotValue)
{
- // Restore the current ExecutionContext
- if (restoreFlow)
- ExecutionContext.RestoreFlow();
+ // CompareExchange succeeded
+ _bufferedValuesIndex = i;
+ return;
}
}
- // Always fire the timer event (so you see everything up to this time).
- OnTimer(null);
}
- private void OnTimer(object state)
+ protected void Flush()
{
- Debug.WriteLine("Timer fired at " + DateTime.UtcNow.ToString("mm.ss.ffffff"));
- lock (this) // Lock the EventCounterGroup
+ Debug.Assert(Monitor.IsEntered(MyLock));
+ for (int i = 0; i < _bufferedValues.Length; i++)
{
- if (_eventSource.IsEnabled())
- {
- DateTime now = DateTime.UtcNow;
- TimeSpan elapsed = now - _timeStampSinceCollectionStarted;
-
- foreach (var eventCounter in _eventCounters)
- {
- EventCounterPayload payload = eventCounter.GetEventCounterPayload();
- payload.IntervalSec = (float)elapsed.TotalSeconds;
- _eventSource.Write("EventCounters", new EventSourceOptions() { Level = EventLevel.LogAlways }, new PayloadType(payload));
- }
- _timeStampSinceCollectionStarted = now;
- }
- else
+ var value = Interlocked.Exchange(ref _bufferedValues[i], UnusedBufferSlotValue);
+ if (value != UnusedBufferSlotValue)
{
- DisposeTimer();
+ OnMetricWritten(value);
}
}
- }
-
- /// <summary>
- /// This is the payload that is sent in the with EventSource.Write
- /// </summary>
- [EventData]
- class PayloadType
- {
- public PayloadType(EventCounterPayload payload) { Payload = payload; }
- public EventCounterPayload Payload { get; set; }
- }
-
- #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(); }
+ _bufferedValuesIndex = 0;
}
-#endif
- #endregion // PCL timer hack
+ }
- #endregion // Timer Processing
+ /// <summary>
+ /// This is the payload that is sent in the with EventSource.Write
+ /// </summary>
+ [EventData]
+ class EventCounterPayloadType
+ {
+ public EventCounterPayloadType(EventCounterPayload payload) { Payload = payload; }
+ public EventCounterPayload Payload { get; set; }
}
- #endregion // internal supporting classes
}
--- /dev/null
+// 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.Diagnostics;
+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>
+ /// IncrementingEventCounter is a variant of EventCounter for variables that are ever-increasing.
+ /// Ex) # of exceptions in the runtime.
+ /// It does not calculate statistics like mean, standard deviation, etc. because it only accumulates
+ /// the counter value.
+ /// </summary>
+ internal partial class IncrementingEventCounter : BaseCounter
+ {
+ /// <summary>
+ /// Initializes a new instance of the <see cref="IncrementingEventCounter"/> class.
+ /// IncrementingEventCounter live as long as the EventSource that they are attached to unless they are
+ /// explicitly Disposed.
+ /// </summary>
+ /// <param name="name">The name.</param>
+ /// <param name="eventSource">The event source.</param>
+ public IncrementingEventCounter(string name, EventSource eventSource) : base(name, eventSource)
+ {
+ }
+
+ /// <summary>
+ /// Writes 'value' to the stream of values tracked by the counter. This updates the sum and other statistics that will
+ /// be logged on the next timer interval.
+ /// </summary>
+ /// <param name="increment">The value to increment by.</param>
+ public void Increment(float increment = 1)
+ {
+ lock(MyLock)
+ {
+ _increment += increment;
+ }
+ }
+
+ internal TimeSpan DisplayRateTimeScale { get; set; }
+ private float _increment;
+ private float _prevIncrement;
+
+ public override string ToString() => $"IncrementingEventCounter '{_name}' Increment {_increment}";
+
+ internal override void WritePayload(float intervalSec)
+ {
+ lock (MyLock) // Lock the counter
+ {
+ IncrementingCounterPayload payload = new IncrementingCounterPayload();
+ payload.Name = _name;
+ payload.IntervalSec = intervalSec;
+ payload.DisplayName = DisplayName;
+ payload.DisplayRateTimeScale = DisplayRateTimeScale;
+ payload.MetaData = GetMetaDataString();
+ payload.Increment = _increment - _prevIncrement;
+ _prevIncrement = _increment;
+ _eventSource.Write("EventCounters", new EventSourceOptions() { Level = EventLevel.LogAlways }, new IncrementingEventCounterPayloadType(payload));
+ }
+ }
+ }
+
+
+ /// <summary>
+ /// This is the payload that is sent in the with EventSource.Write
+ /// </summary>
+ [EventData]
+ class IncrementingEventCounterPayloadType
+ {
+ public IncrementingEventCounterPayloadType(IncrementingCounterPayload payload) { Payload = payload; }
+ public IncrementingCounterPayload Payload { get; set; }
+ }
+
+}
--- /dev/null
+// 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.Diagnostics;
+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>
+ /// IncrementingPollingCounter is a variant of EventCounter for variables that are ever-increasing.
+ /// Ex) # of exceptions in the runtime.
+ /// It does not calculate statistics like mean, standard deviation, etc. because it only accumulates
+ /// the counter value.
+ /// Unlike IncrementingEventCounter, this takes in a polling callback that it can call to update
+ /// its own metric periodically.
+ /// </summary>
+ internal partial class IncrementingPollingCounter : BaseCounter
+ {
+ /// <summary>
+ /// Initializes a new instance of the <see cref="IncrementingPollingCounter"/> class.
+ /// IncrementingPollingCounter live as long as the EventSource that they are attached to unless they are
+ /// explicitly Disposed.
+ /// </summary>
+ /// <param name="name">The name.</param>
+ /// <param name="eventSource">The event source.</param>
+ public IncrementingPollingCounter(string name, EventSource eventSource, Func<float> getCountFunction) : base(name, eventSource)
+ {
+ _getCountFunction = getCountFunction;
+ }
+
+ public override string ToString() => $"IncrementingPollingCounter '{_name}' Increment {_increment}";
+
+ internal TimeSpan DisplayRateTimeScale { get; set; }
+ private float _increment;
+ private float _prevIncrement;
+ private Func<float> _getCountFunction;
+
+ /// <summary>
+ /// Calls "_getCountFunction" to enqueue the counter value to the queue.
+ /// </summary>
+ private void UpdateMetric()
+ {
+ try
+ {
+ lock(MyLock)
+ {
+ _increment += _getCountFunction();
+ }
+ }
+ catch (Exception ex)
+ {
+ ReportOutOfBandMessage($"ERROR: Exception during EventCounter {_name} getMetricFunction callback: " + ex.Message);
+ }
+ }
+
+ internal override void WritePayload(float intervalSec)
+ {
+ UpdateMetric();
+ lock (MyLock) // Lock the counter
+ {
+ IncrementingCounterPayload payload = new IncrementingCounterPayload();
+ payload.Name = _name;
+ payload.DisplayName = DisplayName;
+ payload.DisplayRateTimeScale = DisplayRateTimeScale;
+ payload.IntervalSec = intervalSec;
+ payload.MetaData = GetMetaDataString();
+ payload.Increment = _increment - _prevIncrement;
+ _prevIncrement = _increment;
+ _eventSource.Write("EventCounters", new EventSourceOptions() { Level = EventLevel.LogAlways }, new IncrementingPollingCounterPayloadType(payload));
+ }
+ }
+ }
+
+
+ /// <summary>
+ /// This is the payload that is sent in the with EventSource.Write
+ /// </summary>
+ [EventData]
+ class IncrementingPollingCounterPayloadType
+ {
+ public IncrementingPollingCounterPayloadType(IncrementingCounterPayload payload) { Payload = payload; }
+ public IncrementingCounterPayload Payload { get; set; }
+ }
+
+}
--- /dev/null
+// 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.Diagnostics;
+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>
+ /// PollingCounter is a variant of EventCounter - it collects and calculates similar statistics
+ /// as EventCounter. PollingCounter differs from EventCounter in that it takes in a callback
+ /// function to collect metrics on its own rather than the user having to call WriteMetric()
+ /// every time.
+ /// </summary>
+ internal partial class PollingCounter : BaseCounter
+ {
+ /// <summary>
+ /// Initializes a new instance of the <see cref="PollingCounter"/> class.
+ /// PollingCounter live as long as the EventSource that they are attached to unless they are
+ /// explicitly Disposed.
+ /// </summary>
+ /// <param name="name">The name.</param>
+ /// <param name="eventSource">The event source.</param>
+ public PollingCounter(string name, EventSource eventSource, Func<float> getMetricFunction) : base(name, eventSource)
+ {
+ _getMetricFunction = getMetricFunction;
+ }
+
+ public override string ToString() => $"PollingCounter '{_name}' Count {1} Mean {_lastVal.ToString("n3")}";
+
+ private Func<float> _getMetricFunction;
+ private float _lastVal;
+
+ internal override void WritePayload(float intervalSec)
+ {
+ lock (MyLock)
+ {
+ float value = 0;
+ try
+ {
+ value = _getMetricFunction();
+ }
+ catch (Exception ex)
+ {
+ ReportOutOfBandMessage($"ERROR: Exception during EventCounter {_name} getMetricFunction callback: " + ex.Message);
+ }
+
+ CounterPayload payload = new CounterPayload();
+ payload.Name = _name;
+ payload.Count = 1; // NOTE: These dumb-looking statistics is intentional
+ payload.IntervalSec = intervalSec;
+ payload.Mean = value;
+ payload.Max = value;
+ payload.Min = value;
+ payload.StandardDeviation = 0;
+ _lastVal = value;
+ _eventSource.Write("EventCounters", new EventSourceOptions() { Level = EventLevel.LogAlways }, new PollingPayloadType(payload));
+ }
+ }
+ }
+
+ /// <summary>
+ /// This is the payload that is sent in the with EventSource.Write
+ /// </summary>
+ [EventData]
+ class PollingPayloadType
+ {
+ public PollingPayloadType(CounterPayload payload) { Payload = payload; }
+ public CounterPayload Payload { get; set; }
+ }
+}