Dotnet Counters + Dotnet Monitor Unification (#4254)
authorkkeirstead <85592574+kkeirstead@users.noreply.github.com>
Wed, 11 Oct 2023 22:11:49 +0000 (15:11 -0700)
committerGitHub <noreply@github.com>
Wed, 11 Oct 2023 22:11:49 +0000 (15:11 -0700)
27 files changed:
src/Microsoft.Diagnostics.Monitoring.EventPipe/Counters/CounterPayload.cs
src/Microsoft.Diagnostics.Monitoring.EventPipe/Counters/CounterPayloadExtensions.cs [deleted file]
src/Microsoft.Diagnostics.Monitoring.EventPipe/Counters/ICounterPayload.cs
src/Microsoft.Diagnostics.Monitoring.EventPipe/Counters/MetricsPipeline.cs
src/Microsoft.Diagnostics.Monitoring.EventPipe/Counters/TraceEventExtensions.cs
src/Microsoft.Diagnostics.Monitoring.EventPipe/DiagnosticsEventPipeProcessor.cs
src/Microsoft.Diagnostics.Monitoring.EventPipe/EventPipeStreamProvider.cs
src/Microsoft.Diagnostics.Monitoring.EventPipe/EventSourcePipeline.cs
src/Microsoft.Diagnostics.Monitoring.EventPipe/EventSourcePipelineSettings.cs
src/Microsoft.Diagnostics.Monitoring.EventPipe/Microsoft.Diagnostics.Monitoring.EventPipe.csproj
src/Microsoft.Diagnostics.Monitoring.EventPipe/Trace/EventTracePipeline.cs
src/Microsoft.Diagnostics.Monitoring.EventPipe/Triggers/EventCounter/EventCounterTrigger.cs
src/Microsoft.Diagnostics.Monitoring.EventPipe/Triggers/SystemDiagnosticsMetricsTrigger/SystemDiagnosticsMetricsTrigger.cs
src/Microsoft.Diagnostics.Monitoring.EventPipe/Triggers/SystemDiagnosticsMetricsTrigger/SystemDiagnosticsMetricsTriggerImpl.cs
src/Microsoft.Diagnostics.Monitoring/Microsoft.Diagnostics.Monitoring.csproj
src/Tools/dotnet-counters/CounterMonitor.cs
src/Tools/dotnet-counters/CounterPayload.cs [deleted file]
src/Tools/dotnet-counters/CounterPayloadExtensions.cs [new file with mode: 0644]
src/Tools/dotnet-counters/Exporters/CSVExporter.cs
src/Tools/dotnet-counters/Exporters/ConsoleWriter.cs
src/Tools/dotnet-counters/Exporters/ICounterRenderer.cs
src/Tools/dotnet-counters/Exporters/JSONExporter.cs
src/Tools/dotnet-counters/Program.cs
src/tests/Microsoft.Diagnostics.Monitoring.EventPipe/EventCounterPipelineUnitTests.cs
src/tests/Microsoft.Diagnostics.Monitoring.EventPipe/EventCounterTriggerTests.cs
src/tests/dotnet-counters/CSVExporterTests.cs
src/tests/dotnet-counters/JSONExporterTests.cs

index 99e30300810f085e566b11736ef530b8c457d830..850949b0870a826e2fc796e76dc7f2c108d71422 100644 (file)
@@ -7,12 +7,9 @@ using System.Linq;
 
 namespace Microsoft.Diagnostics.Monitoring.EventPipe
 {
-    /// <summary>
-    /// TODO This is currently a duplication of the src\Tools\dotnet-counters\CounterPayload.cs stack. The two will be unified in a separate change.
-    /// </summary>
-    internal class CounterPayload : ICounterPayload
+    internal abstract class CounterPayload : ICounterPayload
     {
-        public CounterPayload(DateTime timestamp,
+        protected CounterPayload(DateTime timestamp,
             string provider,
             string name,
             string displayName,
@@ -20,7 +17,9 @@ namespace Microsoft.Diagnostics.Monitoring.EventPipe
             double value,
             CounterType counterType,
             float interval,
-            string metadata)
+            int series,
+            string metadata,
+            EventType eventType)
         {
             Timestamp = timestamp;
             Name = name;
@@ -30,30 +29,11 @@ namespace Microsoft.Diagnostics.Monitoring.EventPipe
             CounterType = counterType;
             Provider = provider;
             Interval = interval;
+            Series = series;
             Metadata = metadata;
-            EventType = EventType.Gauge;
-        }
-
-        // Copied from dotnet-counters
-        public CounterPayload(string providerName,
-                              string name,
-                              string metadata,
-                              double value,
-                              DateTime timestamp,
-                              string type,
-                              EventType eventType)
-        {
-            Provider = providerName;
-            Name = name;
-            Metadata = metadata;
-            Value = value;
-            Timestamp = timestamp;
-            CounterType = (CounterType)Enum.Parse(typeof(CounterType), type);
             EventType = eventType;
         }
 
-        public string Namespace { get; }
-
         public string Name { get; }
 
         public string DisplayName { get; protected set; }
@@ -73,12 +53,50 @@ namespace Microsoft.Diagnostics.Monitoring.EventPipe
         public string Metadata { get; }
 
         public EventType EventType { get; set; }
+
+        public virtual bool IsMeter => false;
+
+        public int Series { get; }
+    }
+
+    internal sealed class EventCounterPayload : CounterPayload
+    {
+        public EventCounterPayload(DateTime timestamp,
+            string provider,
+            string name,
+            string displayName,
+            string unit,
+            double value,
+            CounterType counterType,
+            float interval,
+            int series,
+            string metadata) : base(timestamp, provider, name, displayName, unit, value, counterType, interval, series, metadata, EventType.Gauge)
+        {
+        }
+    }
+
+    internal abstract class MeterPayload : CounterPayload
+    {
+        protected MeterPayload(DateTime timestamp,
+                    string provider,
+                    string name,
+                    string displayName,
+                    string unit,
+                    double value,
+                    CounterType counterType,
+                    string metadata,
+                    EventType eventType)
+            : base(timestamp, provider, name, displayName, unit, value, counterType, 0.0f, 0, metadata, eventType)
+        {
+        }
+
+        public override bool IsMeter => true;
     }
 
-    internal class GaugePayload : CounterPayload
+    internal sealed class GaugePayload : MeterPayload
     {
         public GaugePayload(string providerName, string name, string displayName, string displayUnits, string metadata, double value, DateTime timestamp) :
-            base(providerName, name, metadata, value, timestamp, "Metric", EventType.Gauge)
+            base(timestamp, providerName, name, displayName, displayUnits, value, CounterType.Metric, metadata, EventType.Gauge)
         {
             // In case these properties are not provided, set them to appropriate values.
             string counterName = string.IsNullOrEmpty(displayName) ? name : displayName;
@@ -86,10 +104,10 @@ namespace Microsoft.Diagnostics.Monitoring.EventPipe
         }
     }
 
-    internal class UpDownCounterPayload : CounterPayload
+    internal class UpDownCounterPayload : MeterPayload
     {
         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)
+            base(timestamp, providerName, name, displayName, displayUnits, value, CounterType.Metric, metadata, EventType.UpDownCounter)
         {
             // In case these properties are not provided, set them to appropriate values.
             string counterName = string.IsNullOrEmpty(displayName) ? name : displayName;
@@ -97,19 +115,26 @@ namespace Microsoft.Diagnostics.Monitoring.EventPipe
         }
     }
 
-    internal class CounterEndedPayload : CounterPayload
+    internal sealed class BeginInstrumentReportingPayload : MeterPayload
     {
-        public CounterEndedPayload(string providerName, string name, DateTime timestamp)
-            : base(providerName, name, null, 0.0, timestamp, "Metric", EventType.CounterEnded)
+        public BeginInstrumentReportingPayload(string providerName, string name, DateTime timestamp)
+            : base(timestamp, providerName, name, string.Empty, string.Empty, 0.0, CounterType.Metric, null, EventType.BeginInstrumentReporting)
         {
+        }
+    }
 
+    internal sealed class CounterEndedPayload : MeterPayload
+    {
+        public CounterEndedPayload(string providerName, string name, DateTime timestamp)
+            : base(timestamp, providerName, name, string.Empty, string.Empty, 0.0, CounterType.Metric, null, EventType.CounterEnded)
+        {
         }
     }
 
-    internal class RatePayload : CounterPayload
+    internal sealed class RatePayload : MeterPayload
     {
         public RatePayload(string providerName, string name, string displayName, string displayUnits, string metadata, double value, double intervalSecs, DateTime timestamp) :
-            base(providerName, name, metadata, value, timestamp, "Rate", EventType.Rate)
+            base(timestamp, providerName, name, displayName, displayUnits, value, CounterType.Rate, metadata, EventType.Rate)
         {
             // In case these properties are not provided, set them to appropriate values.
             string counterName = string.IsNullOrEmpty(displayName) ? name : displayName;
@@ -119,35 +144,46 @@ namespace Microsoft.Diagnostics.Monitoring.EventPipe
         }
     }
 
-    internal class PercentilePayload : CounterPayload
+    internal record struct Quantile(double Percentage, double Value);
+
+    internal sealed class PercentilePayload : MeterPayload
     {
-        public PercentilePayload(string providerName, string name, string displayName, string displayUnits, string metadata, IEnumerable<Quantile> quantiles, DateTime timestamp) :
-            base(providerName, name, metadata, 0.0, timestamp, "Metric", EventType.Histogram)
+        public PercentilePayload(string providerName, string name, string displayName, string displayUnits, string metadata, double value, DateTime timestamp) :
+            base(timestamp, providerName, name, displayName, displayUnits, value, CounterType.Metric, metadata, EventType.Histogram)
         {
             // 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;
-            Quantiles = quantiles.ToArray();
         }
-
-        public Quantile[] Quantiles { get; }
     }
 
-    internal record struct Quantile(double Percentage, double Value);
-
-    internal class ErrorPayload : CounterPayload
+    // Dotnet-monitor and dotnet-counters previously had incompatible PercentilePayload implementations before being unified -
+    // Dotnet-monitor created a single payload that contained all of the quantiles to keep them together, whereas
+    // dotnet-counters created a separate payload for each quantile (multiple payloads per TraceEvent).
+    // AggregatePercentilePayload allows dotnet-monitor to construct a PercentilePayload for individual quantiles
+    // like dotnet-counters, while still keeping the quantiles together as a unit.
+    internal sealed class AggregatePercentilePayload : MeterPayload
     {
-        public ErrorPayload(string errorMessage) : this(errorMessage, DateTime.UtcNow)
+        public AggregatePercentilePayload(string providerName, string name, string displayName, string displayUnits, string metadata, IEnumerable<Quantile> quantiles, DateTime timestamp) :
+            base(timestamp, providerName, name, displayName, displayUnits, 0.0, CounterType.Metric, metadata, EventType.Histogram)
         {
+            //string counterName = string.IsNullOrEmpty(displayName) ? name : displayName;
+            //DisplayName = !string.IsNullOrEmpty(displayUnits) ? $"{counterName} ({displayUnits})" : counterName;
+            Quantiles = quantiles.ToArray();
         }
 
-        public ErrorPayload(string errorMessage, DateTime timestamp) :
-            base(string.Empty, string.Empty, null, 0.0, timestamp, "Metric", EventType.Error)
+        public Quantile[] Quantiles { get; }
+    }
+
+    internal sealed class ErrorPayload : MeterPayload
+    {
+        public ErrorPayload(string errorMessage, DateTime timestamp, EventType eventType)
+            : base(timestamp, string.Empty, string.Empty, string.Empty, string.Empty, 0.0, CounterType.Metric, null, eventType)
         {
             ErrorMessage = errorMessage;
         }
 
-        public string ErrorMessage { get; private set; }
+        public string ErrorMessage { get; }
     }
 
     internal enum EventType : int
@@ -156,7 +192,52 @@ namespace Microsoft.Diagnostics.Monitoring.EventPipe
         Gauge,
         Histogram,
         UpDownCounter,
-        Error,
-        CounterEnded
+        BeginInstrumentReporting,
+        CounterEnded,
+        HistogramLimitError,
+        TimeSeriesLimitError,
+        ErrorTargetProcess,
+        MultipleSessionsNotSupportedError,
+        MultipleSessionsConfiguredIncorrectlyError,
+        ObservableInstrumentCallbackError
+    }
+
+    internal static class EventTypeExtensions
+    {
+        public static bool IsValuePublishedEvent(this EventType eventType)
+        {
+            return eventType is EventType.Gauge
+                || eventType is EventType.Rate
+                || eventType is EventType.Histogram
+                || eventType is EventType.UpDownCounter;
+        }
+
+        public static bool IsError(this EventType eventType)
+        {
+            return eventType is EventType.HistogramLimitError
+                || eventType is EventType.TimeSeriesLimitError
+                || eventType is EventType.ErrorTargetProcess
+                || eventType is EventType.MultipleSessionsNotSupportedError
+                || eventType is EventType.MultipleSessionsConfiguredIncorrectlyError
+                || eventType is EventType.ObservableInstrumentCallbackError;
+        }
+
+        public static bool IsNonFatalError(this EventType eventType)
+        {
+            return IsError(eventType)
+                && !IsTracingError(eventType)
+                && !IsSessionStartupError(eventType);
+        }
+
+        public static bool IsTracingError(this EventType eventType)
+        {
+            return eventType is EventType.ErrorTargetProcess;
+        }
+
+        public static bool IsSessionStartupError(this EventType eventType)
+        {
+            return eventType is EventType.MultipleSessionsNotSupportedError
+                || eventType is EventType.MultipleSessionsConfiguredIncorrectlyError;
+        }
     }
 }
