Enable UpDownCounter For Dotnet-Counters and Dotnet-Monitor (#3849)
authorkkeirstead <85592574+kkeirstead@users.noreply.github.com>
Tue, 2 May 2023 16:55:11 +0000 (09:55 -0700)
committerGitHub <noreply@github.com>
Tue, 2 May 2023 16:55:11 +0000 (09:55 -0700)
* Use value from UDC event payload.
* Adding support for UpDownCounter reporting a value instead of a rate
* Check for payload version, don't attempt to parse if version 0
* Don't get rate for updowncounter payload

src/Microsoft.Diagnostics.Monitoring.EventPipe/Counters/CounterPayload.cs
src/Microsoft.Diagnostics.Monitoring.EventPipe/Counters/TraceEventExtensions.cs
src/Tools/dotnet-counters/CounterMonitor.cs

index 18eb5264e3b6ed41be583576e35d71c21e5f8280..99e30300810f085e566b11736ef530b8c457d830 100644 (file)
@@ -86,6 +86,17 @@ namespace Microsoft.Diagnostics.Monitoring.EventPipe
         }
     }
 
+    internal class UpDownCounterPayload : CounterPayload
+    {
+        public UpDownCounterPayload(string providerName, string name, string displayName, string displayUnits, string metadata, double value, DateTime timestamp) :
+            base(providerName, name, metadata, value, timestamp, "Metric", EventType.UpDownCounter)
+        {
+            // In case these properties are not provided, set them to appropriate values.
+            string counterName = string.IsNullOrEmpty(displayName) ? name : displayName;
+            DisplayName = !string.IsNullOrEmpty(displayUnits) ? $"{counterName} ({displayUnits})" : counterName;
+        }
+    }
+
     internal class CounterEndedPayload : CounterPayload
     {
         public CounterEndedPayload(string providerName, string name, DateTime timestamp)
@@ -144,6 +155,7 @@ namespace Microsoft.Diagnostics.Monitoring.EventPipe
         Rate,
         Gauge,
         Histogram,
+        UpDownCounter,
         Error,
         CounterEnded
     }
index eedc106d18bad816ecfb3502ac788f90fec3cdca..347a4e11f2c281f8484350a88d42fa1e535259e8 100644 (file)
@@ -90,6 +90,10 @@ namespace Microsoft.Diagnostics.Monitoring.EventPipe
                 {
                     HandleCounterRate(traceEvent, filter, sessionId, out payload);
                 }
+                else if (traceEvent.EventName == "UpDownCounterRateValuePublished")
+                {
+                    HandleUpDownCounterValue(traceEvent, filter, sessionId, out payload);
+                }
                 else if (traceEvent.EventName == "TimeSeriesLimitReached")
                 {
                     HandleTimeSeriesLimitReached(traceEvent, sessionId, out payload);
@@ -183,7 +187,47 @@ namespace Microsoft.Diagnostics.Monitoring.EventPipe
             else
             {
                 // for observable instruments we assume the lack of data is meaningful and remove it from the UI
-                // this happens when the ObservableCounter callback function throws an exception.
+                // this happens when the ObservableCounter callback function throws an exception
+                // or when the ObservableCounter doesn't include a measurement for a particular set of tag values.
+                payload = new CounterEndedPayload(meterName, instrumentName, traceEvent.TimeStamp);
+            }
+        }
+
+        private static void HandleUpDownCounterValue(TraceEvent traceEvent, CounterFilter filter, string sessionId, out ICounterPayload payload)
+        {
+            payload = null;
+
+            string payloadSessionId = (string)traceEvent.PayloadValue(0);
+
+            if (payloadSessionId != sessionId || traceEvent.Version < 1) // Version 1 added the value field.
+            {
+                return;
+            }
+
+            string meterName = (string)traceEvent.PayloadValue(1);
+            //string meterVersion = (string)obj.PayloadValue(2);
+            string instrumentName = (string)traceEvent.PayloadValue(3);
+            string unit = (string)traceEvent.PayloadValue(4);
+            string tags = (string)traceEvent.PayloadValue(5);
+            //string rateText = (string)traceEvent.PayloadValue(6); // Not currently using rate for UpDownCounters.
+            string valueText = (string)traceEvent.PayloadValue(7);
+
+            if (!filter.IsIncluded(meterName, instrumentName))
+            {
+                return;
+            }
+
+            if (double.TryParse(valueText, NumberStyles.Number | NumberStyles.Float, CultureInfo.InvariantCulture, out double value))
+            {
+                // UpDownCounter reports the value, not the rate - this is different than how Counter behaves.
+                payload = new UpDownCounterPayload(meterName, instrumentName, null, unit, tags, value, traceEvent.TimeStamp);
+
+            }
+            else
+            {
+                // for observable instruments we assume the lack of data is meaningful and remove it from the UI
+                // this happens when the ObservableUpDownCounter callback function throws an exception
+                // or when the ObservableUpDownCounter doesn't include a measurement for a particular set of tag values.
                 payload = new CounterEndedPayload(meterName, instrumentName, traceEvent.TimeStamp);
             }
         }
index 928664d33e8cb0cbdf48e4e00a00a72608d059a8..a01707438607b7e098d869623d64f1b7e7198bb2 100644 (file)
@@ -88,6 +88,10 @@ namespace Microsoft.Diagnostics.Tools.Counters
                     {
                         HandleCounterRate(obj);
                     }
+                    else if (obj.EventName == "UpDownCounterRateValuePublished")
+                    {
+                        HandleUpDownCounterValue(obj);
+                    }
                     else if (obj.EventName == "TimeSeriesLimitReached")
                     {
                         HandleTimeSeriesLimitReached(obj);
@@ -198,6 +202,42 @@ namespace Microsoft.Diagnostics.Tools.Counters
             }
         }
 
+        private void HandleUpDownCounterValue(TraceEvent obj)
+        {
+            if (obj.Version < 1) // Version 1 added the value field.
+            {
+                return;
+            }
+
+            string sessionId = (string)obj.PayloadValue(0);
+            string meterName = (string)obj.PayloadValue(1);
+            //string meterVersion = (string)obj.PayloadValue(2);
+            string instrumentName = (string)obj.PayloadValue(3);
+            string unit = (string)obj.PayloadValue(4);
+            string tags = (string)obj.PayloadValue(5);
+            //string rateText = (string)obj.PayloadValue(6); // Not currently using rate for UpDownCounters.
+            string valueText = (string)obj.PayloadValue(7);
+            if (sessionId != _metricsEventSourceSessionId)
+            {
+                return;
+            }
+            MeterInstrumentEventObserved(meterName, obj.TimeStamp);
+
+            // the value might be an empty string indicating no measurement was provided this collection interval
+            if (double.TryParse(valueText, NumberStyles.Number | NumberStyles.Float, CultureInfo.InvariantCulture, out double value))
+            {
+                // UpDownCounter reports the value, not the rate - this is different than how Counter behaves, and is thus treated as a gauge.
+                CounterPayload payload = new GaugePayload(meterName, instrumentName, null, unit, tags, value, obj.TimeStamp);
+                _renderer.CounterPayloadReceived(payload, _pauseCmdSet);
+            }
+            else
+            {
+                // for observable instruments we assume the lack of data is meaningful and remove it from the UI
+                CounterPayload payload = new RatePayload(meterName, instrumentName, null, unit, tags, 0, _interval, obj.TimeStamp);
+                _renderer.CounterStopped(payload);
+            }
+        }
+
         private void HandleHistogram(TraceEvent obj)
         {
             string sessionId = (string)obj.PayloadValue(0);