diff --git a/src/Microsoft.Diagnostics.Monitoring.EventPipe/Counters/CounterPayloadExtensions.cs b/src/Microsoft.Diagnostics.Monitoring.EventPipe/Counters/CounterPayloadExtensions.cs
deleted file mode 100644 (file)
index 12d4b86..0000000
+++ /dev/null
@@ -1,21 +0,0 @@
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-
-namespace Microsoft.Diagnostics.Monitoring.EventPipe
-{
-    internal static class CounterPayloadExtensions
-    {
-        public static string GetDisplay(this ICounterPayload counterPayload)
-        {
-            if (counterPayload.CounterType == CounterType.Rate)
-            {
-                return $"{counterPayload.DisplayName} ({counterPayload.Unit} / {counterPayload.Interval} sec)";
-            }
-            if (!string.IsNullOrEmpty(counterPayload.Unit))
-            {
-                return $"{counterPayload.DisplayName} ({counterPayload.Unit})";
-            }
-            return $"{counterPayload.DisplayName}";
-        }
-    }
-}
index 4e3ce06f747820358bc07f2d36418d6a3d9e0146..4cc5fdb0438f052bb3a499c74917e8cefa4d8305 100644 (file)
@@ -41,6 +41,10 @@ namespace Microsoft.Diagnostics.Monitoring.EventPipe
         /// </summary>
         string Metadata { get; }
 
-        EventType EventType { get; set; }
+        EventType EventType { get; }
+
+        bool IsMeter { get; }
+
+        int Series { get; }
     }
 }
index 1c3a29ab8a5e39d789eb9e768c7a9d597f91384c..9f8f34c1505acc5a7ee45cbc90b1060af1f9d087 100644 (file)
@@ -17,6 +17,7 @@ namespace Microsoft.Diagnostics.Monitoring.EventPipe
         private readonly CounterFilter _filter;
         private string _clientId;
         private string _sessionId;
+        private CounterConfiguration _counterConfiguration;
 
         public MetricsPipeline(DiagnosticsClient client,
             MetricsPipelineSettings settings,
@@ -51,6 +52,14 @@ namespace Microsoft.Diagnostics.Monitoring.EventPipe
             _clientId = config.ClientId;
             _sessionId = config.SessionId;
 
+            _counterConfiguration = new CounterConfiguration(_filter)
+            {
+                SessionId = _sessionId,
+                ClientId = _clientId,
+                MaxHistograms = Settings.MaxHistograms,
+                MaxTimeseries = Settings.MaxTimeSeries
+            };
+
             return config;
         }
 
@@ -61,7 +70,7 @@ namespace Microsoft.Diagnostics.Monitoring.EventPipe
             eventSource.Dynamic.All += traceEvent => {
                 try
                 {
-                    if (traceEvent.TryGetCounterPayload(_filter, _sessionId, _clientId, out ICounterPayload counterPayload))
+                    if (traceEvent.TryGetCounterPayload(_counterConfiguration, out ICounterPayload counterPayload))
                     {
                         ExecuteCounterLoggerAction((metricLogger) => metricLogger.Log(counterPayload));
                     }
index 942a7ebddf89f9e84e87d71b52cec6d085d4568a..06472cf3f3db6cd020a78020ebc9d66d0af44fac 100644 (file)
@@ -9,11 +9,29 @@ using Microsoft.Diagnostics.Tracing;
 
 namespace Microsoft.Diagnostics.Monitoring.EventPipe
 {
+    internal class CounterConfiguration
+    {
+        public CounterConfiguration(CounterFilter filter)
+        {
+            CounterFilter = filter ?? throw new ArgumentNullException(nameof(filter));
+        }
+
+        public CounterFilter CounterFilter { get; }
+
+        public string SessionId { get; set; }
+
+        public string ClientId { get; set; }
+
+        public int MaxHistograms { get; set; }
+
+        public int MaxTimeseries { get; set; }
+    }
+
     internal static class TraceEventExtensions
     {
         private static HashSet<string> inactiveSharedSessions = new(StringComparer.OrdinalIgnoreCase);
 
-        public static bool TryGetCounterPayload(this TraceEvent traceEvent, CounterFilter filter, string sessionId, string clientId, out ICounterPayload payload)
+        public static bool TryGetCounterPayload(this TraceEvent traceEvent, CounterConfiguration counterConfiguration, out ICounterPayload payload)
         {
             payload = null;
 
@@ -27,12 +45,12 @@ namespace Microsoft.Diagnostics.Monitoring.EventPipe
                 string counterName = payloadFields["Name"].ToString();
 
                 string metadata = payloadFields["Metadata"].ToString();
-
+                int seriesValue = GetInterval(series);
                 //CONSIDER
                 //Concurrent counter sessions do not each get a separate interval. Instead the payload
                 //for _all_ the counters changes the Series to be the lowest specified interval, on a per provider basis.
                 //Currently the CounterFilter will remove any data whose Series doesn't match the requested interval.
-                if (!filter.IsIncluded(traceEvent.ProviderName, counterName, GetInterval(series)))
+                if (!counterConfiguration.CounterFilter.IsIncluded(traceEvent.ProviderName, counterName, seriesValue))
                 {
                     return false;
                 }
@@ -61,7 +79,7 @@ namespace Microsoft.Diagnostics.Monitoring.EventPipe
                 // Note that dimensional data such as pod and namespace are automatically added in prometheus and azure monitor scenarios.
                 // We no longer added it here.
 
-                payload = new CounterPayload(
+                payload = new EventCounterPayload(
                     traceEvent.TimeStamp,
                     traceEvent.ProviderName,
                     counterName, displayName,
@@ -69,57 +87,57 @@ namespace Microsoft.Diagnostics.Monitoring.EventPipe
                     value,
                     counterType,
                     intervalSec,
+                    seriesValue / 1000,
                     metadata);
 
                 return true;
             }
 
-            if (clientId != null && !inactiveSharedSessions.Contains(clientId) && MonitoringSourceConfiguration.SystemDiagnosticsMetricsProviderName.Equals(traceEvent.ProviderName))
+            if (counterConfiguration.ClientId != null && !inactiveSharedSessions.Contains(counterConfiguration.ClientId) && MonitoringSourceConfiguration.SystemDiagnosticsMetricsProviderName.Equals(traceEvent.ProviderName))
             {
                 if (traceEvent.EventName == "BeginInstrumentReporting")
                 {
-                    // Do we want to log something for this?
-                    //HandleBeginInstrumentReporting(traceEvent);
+                    HandleBeginInstrumentReporting(traceEvent, counterConfiguration, out payload);
                 }
                 if (traceEvent.EventName == "HistogramValuePublished")
                 {
-                    HandleHistogram(traceEvent, filter, sessionId, out payload);
+                    HandleHistogram(traceEvent, counterConfiguration, out payload);
                 }
                 else if (traceEvent.EventName == "GaugeValuePublished")
                 {
-                    HandleGauge(traceEvent, filter, sessionId, out payload);
+                    HandleGauge(traceEvent, counterConfiguration, out payload);
                 }
                 else if (traceEvent.EventName == "CounterRateValuePublished")
                 {
-                    HandleCounterRate(traceEvent, filter, sessionId, out payload);
+                    HandleCounterRate(traceEvent, counterConfiguration, out payload);
                 }
                 else if (traceEvent.EventName == "UpDownCounterRateValuePublished")
                 {
-                    HandleUpDownCounterValue(traceEvent, filter, sessionId, out payload);
+                    HandleUpDownCounterValue(traceEvent, counterConfiguration, out payload);
                 }
                 else if (traceEvent.EventName == "TimeSeriesLimitReached")
                 {
-                    HandleTimeSeriesLimitReached(traceEvent, sessionId, out payload);
+                    HandleTimeSeriesLimitReached(traceEvent, counterConfiguration, out payload);
                 }
                 else if (traceEvent.EventName == "HistogramLimitReached")
                 {
-                    HandleHistogramLimitReached(traceEvent, sessionId, out payload);
+                    HandleHistogramLimitReached(traceEvent, counterConfiguration, out payload);
                 }
                 else if (traceEvent.EventName == "Error")
                 {
-                    HandleError(traceEvent, sessionId, out payload);
+                    HandleError(traceEvent, counterConfiguration, out payload);
                 }
                 else if (traceEvent.EventName == "ObservableInstrumentCallbackError")
                 {
-                    HandleObservableInstrumentCallbackError(traceEvent, sessionId, out payload);
+                    HandleObservableInstrumentCallbackError(traceEvent, counterConfiguration, out payload);
                 }
                 else if (traceEvent.EventName == "MultipleSessionsNotSupportedError")
                 {
-                    HandleMultipleSessionsNotSupportedError(traceEvent, sessionId, out payload);
+                    HandleMultipleSessionsNotSupportedError(traceEvent, counterConfiguration, out payload);
                 }
                 else if (traceEvent.EventName == "MultipleSessionsConfiguredIncorrectlyError")
                 {
-                    HandleMultipleSessionsConfiguredIncorrectlyError(traceEvent, clientId, out payload);
+                    HandleMultipleSessionsConfiguredIncorrectlyError(traceEvent, counterConfiguration.ClientId, out payload);
                 }
 
                 return payload != null;
@@ -128,13 +146,13 @@ namespace Microsoft.Diagnostics.Monitoring.EventPipe
             return false;
         }
 
-        private static void HandleGauge(TraceEvent obj, CounterFilter filter, string sessionId, out ICounterPayload payload)
+        private static void HandleGauge(TraceEvent obj, CounterConfiguration counterConfiguration, out ICounterPayload payload)
         {
             payload = null;
 
             string payloadSessionId = (string)obj.PayloadValue(0);
 
-            if (payloadSessionId != sessionId)
+            if (payloadSessionId != counterConfiguration.SessionId)
             {
                 return;
             }
@@ -146,7 +164,7 @@ namespace Microsoft.Diagnostics.Monitoring.EventPipe
             string tags = (string)obj.PayloadValue(5);
             string lastValueText = (string)obj.PayloadValue(6);
 
-            if (!filter.IsIncluded(meterName, instrumentName))
+            if (!counterConfiguration.CounterFilter.IsIncluded(meterName, instrumentName))
             {
                 return;
             }
@@ -164,13 +182,36 @@ namespace Microsoft.Diagnostics.Monitoring.EventPipe
             }
         }
 
-        private static void HandleCounterRate(TraceEvent traceEvent, CounterFilter filter, string sessionId, out ICounterPayload payload)
+        private static void HandleBeginInstrumentReporting(TraceEvent traceEvent, CounterConfiguration counterConfiguration, out ICounterPayload payload)
+        {
+            payload = null;
+
+            string payloadSessionId = (string)traceEvent.PayloadValue(0);
+            if (payloadSessionId != counterConfiguration.SessionId)
+            {
+                return;
+            }
+
+            string meterName = (string)traceEvent.PayloadValue(1);
+            //string meterVersion = (string)obj.PayloadValue(2);
+            string instrumentName = (string)traceEvent.PayloadValue(3);
+
+            if (!counterConfiguration.CounterFilter.IsIncluded(meterName, instrumentName))
+            {
+                return;
+            }
+
+
+            payload = new BeginInstrumentReportingPayload(meterName, instrumentName, traceEvent.TimeStamp);
+        }
+
+        private static void HandleCounterRate(TraceEvent traceEvent, CounterConfiguration counterConfiguration, out ICounterPayload payload)
         {
             payload = null;
 
             string payloadSessionId = (string)traceEvent.PayloadValue(0);
 
-            if (payloadSessionId != sessionId)
+            if (payloadSessionId != counterConfiguration.SessionId)
             {
                 return;
             }
@@ -182,14 +223,14 @@ namespace Microsoft.Diagnostics.Monitoring.EventPipe
             string tags = (string)traceEvent.PayloadValue(5);
             string rateText = (string)traceEvent.PayloadValue(6);
 
-            if (!filter.IsIncluded(meterName, instrumentName))
+            if (!counterConfiguration.CounterFilter.IsIncluded(meterName, instrumentName))
             {
                 return;
             }
 
-            if (double.TryParse(rateText, NumberStyles.Number | NumberStyles.Float, CultureInfo.InvariantCulture, out double rate))
+            if (double.TryParse(rateText, NumberStyles.Number | NumberStyles.Float, CultureInfo.InvariantCulture, out double value))
             {
-                payload = new RatePayload(meterName, instrumentName, null, unit, tags, rate, filter.DefaultIntervalSeconds, traceEvent.TimeStamp);
+                payload = new RatePayload(meterName, instrumentName, null, unit, tags, value, counterConfiguration.CounterFilter.DefaultIntervalSeconds, traceEvent.TimeStamp);
             }
             else
             {
@@ -200,13 +241,13 @@ namespace Microsoft.Diagnostics.Monitoring.EventPipe
             }
         }
 
-        private static void HandleUpDownCounterValue(TraceEvent traceEvent, CounterFilter filter, string sessionId, out ICounterPayload payload)
+        private static void HandleUpDownCounterValue(TraceEvent traceEvent, CounterConfiguration configuration, out ICounterPayload payload)
         {
             payload = null;
 
             string payloadSessionId = (string)traceEvent.PayloadValue(0);
 
-            if (payloadSessionId != sessionId || traceEvent.Version < 1) // Version 1 added the value field.
+            if (payloadSessionId != configuration.SessionId || traceEvent.Version < 1) // Version 1 added the value field.
             {
                 return;
             }
@@ -219,7 +260,7 @@ namespace Microsoft.Diagnostics.Monitoring.EventPipe
             //string rateText = (string)traceEvent.PayloadValue(6); // Not currently using rate for UpDownCounters.
             string valueText = (string)traceEvent.PayloadValue(7);
 
-            if (!filter.IsIncluded(meterName, instrumentName))
+            if (!configuration.CounterFilter.IsIncluded(meterName, instrumentName))
             {
                 return;
             }
@@ -239,12 +280,13 @@ namespace Microsoft.Diagnostics.Monitoring.EventPipe
             }
         }
 
-        private static void HandleHistogram(TraceEvent obj, CounterFilter filter, string sessionId, out ICounterPayload payload)
+        private static void HandleHistogram(TraceEvent obj, CounterConfiguration configuration, out ICounterPayload payload)
         {
             payload = null;
 
             string payloadSessionId = (string)obj.PayloadValue(0);
-            if (payloadSessionId != sessionId)
+
+            if (payloadSessionId != configuration.SessionId)
             {
                 return;
             }
@@ -256,72 +298,71 @@ namespace Microsoft.Diagnostics.Monitoring.EventPipe
             string tags = (string)obj.PayloadValue(5);
             string quantilesText = (string)obj.PayloadValue(6);
 
-            if (!filter.IsIncluded(meterName, instrumentName))
+            if (!configuration.CounterFilter.IsIncluded(meterName, instrumentName))
             {
                 return;
             }
 
             //Note quantiles can be empty.
             IList<Quantile> quantiles = ParseQuantiles(quantilesText);
-            payload = new PercentilePayload(meterName, instrumentName, null, unit, tags, quantiles, obj.TimeStamp);
-        }
-
 
+            payload = new AggregatePercentilePayload(meterName, instrumentName, null, unit, tags, quantiles, obj.TimeStamp);
+        }
 
-        private static void HandleHistogramLimitReached(TraceEvent obj, string sessionId, out ICounterPayload payload)
+        private static void HandleHistogramLimitReached(TraceEvent obj, CounterConfiguration configuration, out ICounterPayload payload)
         {
             payload = null;
 
             string payloadSessionId = (string)obj.PayloadValue(0);
 
-            if (payloadSessionId != sessionId)
+            if (payloadSessionId != configuration.SessionId)
             {
                 return;
             }
 
-            string errorMessage = $"Warning: Histogram tracking limit reached. Not all data is being shown. The limit can be changed with maxHistograms but will use more memory in the target process.";
+            string errorMessage = $"Warning: Histogram tracking limit ({configuration.MaxHistograms}) reached. Not all data is being shown. The limit can be changed but will use more memory in the target process.";
 
-            payload = new ErrorPayload(errorMessage);
+            payload = new ErrorPayload(errorMessage, obj.TimeStamp, EventType.HistogramLimitError);
         }
 
-        private static void HandleTimeSeriesLimitReached(TraceEvent obj, string sessionId, out ICounterPayload payload)
+        private static void HandleTimeSeriesLimitReached(TraceEvent obj, CounterConfiguration configuration, out ICounterPayload payload)
         {
             payload = null;
 
             string payloadSessionId = (string)obj.PayloadValue(0);
 
-            if (payloadSessionId != sessionId)
+            if (payloadSessionId != configuration.SessionId)
             {
                 return;
             }
 
-            string errorMessage = "Warning: Time series tracking limit reached. Not all data is being shown. The limit can be changed with maxTimeSeries but will use more memory in the target process.";
+            string errorMessage = $"Warning: Time series tracking limit ({configuration.MaxTimeseries}) reached. Not all data is being shown. The limit can be changed but will use more memory in the target process.";
 
-            payload = new ErrorPayload(errorMessage, obj.TimeStamp);
+            payload = new ErrorPayload(errorMessage, obj.TimeStamp, EventType.TimeSeriesLimitError);
         }
 
-        private static void HandleError(TraceEvent obj, string sessionId, out ICounterPayload payload)
+        private static void HandleError(TraceEvent obj, CounterConfiguration configuration, out ICounterPayload payload)
         {
             payload = null;
 
             string payloadSessionId = (string)obj.PayloadValue(0);
             string error = (string)obj.PayloadValue(1);
-            if (payloadSessionId != sessionId)
+            if (configuration.SessionId != payloadSessionId)
             {
                 return;
             }
 
             string errorMessage = "Error reported from target process:" + Environment.NewLine + error;
 
-            payload = new ErrorPayload(errorMessage, obj.TimeStamp);
+            payload = new ErrorPayload(errorMessage, obj.TimeStamp, EventType.ErrorTargetProcess);
         }
 
-        private static void HandleMultipleSessionsNotSupportedError(TraceEvent obj, string sessionId, out ICounterPayload payload)
+        private static void HandleMultipleSessionsNotSupportedError(TraceEvent obj, CounterConfiguration configuration, out ICounterPayload payload)
         {
             payload = null;
 
             string payloadSessionId = (string)obj.PayloadValue(0);
-            if (payloadSessionId == sessionId)
+            if (payloadSessionId == configuration.SessionId)
             {
                 // If our session is the one that is running then the error is not for us,
                 // it is for some other session that came later
@@ -332,7 +373,7 @@ namespace Microsoft.Diagnostics.Monitoring.EventPipe
                 string errorMessage = "Error: Another metrics collection session is already in progress for the target process." + Environment.NewLine +
                 "Concurrent sessions are not supported.";
 
-                payload = new ErrorPayload(errorMessage, obj.TimeStamp);
+                payload = new ErrorPayload(errorMessage, obj.TimeStamp, EventType.MultipleSessionsNotSupportedError);
             }
         }
 
@@ -383,20 +424,20 @@ namespace Microsoft.Diagnostics.Monitoring.EventPipe
 
             if (TryCreateSharedSessionConfiguredIncorrectlyMessage(obj, clientId, out string message))
             {
-                payload = new ErrorPayload(message.ToString(), obj.TimeStamp);
+                payload = new ErrorPayload(message.ToString(), obj.TimeStamp, EventType.MultipleSessionsConfiguredIncorrectlyError);
 
                 inactiveSharedSessions.Add(clientId);
             }
         }
 
-        private static void HandleObservableInstrumentCallbackError(TraceEvent obj, string sessionId, out ICounterPayload payload)
+        private static void HandleObservableInstrumentCallbackError(TraceEvent obj, CounterConfiguration configuration, out ICounterPayload payload)
         {
             payload = null;
 
             string payloadSessionId = (string)obj.PayloadValue(0);
             string error = (string)obj.PayloadValue(1);
 
-            if (payloadSessionId != sessionId)
+            if (payloadSessionId != configuration.SessionId)
             {
                 return;
             }
@@ -404,10 +445,10 @@ namespace Microsoft.Diagnostics.Monitoring.EventPipe
             string errorMessage = "Exception thrown from an observable instrument callback in the target process:" + Environment.NewLine +
                 error;
 
-            payload = new ErrorPayload(errorMessage, obj.TimeStamp);
+            payload = new ErrorPayload(errorMessage, obj.TimeStamp, EventType.ObservableInstrumentCallbackError);
         }
 
-        private static IList<Quantile> ParseQuantiles(string quantileList)
+        private static List<Quantile> ParseQuantiles(string quantileList)
         {
             string[] quantileParts = quantileList.Split(';', StringSplitOptions.RemoveEmptyEntries);
             List<Quantile> quantiles = new();
@@ -418,11 +459,11 @@ namespace Microsoft.Diagnostics.Monitoring.EventPipe
                 {
                     continue;
                 }
-                if (!double.TryParse(keyValParts[0], out double key))
+                if (!double.TryParse(keyValParts[0], NumberStyles.Number | NumberStyles.Float, CultureInfo.InvariantCulture, out double key))
                 {
                     continue;
                 }
-                if (!double.TryParse(keyValParts[1], out double val))
+                if (!double.TryParse(keyValParts[1], NumberStyles.Number | NumberStyles.Float, CultureInfo.InvariantCulture, out double val))
                 {
                     continue;
                 }
index 7ebc6409b304c4a7a7c47026bab3b8754ec61259..cff749c126054a75b993313ddd58c9a3a49bd100 100644 (file)
@@ -39,7 +39,7 @@ namespace Microsoft.Diagnostics.Monitoring.EventPipe
             _sessionStarted = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);
         }
 
-        public async Task Process(DiagnosticsClient client, TimeSpan duration, CancellationToken token)
+        public async Task Process(DiagnosticsClient client, TimeSpan duration, bool resumeRuntime, CancellationToken token)
         {
             //No need to guard against reentrancy here, since the calling pipeline does this already.
             IDisposable registration = token.Register(() => TryCancelCompletionSources(token));
@@ -53,7 +53,7 @@ namespace Microsoft.Diagnostics.Monitoring.EventPipe
                     // Allows the event handling routines to stop processing before the duration expires.
                     Func<Task> stopFunc = () => Task.Run(() => { streamProvider.StopProcessing(); });
 
-                    Stream sessionStream = await streamProvider.ProcessEvents(client, duration, token).ConfigureAwait(false);
+                    Stream sessionStream = await streamProvider.ProcessEvents(client, duration, resumeRuntime, token).ConfigureAwait(false);
 
                     if (!_sessionStarted.TrySetResult(true))
                     {
index 45cf97be4391ca631049ba170c530ab18b5a88ce..23bb51b28f24357b6f9ad2e6ea10810d991bb3ce 100644 (file)
@@ -21,7 +21,7 @@ namespace Microsoft.Diagnostics.Monitoring.EventPipe
             _stopProcessingSource = new TaskCompletionSource<object>(TaskCreationOptions.RunContinuationsAsynchronously);
         }
 
-        public async Task<Stream> ProcessEvents(DiagnosticsClient client, TimeSpan duration, CancellationToken cancellationToken)
+        public async Task<Stream> ProcessEvents(DiagnosticsClient client, TimeSpan duration, bool resumeRuntime, CancellationToken cancellationToken)
         {
             cancellationToken.ThrowIfCancellationRequested();
 
@@ -29,6 +29,17 @@ namespace Microsoft.Diagnostics.Monitoring.EventPipe
             try
             {
                 session = await client.StartEventPipeSessionAsync(_sourceConfig.GetProviders(), _sourceConfig.RequestRundown, _sourceConfig.BufferSizeInMB, cancellationToken).ConfigureAwait(false);
+                if (resumeRuntime)
+                {
+                    try
+                    {
+                        await client.ResumeRuntimeAsync(cancellationToken).ConfigureAwait(false);
+                    }
+                    catch (UnsupportedCommandException)
+                    {
+                        // Noop if the command is unknown since the target process is most likely a 3.1 app.
+                    }
+                }
             }
             catch (EndOfStreamException e)
             {
index 7da5c54ea6912708176538e46aaaa171097476bd..1b36f04e523cd0d4f9b0f0a2aab7f75816be6c6d 100644 (file)
@@ -35,7 +35,7 @@ namespace Microsoft.Diagnostics.Monitoring.EventPipe
         {
             try
             {
-                return _processor.Value.Process(Client, Settings.Duration, token);
+                return _processor.Value.Process(Client, Settings.Duration, Settings.ResumeRuntime, token);
             }
             catch (InvalidOperationException e)
             {
index ece20a23689f3f4ab63177d6223626489b3fc718..1c35878eb3fefa6cd3f3fb3e21b25a8c9d551296 100644 (file)
@@ -8,5 +8,7 @@ namespace Microsoft.Diagnostics.Monitoring.EventPipe
     internal class EventSourcePipelineSettings
     {
         public TimeSpan Duration { get; set; }
+
+        public bool ResumeRuntime { get; set; }
     }
 }
index 0682848f12adeca53e4ba842ef25d21cbaa707c5..c104003875542dffafc874bdcc1305a0eaa7f0f2 100644 (file)
@@ -40,6 +40,7 @@
   <ItemGroup>
     <InternalsVisibleTo Include="dotnet-monitor" />
     <InternalsVisibleTo Include="dotnet-counters" />
+    <InternalsVisibleTo Include="DotnetCounters.UnitTests" />
     <InternalsVisibleTo Include="Microsoft.Diagnostics.Monitoring.EventPipe.UnitTests" />
     <InternalsVisibleTo Include="Microsoft.Diagnostics.Monitoring.WebApi" />
     <InternalsVisibleTo Include="Microsoft.Diagnostics.Monitoring.Tool.UnitTests" />
index 68f1760364a0514e75f90577f9df2b6186c50900..bb4d1fc3afb718dd02006fc818c9d812281f771c 100644 (file)
@@ -31,7 +31,7 @@ namespace Microsoft.Diagnostics.Monitoring.EventPipe
             {
                 //It is important that the underlying stream be completely read, or disposed.
                 //If rundown is enabled, the underlying stream must be drained or disposed, or the app hangs.
-                using Stream eventStream = await _provider.Value.ProcessEvents(Client, Settings.Duration, token).ConfigureAwait(false);
+                using Stream eventStream = await _provider.Value.ProcessEvents(Client, Settings.Duration, Settings.ResumeRuntime, token).ConfigureAwait(false);
 
                 await _onStreamAvailable(eventStream, token).ConfigureAwait(false);
             }
index 6350d430386c90fdffc7258925ed2a991b28b4dc..a1b96bf00495c6612df1444fb2324f80f7e09e23 100644 (file)
@@ -32,6 +32,7 @@ namespace Microsoft.Diagnostics.Monitoring.EventPipe.Triggers.EventCounter
         private readonly CounterFilter _filter;
         private readonly EventCounterTriggerImpl _impl;
         private readonly string _providerName;
+        private CounterConfiguration _counterConfiguration;
 
         public EventCounterTrigger(EventCounterTriggerSettings settings)
         {
@@ -45,6 +46,8 @@ namespace Microsoft.Diagnostics.Monitoring.EventPipe.Triggers.EventCounter
             _filter = new CounterFilter(settings.CounterIntervalSeconds);
             _filter.AddFilter(settings.ProviderName, new string[] { settings.CounterName });
 
+            _counterConfiguration = new CounterConfiguration(_filter);
+
             _impl = new EventCounterTriggerImpl(settings);
 
             _providerName = settings.ProviderName;
@@ -58,7 +61,7 @@ namespace Microsoft.Diagnostics.Monitoring.EventPipe.Triggers.EventCounter
         public bool HasSatisfiedCondition(TraceEvent traceEvent)
         {
             // Filter to the counter of interest before forwarding to the implementation
-            if (traceEvent.TryGetCounterPayload(_filter, null, null, out ICounterPayload payload))
+            if (traceEvent.TryGetCounterPayload(_counterConfiguration, out ICounterPayload payload))
             {
                 return _impl.HasSatisfiedCondition(payload);
             }
index 378e38906565cfc632d3fd276b73b0958013769e..d9e9a1267a55785fd67ec1ad947a183ec8653588 100644 (file)
@@ -29,6 +29,7 @@ namespace Microsoft.Diagnostics.Monitoring.EventPipe.Triggers.SystemDiagnosticsM
         private readonly string _meterName;
         private readonly string _clientId;
         private readonly string _sessionId;
+        private CounterConfiguration _counterConfiguration;
 
         public SystemDiagnosticsMetricsTrigger(SystemDiagnosticsMetricsTriggerSettings settings)
         {
@@ -49,6 +50,10 @@ namespace Microsoft.Diagnostics.Monitoring.EventPipe.Triggers.SystemDiagnosticsM
             _clientId = settings.ClientId;
 
             _sessionId = settings.SessionId;
+
+            _clientId = settings.ClientId;
+
+            _counterConfiguration = new CounterConfiguration(_filter) { SessionId = _sessionId, ClientId = _clientId };
         }
 
         public IReadOnlyDictionary<string, IReadOnlyCollection<string>> GetProviderEventMap()
@@ -59,7 +64,7 @@ namespace Microsoft.Diagnostics.Monitoring.EventPipe.Triggers.SystemDiagnosticsM
         public bool HasSatisfiedCondition(TraceEvent traceEvent)
         {
             // Filter to the counter of interest before forwarding to the implementation
-            if (traceEvent.TryGetCounterPayload(_filter, _sessionId, _clientId, out ICounterPayload payload))
+            if (traceEvent.TryGetCounterPayload(_counterConfiguration, out ICounterPayload payload))
             {
                 return _impl.HasSatisfiedCondition(payload);
             }
index cb60f10320bafcba23a342e6462e8d54e2cae2c3..d0e4e28087c66d460cfd72f2444d269e2961a6c7 100644 (file)
@@ -55,7 +55,7 @@ namespace Microsoft.Diagnostics.Monitoring.EventPipe.Triggers.SystemDiagnosticsM
         {
             EventType eventType = payload.EventType;
 
-            if (eventType == EventType.Error || eventType == EventType.CounterEnded)
+            if (eventType.IsError() || eventType == EventType.CounterEnded)
             {
                 // not currently logging the error messages
 
@@ -63,17 +63,17 @@ namespace Microsoft.Diagnostics.Monitoring.EventPipe.Triggers.SystemDiagnosticsM
             }
             else
             {
-                bool passesValueFilter = (payload is PercentilePayload percentilePayload) ?
-                    _valueFilterHistogram(CreatePayloadDictionary(percentilePayload)) :
+                bool passesValueFilter = (payload is AggregatePercentilePayload aggregatePercentilePayload) ?
+                    _valueFilterHistogram(CreatePayloadDictionary(aggregatePercentilePayload)) :
                     _valueFilterDefault(payload.Value);
 
                 return SharedTriggerImplHelper.HasSatisfiedCondition(ref _latestTicks, ref _targetTicks, _windowTicks, _intervalTicks, payload, passesValueFilter);
             }
         }
 
-        private static Dictionary<int, double> CreatePayloadDictionary(PercentilePayload percentilePayload)
+        private static Dictionary<int, double> CreatePayloadDictionary(AggregatePercentilePayload aggregatePercentilePayload)
         {
-            return percentilePayload.Quantiles.ToDictionary(keySelector: p => CounterUtilities.CreatePercentile(p.Percentage), elementSelector: p => p.Value);
+            return aggregatePercentilePayload.Quantiles.ToDictionary(keySelector: q => CounterUtilities.CreatePercentile(q.Percentage), elementSelector: q => q.Value);
         }
     }
 }
index 975476fa6242b5b309e9124a36cf427acc1026c6..a4fff0796fd3cf8c69e4f5226ebbcdcbd37ebc00 100644 (file)
@@ -26,6 +26,8 @@
 
   <ItemGroup>
     <InternalsVisibleTo Include="dotnet-monitor" />
+    <InternalsVisibleTo Include="dotnet-counters" />
+    <InternalsVisibleTo Include="DotnetCounters.UnitTests" />
     <InternalsVisibleTo Include="Microsoft.Diagnostics.Monitoring.EventPipe" />
     <InternalsVisibleTo Include="Microsoft.Diagnostics.Monitoring.EventPipe.UnitTests" />
     <InternalsVisibleTo Include="Microsoft.Diagnostics.Monitoring.Tool.UnitTests" />
index a2a623d0a6ede1123074585c0dff74532b95fb09..04159229abf6637e718f9f59ff05f254a01f7078 100644 (file)
@@ -6,45 +6,31 @@ using System.Collections.Generic;
 using System.CommandLine;
 using System.CommandLine.IO;
 using System.CommandLine.Rendering;
+using System.ComponentModel;
 using System.Diagnostics;
-using System.Diagnostics.Tracing;
-using System.Globalization;
-using System.IO;
 using System.Linq;
-using System.Text;
 using System.Threading;
 using System.Threading.Tasks;
+using Microsoft.Diagnostics.Monitoring;
 using Microsoft.Diagnostics.Monitoring.EventPipe;
 using Microsoft.Diagnostics.NETCore.Client;
 using Microsoft.Diagnostics.Tools.Counters.Exporters;
-using Microsoft.Diagnostics.Tracing;
 using Microsoft.Internal.Common.Utils;
 
 namespace Microsoft.Diagnostics.Tools.Counters
 {
-    public class CounterMonitor
+    internal class CounterMonitor : ICountersLogger
     {
         private const int BufferDelaySecs = 1;
-        private const string SharedSessionId = "SHARED"; // This should be identical to the one used by dotnet-monitor in MetricSourceConfiguration.cs
-        private static HashSet<string> inactiveSharedSessions = new(StringComparer.OrdinalIgnoreCase);
-
-        private string _sessionId;
         private int _processId;
-        private int _interval;
         private CounterSet _counterList;
-        private CancellationToken _ct;
         private IConsole _console;
         private ICounterRenderer _renderer;
         private string _output;
         private bool _pauseCmdSet;
-        private readonly TaskCompletionSource<int> _shouldExit;
-        private bool _resumeRuntime;
+        private readonly TaskCompletionSource<ReturnCode> _shouldExit;
         private DiagnosticsClient _diagnosticsClient;
-        private EventPipeSession _session;
-        private readonly string _clientId;
-        private int _maxTimeSeries;
-        private int _maxHistograms;
-        private TimeSpan _duration;
+        private MetricsPipelineSettings _settings;
 
         private class ProviderEventState
         {
@@ -57,77 +43,7 @@ namespace Microsoft.Diagnostics.Tools.Counters
         public CounterMonitor()
         {
             _pauseCmdSet = false;
-            _clientId = Guid.NewGuid().ToString();
-
-            _shouldExit = new TaskCompletionSource<int>();
-        }
-
-        private void DynamicAllMonitor(TraceEvent obj)
-        {
-            if (_shouldExit.Task.IsCompleted)
-            {
-                return;
-            }
-
-            lock (this)
-            {
-                // If we are paused, ignore the event.
-                // There's a potential race here between the two tasks but not a huge deal if we miss by one event.
-                _renderer.ToggleStatus(_pauseCmdSet);
-
-                // If a session received a MultipleSessionsConfiguredIncorrectlyError, ignore future shared events
-                if (obj.ProviderName == "System.Diagnostics.Metrics" && !inactiveSharedSessions.Contains(_clientId))
-                {
-                    if (obj.EventName == "BeginInstrumentReporting")
-                    {
-                        HandleBeginInstrumentReporting(obj);
-                    }
-                    if (obj.EventName == "HistogramValuePublished")
-                    {
-                        HandleHistogram(obj);
-                    }
-                    else if (obj.EventName == "GaugeValuePublished")
-                    {
-                        HandleGauge(obj);
-                    }
-                    else if (obj.EventName == "CounterRateValuePublished")
-                    {
-                        HandleCounterRate(obj);
-                    }
-                    else if (obj.EventName == "UpDownCounterRateValuePublished")
-                    {
-                        HandleUpDownCounterValue(obj);
-                    }
-                    else if (obj.EventName == "TimeSeriesLimitReached")
-                    {
-                        HandleTimeSeriesLimitReached(obj);
-                    }
-                    else if (obj.EventName == "HistogramLimitReached")
-                    {
-                        HandleHistogramLimitReached(obj);
-                    }
-                    else if (obj.EventName == "Error")
-                    {
-                        HandleError(obj);
-                    }
-                    else if (obj.EventName == "ObservableInstrumentCallbackError")
-                    {
-                        HandleObservableInstrumentCallbackError(obj);
-                    }
-                    else if (obj.EventName == "MultipleSessionsNotSupportedError")
-                    {
-                        HandleMultipleSessionsNotSupportedError(obj);
-                    }
-                    else if (obj.EventName == "MultipleSessionsConfiguredIncorrectlyError")
-                    {
-                        HandleMultipleSessionsConfiguredIncorrectlyError(obj);
-                    }
-                }
-                else if (obj.EventName == "EventCounters")
-                {
-                    HandleDiagnosticCounter(obj);
-                }
-            }
+            _shouldExit = new TaskCompletionSource<ReturnCode>();
         }
 
         private void MeterInstrumentEventObserved(string meterName, DateTime timestamp)
@@ -147,255 +63,16 @@ namespace Microsoft.Diagnostics.Tools.Counters
             }
         }
 
-        private void HandleBeginInstrumentReporting(TraceEvent obj)
-        {
-            string sessionId = (string)obj.PayloadValue(0);
-            string meterName = (string)obj.PayloadValue(1);
-            // string instrumentName = (string)obj.PayloadValue(3);
-            if (sessionId != _sessionId)
-            {
-                return;
-            }
-            MeterInstrumentEventObserved(meterName, obj.TimeStamp);
-        }
-
-        private void HandleCounterRate(TraceEvent obj)
-        {
-            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);
-            if (sessionId != _sessionId || !Filter(meterName, instrumentName))
-            {
-                return;
-            }
-            MeterInstrumentEventObserved(meterName, obj.TimeStamp);
-
-            // the value might be an empty string indicating no measurement was provided this collection interval
-            if (double.TryParse(rateText, NumberStyles.Number | NumberStyles.Float, CultureInfo.InvariantCulture, out double rate))
-            {
-                CounterPayload payload = new RatePayload(meterName, instrumentName, null, unit, tags, rate, _interval, obj.TimeStamp);
-                _renderer.CounterPayloadReceived(payload, _pauseCmdSet);
-            }
-
-        }
-
-        private void HandleGauge(TraceEvent obj)
-        {
-            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 lastValueText = (string)obj.PayloadValue(6);
-            if (sessionId != _sessionId || !Filter(meterName, instrumentName))
-            {
-                return;
-            }
-            MeterInstrumentEventObserved(meterName, obj.TimeStamp);
-
-            // the value might be an empty string indicating no measurement was provided this collection interval
-            if (double.TryParse(lastValueText, NumberStyles.Number | NumberStyles.Float, CultureInfo.InvariantCulture, out double lastValue))
-            {
-                CounterPayload payload = new GaugePayload(meterName, instrumentName, null, unit, tags, lastValue, 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 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 != _sessionId || !Filter(meterName, instrumentName))
-            {
-                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);
-            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 quantilesText = (string)obj.PayloadValue(6);
-            if (sessionId != _sessionId || !Filter(meterName, instrumentName))
-            {
-                return;
-            }
-            MeterInstrumentEventObserved(meterName, obj.TimeStamp);
-            KeyValuePair<double, double>[] quantiles = ParseQuantiles(quantilesText);
-            foreach ((double key, double val) in quantiles)
-            {
-                CounterPayload payload = new PercentilePayload(meterName, instrumentName, null, unit, AppendQuantile(tags, $"Percentile={key * 100}"), val, obj.TimeStamp);
-                _renderer.CounterPayloadReceived(payload, _pauseCmdSet);
-            }
-        }
-
-        private void HandleHistogramLimitReached(TraceEvent obj)
-        {
-            string sessionId = (string)obj.PayloadValue(0);
-            if (sessionId != _clientId)
-            {
-                return;
-            }
-            _renderer.SetErrorText(
-                $"Warning: Histogram tracking limit ({_maxHistograms}) reached. Not all data is being shown." + Environment.NewLine +
-                "The limit can be changed with --maxHistograms but will use more memory in the target process."
-                );
-        }
-
-        private void HandleTimeSeriesLimitReached(TraceEvent obj)
-        {
-            string sessionId = (string)obj.PayloadValue(0);
-            if (sessionId != _sessionId)
-            {
-                return;
-            }
-            _renderer.SetErrorText(
-                $"Warning: Time series tracking limit ({_maxTimeSeries}) reached. Not all data is being shown." + Environment.NewLine +
-                "The limit can be changed with --maxTimeSeries but will use more memory in the target process."
-                );
-        }
-
-        private void HandleError(TraceEvent obj)
-        {
-            string sessionId = (string)obj.PayloadValue(0);
-            string error = (string)obj.PayloadValue(1);
-            if (sessionId != _sessionId)
-            {
-                return;
-            }
-            _renderer.SetErrorText(
-                "Error reported from target process:" + Environment.NewLine +
-                error
-                );
-            _shouldExit.TrySetResult((int)ReturnCode.TracingError);
-        }
-
-        private void HandleObservableInstrumentCallbackError(TraceEvent obj)
-        {
-            string sessionId = (string)obj.PayloadValue(0);
-            string error = (string)obj.PayloadValue(1);
-            if (sessionId != _sessionId)
-            {
-                return;
-            }
-            _renderer.SetErrorText(
-                "Exception thrown from an observable instrument callback in the target process:" + Environment.NewLine +
-                error
-                );
-        }
-
-        private void HandleMultipleSessionsNotSupportedError(TraceEvent obj)
-        {
-            string runningSessionId = (string)obj.PayloadValue(0);
-            if (runningSessionId == _sessionId)
-            {
-                // If our session is the one that is running then the error is not for us,
-                // it is for some other session that came later
-                return;
-            }
-            _renderer.SetErrorText(
-                "Error: Another metrics collection session is already in progress for the target process." + Environment.NewLine +
-                "Concurrent sessions are not supported.");
-            _shouldExit.TrySetResult((int)ReturnCode.SessionCreationError);
-        }
-
-        private void HandleMultipleSessionsConfiguredIncorrectlyError(TraceEvent obj)
-        {
-            if (TraceEventExtensions.TryCreateSharedSessionConfiguredIncorrectlyMessage(obj, _clientId, out string message))
-            {
-                _renderer.SetErrorText(message);
-                inactiveSharedSessions.Add(_clientId);
-                _shouldExit.TrySetResult((int)ReturnCode.SessionCreationError);
-            }
-        }
-
-        private static KeyValuePair<double, double>[] ParseQuantiles(string quantileList)
-        {
-            string[] quantileParts = quantileList.Split(';', StringSplitOptions.RemoveEmptyEntries);
-            List<KeyValuePair<double, double>> quantiles = new();
-            foreach (string quantile in quantileParts)
-            {
-                string[] keyValParts = quantile.Split('=', StringSplitOptions.RemoveEmptyEntries);
-                if (keyValParts.Length != 2)
-                {
-                    continue;
-                }
-                if (!double.TryParse(keyValParts[0], NumberStyles.Number | NumberStyles.Float, CultureInfo.InvariantCulture, out double key))
-                {
-                    continue;
-                }
-                if (!double.TryParse(keyValParts[1], NumberStyles.Number | NumberStyles.Float, CultureInfo.InvariantCulture, out double val))
-                {
-                    continue;
-                }
-                quantiles.Add(new KeyValuePair<double, double>(key, val));
-            }
-            return quantiles.ToArray();
-        }
-
-        private static string AppendQuantile(string tags, string quantile) => string.IsNullOrEmpty(tags) ? quantile : $"{tags},{quantile}";
-
-        private void HandleDiagnosticCounter(TraceEvent obj)
+        private void HandleDiagnosticCounter(ICounterPayload payload)
         {
-            IDictionary<string, object> payloadVal = (IDictionary<string, object>)(obj.PayloadValue(0));
-            IDictionary<string, object> payloadFields = (IDictionary<string, object>)(payloadVal["Payload"]);
-
-            // If it's not a counter we asked for, ignore it.
-            string name = payloadFields["Name"].ToString();
-            if (!_counterList.Contains(obj.ProviderName, name))
-            {
-                return;
-            }
-
             // init providerEventState if this is the first time we've seen an event from this provider
-            if (!_providerEventStates.TryGetValue(obj.ProviderName, out ProviderEventState providerState))
+            if (!_providerEventStates.TryGetValue(payload.Provider, out ProviderEventState providerState))
             {
                 providerState = new ProviderEventState()
                 {
-                    FirstReceiveTimestamp = obj.TimeStamp
+                    FirstReceiveTimestamp = payload.Timestamp
                 };
-                _providerEventStates.Add(obj.ProviderName, providerState);
+                _providerEventStates.Add(payload.Provider, providerState);
             }
 
             // we give precedence to instrument events over diagnostic counter events. If we are seeing
@@ -405,42 +82,35 @@ namespace Microsoft.Diagnostics.Tools.Counters
                 return;
             }
 
-            CounterPayload payload;
-            if (payloadFields["CounterType"].Equals("Sum"))
-            {
-                payload = new RatePayload(
-                    obj.ProviderName,
-                    name,
-                    payloadFields["DisplayName"].ToString(),
-                    payloadFields["DisplayUnits"].ToString(),
-                    null,
-                    (double)payloadFields["Increment"],
-                    _interval,
-                    obj.TimeStamp);
-            }
-            else
-            {
-                payload = new GaugePayload(
-                    obj.ProviderName,
-                    name,
-                    payloadFields["DisplayName"].ToString(),
-                    payloadFields["DisplayUnits"].ToString(),
-                    null,
-                    (double)payloadFields["Mean"],
-                    obj.TimeStamp);
-            }
-
             // If we saw the first event for this provider recently then a duplicate instrument event may still be
             // coming. We'll buffer this event for a while and then render it if it remains unduplicated for
             // a while.
             // This is all best effort, if we do show the DiagnosticCounter event and then an instrument event shows up
-            // later the renderer may obsserve some odd behavior like changes in the counter metadata, oddly timed reporting
+            // later the renderer may observe some odd behavior like changes in the counter metadata, oddly timed reporting
             // intervals, or counters that stop reporting.
             // I'm gambling this is good enough that the behavior will never be seen in practice, but if it is we could
             // either adjust the time delay or try to improve how the renderers handle it.
-            if (providerState.FirstReceiveTimestamp + TimeSpan.FromSeconds(BufferDelaySecs) >= obj.TimeStamp)
+            if (providerState.FirstReceiveTimestamp + TimeSpan.FromSeconds(BufferDelaySecs) >= payload.Timestamp)
+            {
+                _bufferedEvents.Enqueue((CounterPayload)payload);
+            }
+            else
             {
-                _bufferedEvents.Enqueue(payload);
+                CounterPayloadReceived((CounterPayload)payload);
+            }
+        }
+
+        private void CounterPayloadReceived(CounterPayload payload)
+        {
+            if (payload is AggregatePercentilePayload aggregatePayload)
+            {
+                foreach (Quantile quantile in aggregatePayload.Quantiles)
+                {
+                    (double key, double val) = quantile;
+                    PercentilePayload percentilePayload = new(payload.Provider, payload.Name, payload.DisplayName, payload.Unit, AppendQuantile(payload.Metadata, $"Percentile={key * 100}"), val, payload.Timestamp);
+                    _renderer.CounterPayloadReceived(percentilePayload, _pauseCmdSet);
+                }
+
             }
             else
             {
@@ -448,6 +118,8 @@ namespace Microsoft.Diagnostics.Tools.Counters
             }
         }
 
+        private static string AppendQuantile(string tags, string quantile) => string.IsNullOrEmpty(tags) ? quantile : $"{tags},{quantile}";
+
         // when receiving DiagnosticCounter events we may have buffered them to wait for
         // duplicate instrument events. If we've waited long enough then we should remove
         // them from the buffer and render them.
@@ -459,7 +131,7 @@ namespace Microsoft.Diagnostics.Tools.Counters
                 while (_bufferedEvents.Count != 0)
                 {
                     CounterPayload payload = _bufferedEvents.Peek();
-                    ProviderEventState providerEventState = _providerEventStates[payload.ProviderName];
+                    ProviderEventState providerEventState = _providerEventStates[payload.Provider];
                     if (providerEventState.InstrumentEventObserved)
                     {
                         _bufferedEvents.Dequeue();
@@ -467,7 +139,7 @@ namespace Microsoft.Diagnostics.Tools.Counters
                     else if (providerEventState.FirstReceiveTimestamp + TimeSpan.FromSeconds(BufferDelaySecs) < now)
                     {
                         _bufferedEvents.Dequeue();
-                        _renderer.CounterPayloadReceived(payload, _pauseCmdSet);
+                        CounterPayloadReceived((CounterPayload)payload);
                     }
                     else
                     {
@@ -481,37 +153,7 @@ namespace Microsoft.Diagnostics.Tools.Counters
             }
         }
 
-        private void StopMonitor()
-        {
-            try
-            {
-                _session?.Stop();
-            }
-            catch (EndOfStreamException ex)
-            {
-                // If the app we're monitoring exits abruptly, this may throw in which case we just swallow the exception and exit gracefully.
-                Debug.WriteLine($"[ERROR] {ex}");
-            }
-            // We may time out if the process ended before we sent StopTracing command. We can just exit in that case.
-            catch (TimeoutException)
-            {
-            }
-            // On Unix platforms, we may actually get a PNSE since the pipe is gone with the process, and Runtime Client Library
-            // does not know how to distinguish a situation where there is no pipe to begin with, or where the process has exited
-            // before dotnet-counters and got rid of a pipe that once existed.
-            // Since we are catching this in StopMonitor() we know that the pipe once existed (otherwise the exception would've
-            // been thrown in StartMonitor directly)
-            catch (PlatformNotSupportedException)
-            {
-            }
-            // On non-abrupt exits, the socket may be already closed by the runtime and we won't be able to send a stop request through it.
-            catch (ServerNotAvailableException)
-            {
-            }
-            _renderer.Stop();
-        }
-
-        public async Task<int> Monitor(
+        public async Task<ReturnCode> Monitor(
             CancellationToken ct,
             List<string> counter_list,
             string counters,
@@ -535,7 +177,7 @@ namespace Microsoft.Diagnostics.Tools.Counters
                 ValidateNonNegative(maxTimeSeries, nameof(maxTimeSeries));
                 if (!ProcessLauncher.Launcher.HasChildProc && !CommandUtils.ValidateArgumentsForAttach(processId, name, diagnosticPort, out _processId))
                 {
-                    return (int)ReturnCode.ArgumentError;
+                    return ReturnCode.ArgumentError;
                 }
                 ct.Register(() => _shouldExit.TrySetResult((int)ReturnCode.Ok));
 
@@ -546,7 +188,7 @@ namespace Microsoft.Diagnostics.Tools.Counters
                     bool useAnsi = vTerm.IsEnabled;
                     if (holder == null)
                     {
-                        return (int)ReturnCode.Ok;
+                        return ReturnCode.Ok;
                     }
                     try
                     {
@@ -554,38 +196,48 @@ namespace Microsoft.Diagnostics.Tools.Counters
                         // the launch command may misinterpret app arguments as the old space separated
                         // provider list so we need to ignore it in that case
                         _counterList = ConfigureCounters(counters, _processId != 0 ? counter_list : null);
-                        _ct = ct;
-                        _interval = refreshInterval;
-                        _maxHistograms = maxHistograms;
-                        _maxTimeSeries = maxTimeSeries;
                         _renderer = new ConsoleWriter(useAnsi);
                         _diagnosticsClient = holder.Client;
-                        _resumeRuntime = resumeRuntime;
-                        _duration = duration;
-                        int ret = await Start().ConfigureAwait(false);
+                        _settings = new MetricsPipelineSettings();
+                        _settings.Duration = duration == TimeSpan.Zero ? Timeout.InfiniteTimeSpan : duration;
+                        _settings.MaxHistograms = maxHistograms;
+                        _settings.MaxTimeSeries = maxTimeSeries;
+                        _settings.CounterIntervalSeconds = refreshInterval;
+                        _settings.ResumeRuntime = resumeRuntime;
+                        _settings.CounterGroups = GetEventPipeProviders();
+
+                        bool useSharedSession = false;
+                        if (_diagnosticsClient.GetProcessInfo().TryGetProcessClrVersion(out Version version))
+                        {
+                            useSharedSession = version.Major >= 8 ? true : false;
+                        }
+                        _settings.UseSharedSession = useSharedSession;
+
+                        ReturnCode ret;
+                        MetricsPipeline eventCounterPipeline = new(holder.Client, _settings, new[] { this });
+                        await using (eventCounterPipeline.ConfigureAwait(false))
+                        {
+                            ret = await Start(eventCounterPipeline, ct).ConfigureAwait(false);
+                        }
                         ProcessLauncher.Launcher.Cleanup();
                         return ret;
                     }
                     catch (OperationCanceledException)
                     {
-                        try
-                        {
-                            _session.Stop();
-                        }
-                        catch (Exception) { } // Swallow all exceptions for now.
+                        //Cancellation token should automatically stop the session
 
                         console.Out.WriteLine($"Complete");
-                        return (int)ReturnCode.Ok;
+                        return ReturnCode.Ok;
                     }
                 }
             }
             catch (CommandLineErrorException e)
             {
                 console.Error.WriteLine(e.Message);
-                return (int)ReturnCode.ArgumentError;
+                return ReturnCode.ArgumentError;
             }
         }
-        public async Task<int> Collect(
+        public async Task<ReturnCode> Collect(
             CancellationToken ct,
             List<string> counter_list,
             string counters,
@@ -611,7 +263,7 @@ namespace Microsoft.Diagnostics.Tools.Counters
                 ValidateNonNegative(maxTimeSeries, nameof(maxTimeSeries));
                 if (!ProcessLauncher.Launcher.HasChildProc && !CommandUtils.ValidateArgumentsForAttach(processId, name, diagnosticPort, out _processId))
                 {
-                    return (int)ReturnCode.ArgumentError;
+                    return ReturnCode.ArgumentError;
                 }
                 ct.Register(() => _shouldExit.TrySetResult((int)ReturnCode.Ok));
 
@@ -629,17 +281,19 @@ namespace Microsoft.Diagnostics.Tools.Counters
                         // the launch command may misinterpret app arguments as the old space separated
                         // provider list so we need to ignore it in that case
                         _counterList = ConfigureCounters(counters, _processId != 0 ? counter_list : null);
-                        _ct = ct;
-                        _interval = refreshInterval;
-                        _maxHistograms = maxHistograms;
-                        _maxTimeSeries = maxTimeSeries;
+                        _settings = new MetricsPipelineSettings();
+                        _settings.Duration = duration == TimeSpan.Zero ? Timeout.InfiniteTimeSpan : duration;
+                        _settings.MaxHistograms = maxHistograms;
+                        _settings.MaxTimeSeries = maxTimeSeries;
+                        _settings.CounterIntervalSeconds = refreshInterval;
+                        _settings.ResumeRuntime = resumeRuntime;
+                        _settings.CounterGroups = GetEventPipeProviders();
                         _output = output;
                         _diagnosticsClient = holder.Client;
-                        _duration = duration;
                         if (_output.Length == 0)
                         {
                             _console.Error.WriteLine("Output cannot be an empty string");
-                            return (int)ReturnCode.ArgumentError;
+                            return ReturnCode.ArgumentError;
                         }
                         if (format == CountersExportFormat.csv)
                         {
@@ -663,27 +317,29 @@ namespace Microsoft.Diagnostics.Tools.Counters
                         else
                         {
                             _console.Error.WriteLine($"The output format {format} is not a valid output format.");
-                            return (int)ReturnCode.ArgumentError;
+                            return ReturnCode.ArgumentError;
                         }
-                        _resumeRuntime = resumeRuntime;
-                        int ret = await Start().ConfigureAwait(false);
+
+                        ReturnCode ret;
+                        MetricsPipeline eventCounterPipeline = new(holder.Client, _settings, new[] { this });
+                        await using (eventCounterPipeline.ConfigureAwait(false))
+                        {
+                            ret = await Start(pipeline: eventCounterPipeline, ct).ConfigureAwait(false);
+                        }
+
                         return ret;
                     }
                     catch (OperationCanceledException)
                     {
-                        try
-                        {
-                            _session.Stop();
-                        }
-                        catch (Exception) { } // session.Stop() can throw if target application already stopped before we send the stop command.
-                        return (int)ReturnCode.Ok;
+                        //Cancellation token should automatically stop the session
+                        return ReturnCode.Ok;
                     }
                 }
             }
             catch (CommandLineErrorException e)
             {
                 console.Error.WriteLine(e.Message);
-                return (int)ReturnCode.ArgumentError;
+                return ReturnCode.ArgumentError;
             }
         }
 
@@ -833,85 +489,21 @@ namespace Microsoft.Diagnostics.Tools.Counters
             }
         }
 
-        private EventPipeProvider[] GetEventPipeProviders()
-        {
-            // EventSources support EventCounter based metrics directly
-            IEnumerable<EventPipeProvider> eventCounterProviders = _counterList.Providers.Select(
-                providerName => new EventPipeProvider(providerName, EventLevel.Error, 0, new Dictionary<string, string>()
-                {{ "EventCounterIntervalSec", _interval.ToString() }}));
-
-            //System.Diagnostics.Metrics EventSource supports the new Meter/Instrument APIs
-            const long TimeSeriesValues = 0x2;
-            StringBuilder metrics = new();
-            foreach (string provider in _counterList.Providers)
+        private EventPipeCounterGroup[] GetEventPipeProviders() =>
+            _counterList.Providers.Select(provider => new EventPipeCounterGroup
             {
-                if (metrics.Length != 0)
-                {
-                    metrics.Append(',');
-                }
-                if (_counterList.IncludesAllCounters(provider))
-                {
-                    metrics.Append(provider);
-                }
-                else
-                {
-                    string[] providerCounters = _counterList.GetCounters(provider).Select(counter => $"{provider}\\{counter}").ToArray();
-                    metrics.Append(string.Join(',', providerCounters));
-                }
-            }
+                ProviderName = provider,
+                CounterNames = _counterList.GetCounters(provider).ToArray()
+            }).ToArray();
 
-            // Shared Session Id was added in 8.0 - older runtimes will not properly support it.
-            _sessionId = Guid.NewGuid().ToString();
-            if (_diagnosticsClient.GetProcessInfo().TryGetProcessClrVersion(out Version version))
-            {
-                _sessionId = version.Major >= 8 ? SharedSessionId : _sessionId;
-            }
-
-            EventPipeProvider metricsEventSourceProvider =
-                new("System.Diagnostics.Metrics", EventLevel.Informational, TimeSeriesValues,
-                    new Dictionary<string, string>()
-                    {
-                        { "SessionId", _sessionId },
-                        { "Metrics", metrics.ToString() },
-                        { "RefreshInterval", _interval.ToString() },
-                        { "MaxTimeSeries", _maxTimeSeries.ToString() },
-                        { "MaxHistograms", _maxHistograms.ToString() },
-                        { "ClientId", _clientId  }
-                    }
-                );
-
-            return eventCounterProviders.Append(metricsEventSourceProvider).ToArray();
-        }
-
-        private bool Filter(string meterName, string instrumentName)
-        {
-            return _counterList.GetCounters(meterName).Contains(instrumentName) || _counterList.IncludesAllCounters(meterName);
-        }
-
-        private Task<int> Start()
+        private async Task<ReturnCode> Start(MetricsPipeline pipeline, CancellationToken token)
         {
-            EventPipeProvider[] providers = GetEventPipeProviders();
             _renderer.Initialize();
-
-            Task monitorTask = new(() => {
+            Task monitorTask = new(async () => {
                 try
                 {
-                    _session = _diagnosticsClient.StartEventPipeSession(providers, false, 10);
-                    if (_resumeRuntime)
-                    {
-                        try
-                        {
-                            _diagnosticsClient.ResumeRuntime();
-                        }
-                        catch (UnsupportedCommandException)
-                        {
-                            // Noop if the command is unknown since the target process is most likely a 3.1 app.
-                        }
-                    }
-                    EventPipeEventSource source = new(_session.EventStream);
-                    source.Dynamic.All += DynamicAllMonitor;
-                    _renderer.EventPipeSourceConnected();
-                    source.Process();
+                    Task runAsyncTask = await pipeline.StartAsync(token).ConfigureAwait(false);
+                    await runAsyncTask.ConfigureAwait(false);
                 }
                 catch (DiagnosticsClientException ex)
                 {
@@ -928,15 +520,8 @@ namespace Microsoft.Diagnostics.Tools.Counters
             });
 
             monitorTask.Start();
-            bool shouldStopAfterDuration = _duration != default(TimeSpan);
-            Stopwatch durationStopwatch = null;
 
-            if (shouldStopAfterDuration)
-            {
-                durationStopwatch = Stopwatch.StartNew();
-            }
-
-            while (!_shouldExit.Task.Wait(250))
+            while (!_shouldExit.Task.Wait(250, token))
             {
                 HandleBufferedEvents();
                 if (!Console.IsInputRedirected && Console.KeyAvailable)
@@ -955,16 +540,107 @@ namespace Microsoft.Diagnostics.Tools.Counters
                         _pauseCmdSet = false;
                     }
                 }
+            }
+
+            try
+            {
+                await pipeline.StopAsync(token).ConfigureAwait(false);
+            }
+            catch (OperationCanceledException)
+            {
+            }
+            catch (PipelineException)
+            {
+            }
+
+            return await _shouldExit.Task.ConfigureAwait(false);
+        }
+
+        void ICountersLogger.Log(ICounterPayload payload)
+        {
+            if (_shouldExit.Task.IsCompleted)
+            {
+                return;
+            }
 
-                if (shouldStopAfterDuration && durationStopwatch.Elapsed >= _duration)
+            lock (this)
+            {
+                // If we are paused, ignore the event.
+                // There's a potential race here between the two tasks but not a huge deal if we miss by one event.
+                _renderer.ToggleStatus(_pauseCmdSet);
+                if (payload is ErrorPayload errorPayload)
                 {
-                    durationStopwatch.Stop();
-                    break;
+                    // Several of the error messages used by Dotnet are specific to the tool;
+                    // the error messages found in errorPayload.ErrorMessage are not tool-specific.
+                    // This replaces the generic error messages with specific ones as-needed.
+                    string errorMessage = string.Empty;
+                    switch (errorPayload.EventType)
+                    {
+                        case EventType.HistogramLimitError:
+                            errorMessage = $"Warning: Histogram tracking limit ({_settings.MaxHistograms}) reached. Not all data is being shown." + Environment.NewLine +
+                "The limit can be changed with --maxHistograms but will use more memory in the target process.";
+                            break;
+                        case EventType.TimeSeriesLimitError:
+                            errorMessage = $"Warning: Time series tracking limit ({_settings.MaxTimeSeries}) reached. Not all data is being shown." + Environment.NewLine +
+                "The limit can be changed with --maxTimeSeries but will use more memory in the target process.";
+                            break;
+                        case EventType.ErrorTargetProcess:
+                        case EventType.MultipleSessionsNotSupportedError:
+                        case EventType.MultipleSessionsConfiguredIncorrectlyError:
+                        case EventType.ObservableInstrumentCallbackError:
+                        default:
+                            errorMessage = errorPayload.ErrorMessage;
+                            break;
+                    }
+
+                    _renderer.SetErrorText(errorMessage);
+
+                    if (errorPayload.EventType.IsSessionStartupError())
+                    {
+                        _shouldExit.TrySetResult(ReturnCode.SessionCreationError);
+                    }
+                    else if (errorPayload.EventType.IsTracingError())
+                    {
+                        _shouldExit.TrySetResult(ReturnCode.TracingError);
+                    }
+                    else if (errorPayload.EventType.IsNonFatalError())
+                    {
+                        // Don't need to exit for NonFatalError
+                    }
+                    else
+                    {
+                        _shouldExit.TrySetResult(ReturnCode.UnknownError);
+                    }
+                }
+                else if (payload is CounterEndedPayload counterEnded)
+                {
+                    _renderer.CounterStopped(counterEnded);
+                }
+                else if (payload.IsMeter)
+                {
+                    MeterInstrumentEventObserved(payload.Provider, payload.Timestamp);
+                    if (payload.EventType.IsValuePublishedEvent())
+                    {
+                        CounterPayloadReceived((CounterPayload)payload);
+                    }
+                }
+                else
+                {
+                    HandleDiagnosticCounter(payload);
                 }
             }
+        }
+
+        public Task PipelineStarted(CancellationToken token)
+        {
+            _renderer.EventPipeSourceConnected();
+            return Task.CompletedTask;
+        }
 
-            StopMonitor();
-            return _shouldExit.Task;
+        public Task PipelineStopped(CancellationToken token)
+        {
+            _renderer.Stop();
+            return Task.CompletedTask;
         }
     }
 }
diff --git a/src/Tools/dotnet-counters/CounterPayload.cs b/src/Tools/dotnet-counters/CounterPayload.cs
deleted file mode 100644 (file)
index a7863bd..0000000
+++ /dev/null
@@ -1,63 +0,0 @@
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-
-using System;
-
-namespace Microsoft.Diagnostics.Tools.Counters
-{
-    public class CounterPayload
-    {
-        public CounterPayload(string providerName, string name, string displayName, string displayUnits, string tags, double value, DateTime timestamp, string type)
-        {
-            ProviderName = providerName;
-            Name = name;
-            Tags = tags;
-            Value = value;
-            Timestamp = timestamp;
-            CounterType = type;
-        }
-
-        public string ProviderName { get; private set; }
-        public string Name { get; private set; }
-        public double Value { get; private set; }
-        public virtual string DisplayName { get; protected set; }
-        public string CounterType { get; private set; }
-        public DateTime Timestamp { get; private set; }
-        public string Tags { get; private set; }
-    }
-
-    internal class GaugePayload : CounterPayload
-    {
-        public GaugePayload(string providerName, string name, string displayName, string displayUnits, string tags, double value, DateTime timestamp) :
-            base(providerName, name, displayName, displayUnits, tags, value, timestamp, "Metric")
-        {
-            // 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 RatePayload : CounterPayload
-    {
-        public RatePayload(string providerName, string name, string displayName, string displayUnits, string tags, double value, double intervalSecs, DateTime timestamp) :
-            base(providerName, name, displayName, displayUnits, tags, value, timestamp, "Rate")
-        {
-            // In case these properties are not provided, set them to appropriate values.
-            string counterName = string.IsNullOrEmpty(displayName) ? name : displayName;
-            string unitsName = string.IsNullOrEmpty(displayUnits) ? "Count" : displayUnits;
-            string intervalName = intervalSecs.ToString() + " sec";
-            DisplayName = $"{counterName} ({unitsName} / {intervalName})";
-        }
-    }
-
-    internal class PercentilePayload : CounterPayload
-    {
-        public PercentilePayload(string providerName, string name, string displayName, string displayUnits, string tags, double val, DateTime timestamp) :
-            base(providerName, name, displayName, displayUnits, tags, val, timestamp, "Metric")
-        {
-            // 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;
-        }
-    }
-}
diff --git a/src/Tools/dotnet-counters/CounterPayloadExtensions.cs b/src/Tools/dotnet-counters/CounterPayloadExtensions.cs
new file mode 100644 (file)
index 0000000..974e054
--- /dev/null
@@ -0,0 +1,28 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using Microsoft.Diagnostics.Monitoring.EventPipe;
+
+namespace Microsoft.Diagnostics.Tools.Counters
+{
+    internal static class CounterPayloadExtensions
+    {
+        public static string GetDisplay(this ICounterPayload counterPayload)
+        {
+            if (!counterPayload.IsMeter)
+            {
+                string unit = counterPayload.Unit == "count" ? "Count" : counterPayload.Unit;
+                if (counterPayload.CounterType == CounterType.Rate)
+                {
+                    return $"{counterPayload.DisplayName} ({unit} / {counterPayload.Series} sec)";
+                }
+                if (!string.IsNullOrEmpty(counterPayload.Unit))
+                {
+                    return $"{counterPayload.DisplayName} ({unit})";
+                }
+            }
+
+            return $"{counterPayload.DisplayName}";
+        }
+    }
+}
index cdf760edb463260a123c49d4f0daa1adcf263288..eb4399de96084f29e467671d0c950dc7b3007bcb 100644 (file)
@@ -5,6 +5,7 @@ using System;
 using System.Globalization;
 using System.IO;
 using System.Text;
+using Microsoft.Diagnostics.Monitoring.EventPipe;
 
 namespace Microsoft.Diagnostics.Tools.Counters.Exporters
 {
@@ -70,11 +71,11 @@ namespace Microsoft.Diagnostics.Tools.Counters.Exporters
 
                 builder
                     .Append(payload.Timestamp.ToString()).Append(',')
-                    .Append(payload.ProviderName).Append(',')
-                    .Append(payload.DisplayName);
-                if (!string.IsNullOrEmpty(payload.Tags))
+                    .Append(payload.Provider).Append(',')
+                    .Append(payload.GetDisplay());
+                if (!string.IsNullOrEmpty(payload.Metadata))
                 {
-                    builder.Append('[').Append(payload.Tags.Replace(',', ';')).Append(']');
+                    builder.Append('[').Append(payload.Metadata.Replace(',', ';')).Append(']');
                 }
                 builder.Append(',')
                     .Append(payload.CounterType).Append(',')
index 76f06931f736475e6d01c3b6aadf79ab7c147ceb..8795ba4e73067f889e13deb4666c1590011f8845 100644 (file)
@@ -5,6 +5,7 @@ using System;
 using System.Collections.Generic;
 using System.Globalization;
 using System.Linq;
+using Microsoft.Diagnostics.Monitoring.EventPipe;
 
 namespace Microsoft.Diagnostics.Tools.Counters.Exporters
 {
@@ -12,7 +13,7 @@ namespace Microsoft.Diagnostics.Tools.Counters.Exporters
     /// ConsoleWriter is an implementation of ICounterRenderer for rendering the counter values in real-time
     /// to the console. This is the renderer for the `dotnet-counters monitor` command.
     /// </summary>
-    public class ConsoleWriter : ICounterRenderer
+    internal class ConsoleWriter : ICounterRenderer
     {
         /// <summary>Information about an observed provider.</summary>
         private class ObservedProvider
@@ -257,9 +258,9 @@ namespace Microsoft.Diagnostics.Tools.Counters.Exporters
                     return;
                 }
 
-                string providerName = payload.ProviderName;
+                string providerName = payload.Provider;
                 string name = payload.Name;
-                string tags = payload.Tags;
+                string tags = payload.Metadata;
 
                 bool redraw = false;
                 if (!_providers.TryGetValue(providerName, out ObservedProvider provider))
@@ -270,7 +271,7 @@ namespace Microsoft.Diagnostics.Tools.Counters.Exporters
 
                 if (!provider.Counters.TryGetValue(name, out ObservedCounter counter))
                 {
-                    string displayName = payload.DisplayName;
+                    string displayName = payload.GetDisplay();
                     provider.Counters[name] = counter = new ObservedCounter(displayName);
                     _maxNameLength = Math.Max(_maxNameLength, displayName.Length);
                     if (tags != null)
@@ -313,9 +314,9 @@ namespace Microsoft.Diagnostics.Tools.Counters.Exporters
         {
             lock (_lock)
             {
-                string providerName = payload.ProviderName;
+                string providerName = payload.Provider;
                 string counterName = payload.Name;
-                string tags = payload.Tags;
+                string tags = payload.Metadata;
 
                 if (!_providers.TryGetValue(providerName, out ObservedProvider provider))
                 {
index d8389b2ec7f96d5d3ee59ecbd369664c81cf3078..2bd4ca254f28671875edf42d8311b0718efa0ecc 100644 (file)
@@ -1,9 +1,11 @@
 // Licensed to the .NET Foundation under one or more agreements.
 // The .NET Foundation licenses this file to you under the MIT license.
 
+using Microsoft.Diagnostics.Monitoring.EventPipe;
+
 namespace Microsoft.Diagnostics.Tools.Counters.Exporters
 {
-    public interface ICounterRenderer
+    internal interface ICounterRenderer
     {
         void Initialize();
         void EventPipeSourceConnected();
index 8abadbbeb76b3ffebc712a6143ecfb6d26b35352..ec571ad7bad80b55e8cfebf1cce61e4488e0724b 100644 (file)
@@ -5,6 +5,7 @@ using System;
 using System.Globalization;
 using System.IO;
 using System.Text;
+using Microsoft.Diagnostics.Monitoring.EventPipe;
 
 namespace Microsoft.Diagnostics.Tools.Counters.Exporters
 {
@@ -72,10 +73,10 @@ namespace Microsoft.Diagnostics.Tools.Counters.Exporters
                 }
                 builder
                     .Append("{ \"timestamp\": \"").Append(DateTime.Now.ToString("u")).Append("\", ")
-                    .Append(" \"provider\": \"").Append(JsonEscape(payload.ProviderName)).Append("\", ")
-                    .Append(" \"name\": \"").Append(JsonEscape(payload.DisplayName)).Append("\", ")
-                    .Append(" \"tags\": \"").Append(JsonEscape(payload.Tags)).Append("\", ")
-                    .Append(" \"counterType\": \"").Append(JsonEscape(payload.CounterType)).Append("\", ")
+                    .Append(" \"provider\": \"").Append(JsonEscape(payload.Provider)).Append("\", ")
+                    .Append(" \"name\": \"").Append(JsonEscape(payload.GetDisplay())).Append("\", ")
+                    .Append(" \"tags\": \"").Append(JsonEscape(payload.Metadata)).Append("\", ")
+                    .Append(" \"counterType\": \"").Append(JsonEscape(payload.CounterType.ToString())).Append("\", ")
                     .Append(" \"value\": ").Append(payload.Value.ToString(CultureInfo.InvariantCulture)).Append(" },");
             }
         }
index ec79a2e6efd89a44d245cc4e55125a9b55752d70..d1fde42e6fdd07a4042fd5b6ad047335c935a01d 100644 (file)
@@ -21,7 +21,7 @@ namespace Microsoft.Diagnostics.Tools.Counters
 
     internal static class Program
     {
-        private delegate Task<int> CollectDelegate(
+        private delegate Task<ReturnCode> CollectDelegate(
             CancellationToken ct,
             List<string> counter_list,
             string counters,
@@ -37,7 +37,7 @@ namespace Microsoft.Diagnostics.Tools.Counters
             int maxTimeSeries,
             TimeSpan duration);
 
-        private delegate Task<int> MonitorDelegate(
+        private delegate Task<ReturnCode> MonitorDelegate(
             CancellationToken ct,
             List<string> counter_list,
             string counters,
index 46ae5946320fc169e627208ea9b1f5277ec74687..e7b4ae37528a40065b0ce8d6ffff6f90069aa193 100644 (file)
@@ -57,11 +57,11 @@ namespace Microsoft.Diagnostics.Monitoring.EventPipe.UnitTests
 
             public IEnumerable<ICounterPayload> Metrics => _metrics.Values;
 
-            public void Log(ICounterPayload metric)
+            public void Log(ICounterPayload payload)
             {
-                string key = CreateKey(metric);
+                string key = CreateKey(payload);
 
-                _metrics[key] = metric;
+                _metrics[key] = payload;
 
                 // Complete the task source if the last expected key was removed.
                 if (_expectedCounters.Remove(key) && _expectedCounters.Count == 0)
index 9634999aa08612b18bc28a8c614078f588051f77..b55ed5a64a64d5a8387e9ce8ef7aac86eb23d7ed 100644 (file)
@@ -477,7 +477,7 @@ namespace Microsoft.Diagnostics.Monitoring.EventPipe.UnitTests
                 // Add some variance between -5 and 5 milliseconds to simulate "real" timestamp
                 _lastTimestamp = _lastTimestamp.Value.AddMilliseconds((10 * _random.NextDouble()) - 5);
 
-                return new CounterPayload(
+                return new EventCounterPayload(
                     _lastTimestamp.Value,
                     EventCounterConstants.RuntimeProviderName,
                     EventCounterConstants.CpuUsageCounterName,
@@ -486,6 +486,7 @@ namespace Microsoft.Diagnostics.Monitoring.EventPipe.UnitTests
                     value,
                     CounterType.Metric,
                     actualInterval,
+                    (int)_intervalSeconds,
                     null);
             }
         }
index 7c283ff4e344284d1080700842dc156b7fb084de..342d36748045430af80e69449b34443a9fc8c668 100644 (file)
@@ -7,6 +7,7 @@ using System.IO;
 using System.Linq;
 using Microsoft.Diagnostics.Tools.Counters;
 using Microsoft.Diagnostics.Tools.Counters.Exporters;
+using Microsoft.Diagnostics.Monitoring.EventPipe;
 using Xunit;
 
 namespace DotnetCounters.UnitTests
@@ -25,7 +26,7 @@ namespace DotnetCounters.UnitTests
             DateTime start = DateTime.Now;
             for (int i = 0; i < 100; i++)
             {
-                exporter.CounterPayloadReceived(new RatePayload("myProvider", "incrementingCounterOne", "Incrementing Counter One", "", "", i, 1, start + TimeSpan.FromSeconds(i)), false);
+                exporter.CounterPayloadReceived(new RatePayload("myProvider", "incrementingCounterOne", "Incrementing Counter One", string.Empty, string.Empty, i, 1, start + TimeSpan.FromSeconds(i)), false);
             }
             exporter.Stop();
 
@@ -63,7 +64,7 @@ namespace DotnetCounters.UnitTests
             DateTime start = DateTime.Now;
             for (int i = 0; i < 10; i++)
             {
-                exporter.CounterPayloadReceived(new GaugePayload("myProvider", "counterOne", "Counter One", "", "", i, start + TimeSpan.FromSeconds(i)), false);
+                exporter.CounterPayloadReceived(new GaugePayload("myProvider", "counterOne", "Counter One", string.Empty, null, i, start + TimeSpan.FromSeconds(i)), false);
             }
             exporter.Stop();
 
@@ -101,7 +102,7 @@ namespace DotnetCounters.UnitTests
             DateTime start = DateTime.Now;
             for (int i = 0; i < 100; i++)
             {
-                exporter.CounterPayloadReceived(new RatePayload("myProvider", "incrementingCounterOne", "Incrementing Counter One", "", "", i, 60, start + TimeSpan.FromSeconds(i)), false);
+                exporter.CounterPayloadReceived(new RatePayload("myProvider", "incrementingCounterOne", "Incrementing Counter One", string.Empty, null, i, 60, start + TimeSpan.FromSeconds(i)), false);
             }
             exporter.Stop();
 
@@ -139,7 +140,7 @@ namespace DotnetCounters.UnitTests
             DateTime start = DateTime.Now;
             for (int i = 0; i < 100; i++)
             {
-                exporter.CounterPayloadReceived(new RatePayload("myProvider", "allocRateGen", "Allocation Rate Gen", "MB", "", i, 60, start + TimeSpan.FromSeconds(i)), false);
+                exporter.CounterPayloadReceived(new RatePayload("myProvider", "allocRateGen", "Allocation Rate Gen", "MB", string.Empty, i, 60, start + TimeSpan.FromSeconds(i)), false);
             }
             exporter.Stop();
 
index 379f699be391dec561729e45a307224b4efbaaf0..d4daadbbcb78237b7d30cf86aea83b6fea1a8d37 100644 (file)
@@ -6,7 +6,9 @@ using System.IO;
 using Microsoft.Diagnostics.Tools.Counters;
 using Microsoft.Diagnostics.Tools.Counters.Exporters;
 using Newtonsoft.Json;
+using Microsoft.Diagnostics.Monitoring.EventPipe;
 using Xunit;
+using System.Collections.Generic;
 
 #pragma warning disable CA1507 // Use nameof to express symbol names
 
@@ -26,7 +28,7 @@ namespace DotnetCounters.UnitTests
             DateTime start = DateTime.Now;
             for (int i = 0; i < 10; i++)
             {
-                exporter.CounterPayloadReceived(new RatePayload("myProvider", "incrementingCounterOne", "Incrementing Counter One", "", "", 1, 1, start + TimeSpan.FromSeconds(i)), false);
+                exporter.CounterPayloadReceived(new RatePayload("myProvider", "incrementingCounterOne", "Incrementing Counter One", string.Empty, string.Empty, 1, 1, start + TimeSpan.FromSeconds(i)), false);
             }
             exporter.Stop();
 
@@ -57,7 +59,7 @@ namespace DotnetCounters.UnitTests
             DateTime start = DateTime.Now;
             for (int i = 0; i < 10; i++)
             {
-                exporter.CounterPayloadReceived(new GaugePayload("myProvider", "counterOne", "Counter One", "", "", 1, start + TimeSpan.FromSeconds(i)), false);
+                exporter.CounterPayloadReceived(new GaugePayload("myProvider", "counterOne", "Counter One", string.Empty, string.Empty, 1, start + TimeSpan.FromSeconds(i)), false);
             }
             exporter.Stop();
 
@@ -88,7 +90,7 @@ namespace DotnetCounters.UnitTests
             DateTime start = DateTime.Now;
             for (int i = 0; i < 20; i++)
             {
-                exporter.CounterPayloadReceived(new GaugePayload("myProvider", "heapSize", "Heap Size", "MB", "", i, start + TimeSpan.FromSeconds(i)), false);
+                exporter.CounterPayloadReceived(new GaugePayload("myProvider", "heapSize", "Heap Size", "MB", string.Empty, i, start + TimeSpan.FromSeconds(i)), false);
             }
             exporter.Stop();
 
@@ -122,7 +124,7 @@ namespace DotnetCounters.UnitTests
             DateTime start = DateTime.Now;
             for (int i = 0; i < 20; i++)
             {
-                exporter.CounterPayloadReceived(new RatePayload("myProvider", "heapSize", "Heap Size", "MB", "", 0, 60, start + TimeSpan.FromSeconds(i)), false);
+                exporter.CounterPayloadReceived(new RatePayload("myProvider", "heapSize", "Heap Size", "MB", string.Empty, 0, 60, start + TimeSpan.FromSeconds(i)), false);
             }
             exporter.Stop();
 
@@ -210,7 +212,7 @@ namespace DotnetCounters.UnitTests
             DateTime start = DateTime.Now;
             for (int i = 0; i < 10; i++)
             {
-                exporter.CounterPayloadReceived(new PercentilePayload("myProvider", "counterOne", "Counter One", "", "f=abc,Percentile=50", 1, start + TimeSpan.FromSeconds(i)), false);
+                exporter.CounterPayloadReceived(new PercentilePayload("myProvider", "counterOne", "Counter One", "", "f=abc,Percentile=50", 1, start + TimeSpan.FromSeconds(1)), false);
             }
             exporter.Stop();