From ae3994eb131fe9276e9864d3e558795a62f5778d Mon Sep 17 00:00:00 2001 From: Noah Falk Date: Tue, 20 Aug 2024 06:48:00 -0700 Subject: [PATCH] dotnet-counters: Support EventCounter prefix + remove known data (#4871) - dotnet-counters now supports a prefix 'EventCounters\\' in front of provider name to force the tool to show EventCounter data when both a Meter and an EventSource have the same name. Without the prefix the Meter data would be shown by default. This is intended to help developers who are migrating between the two and want to see the old data. - dotnet-counters list command no longer shows a hard-coded list of provider and counter names which was already out-of-date. Instead the command now directs users to the online docs. This should be less work for us and more accurate information for users. - dotnet-counters console renderer now sorts all providers alphabetically when displaying them. Previously providers that were in the known provider list had special treatment and got sorted first. Several console exporter tests had to be updated because of the sort order change. - JSON and CSV monitoring tests are re-enabled and use the new EventCounters feature to keep their behavior stable across runtimes given that 9.0 has a System.Runtime Meter and earlier runtimes do not. We already have other tests that handle monitoring Meters and there are tests in the runtime repo for the System.Runtime Meter data. - CounterSet was deleted and ConfigureCounters() was refactored to use List\ instead. CounterSet and EventPipeCounterGroup were already similar abstractions so there wasn't much value in maintaining both of them. EventPipeCounterGroup also supports marking a provider as EventCounter only which was needed for EventCounter prefix support. The counter list parsing tests were updated to validate the same results using the EventPipeCounterGroup API instead. --- src/Tools/dotnet-counters/CounterMonitor.cs | 81 +++++--- src/Tools/dotnet-counters/CounterSet.cs | 70 ------- .../Exporters/ConsoleWriter.cs | 4 +- src/Tools/dotnet-counters/KnownData.cs | 175 ------------------ src/Tools/dotnet-counters/Program.cs | 31 +--- .../dotnet-counters/ConsoleExporterTests.cs | 25 +-- .../CounterMonitorPayloadTests.cs | 20 +- .../dotnet-counters/CounterMonitorTests.cs | 91 +++++---- .../dotnet-counters/KnownProviderTests.cs | 45 ----- 9 files changed, 148 insertions(+), 394 deletions(-) delete mode 100644 src/Tools/dotnet-counters/CounterSet.cs delete mode 100644 src/Tools/dotnet-counters/KnownData.cs delete mode 100644 src/tests/dotnet-counters/KnownProviderTests.cs diff --git a/src/Tools/dotnet-counters/CounterMonitor.cs b/src/Tools/dotnet-counters/CounterMonitor.cs index 5f1ff39d6..b57bfec95 100644 --- a/src/Tools/dotnet-counters/CounterMonitor.cs +++ b/src/Tools/dotnet-counters/CounterMonitor.cs @@ -23,8 +23,9 @@ namespace Microsoft.Diagnostics.Tools.Counters internal class CounterMonitor : ICountersLogger { private const int BufferDelaySecs = 1; + private const string EventCountersProviderPrefix = "EventCounters\\"; private int _processId; - private CounterSet _counterList; + private List _counterList; private IConsole _console; private ICounterRenderer _renderer; private string _output; @@ -66,6 +67,13 @@ namespace Microsoft.Diagnostics.Tools.Counters private void HandleDiagnosticCounter(ICounterPayload payload) { + // if EventCounters were explicitly requested on the command-line then show them always + if (_counterList.Where(group => group.ProviderName == payload.CounterMetadata.ProviderName && group.Type == CounterGroupType.EventCounter).Any()) + { + CounterPayloadReceived((CounterPayload)payload); + return; + } + // init providerEventState if this is the first time we've seen an event from this provider if (!_providerEventStates.TryGetValue(payload.CounterMetadata.ProviderName, out ProviderEventState providerState)) { @@ -206,7 +214,7 @@ namespace Microsoft.Diagnostics.Tools.Counters _settings.MaxTimeSeries = maxTimeSeries; _settings.CounterIntervalSeconds = refreshInterval; _settings.ResumeRuntime = resumeRuntime; - _settings.CounterGroups = GetEventPipeProviders(); + _settings.CounterGroups = _counterList.ToArray(); _settings.UseCounterRateAndValuePayloads = true; bool useSharedSession = false; @@ -290,7 +298,7 @@ namespace Microsoft.Diagnostics.Tools.Counters _settings.MaxTimeSeries = maxTimeSeries; _settings.CounterIntervalSeconds = refreshInterval; _settings.ResumeRuntime = resumeRuntime; - _settings.CounterGroups = GetEventPipeProviders(); + _settings.CounterGroups = _counterList.ToArray(); _output = output; _diagnosticsClient = holder.Client; if (_output.Length == 0) @@ -354,9 +362,9 @@ namespace Microsoft.Diagnostics.Tools.Counters } } - internal CounterSet ConfigureCounters(string commaSeparatedProviderListText, List providerList) + internal List ConfigureCounters(string commaSeparatedProviderListText, List providerList) { - CounterSet counters = new(); + List counters = new(); try { if (commaSeparatedProviderListText != null) @@ -388,24 +396,24 @@ namespace Microsoft.Diagnostics.Tools.Counters } } - if (counters.IsEmpty) + if (counters.Count == 0) { _console.Out.WriteLine($"--counters is unspecified. Monitoring System.Runtime counters by default."); - counters.AddAllProviderCounters("System.Runtime"); + ParseCounterProvider("System.Runtime", counters); } return counters; } // parses a comma separated list of providers - internal static CounterSet ParseProviderList(string providerListText) + internal static List ParseProviderList(string providerListText) { - CounterSet set = new(); + List set = new(); ParseProviderList(providerListText, set); return set; } // parses a comma separated list of providers - internal static void ParseProviderList(string providerListText, CounterSet counters) + internal static void ParseProviderList(string providerListText, List counters) { bool inParen = false; int startIdx = -1; @@ -457,7 +465,7 @@ namespace Microsoft.Diagnostics.Tools.Counters // System.Runtime // System.Runtime[exception-count] // System.Runtime[exception-count,cpu-usage] - private static void ParseCounterProvider(string providerText, CounterSet counters) + private static void ParseCounterProvider(string providerText, List counters) { string[] tokens = providerText.Split('['); if (tokens.Length == 0) @@ -468,12 +476,24 @@ namespace Microsoft.Diagnostics.Tools.Counters { throw new FormatException("Expected at most one '[' in counter_provider"); } + string providerName = tokens[0]; - if (tokens.Length == 1) + CounterGroupType groupType = CounterGroupType.All; + // EventCounters\ is a special prefix for a provider that marks it as an EventCounter provider. + if (providerName.StartsWith(EventCountersProviderPrefix)) { - counters.AddAllProviderCounters(providerName); // Only a provider name was specified + providerName = providerName.Substring(EventCountersProviderPrefix.Length); + groupType = CounterGroupType.EventCounter; } - else + + // An empty set of counters means all counters are enabled. + string[] enabledCounters = Array.Empty(); + EventPipeCounterGroup preExistingGroup = counters.FirstOrDefault(group => group.ProviderName == providerName); + if (preExistingGroup != null && preExistingGroup.Type != groupType) + { + throw new FormatException("Using the same provider name with and without the EventCounters\\ prefix in the counter list is not supported."); + } + if (tokens.Length == 2) { string counterNames = tokens[1]; if (!counterNames.EndsWith(']')) @@ -487,17 +507,34 @@ namespace Microsoft.Diagnostics.Tools.Counters throw new FormatException("Unexpected characters after closing ']' in counter_provider"); } } - string[] enabledCounters = counterNames.Substring(0, counterNames.Length - 1).Split(',', StringSplitOptions.RemoveEmptyEntries); - counters.AddProviderCounters(providerName, enabledCounters); + enabledCounters = counterNames.Substring(0, counterNames.Length - 1).Split(',', StringSplitOptions.RemoveEmptyEntries); } - } - private EventPipeCounterGroup[] GetEventPipeProviders() => - _counterList.Providers.Select(provider => new EventPipeCounterGroup + // haven't seen this provider yet so add it + if (preExistingGroup == null) { - ProviderName = provider, - CounterNames = _counterList.GetCounters(provider).ToArray() - }).ToArray(); + counters.Add(new EventPipeCounterGroup + { + ProviderName = providerName, + CounterNames = enabledCounters, + Type = groupType + }); + } + // we've already seen the provider so merge the configurations + else + { + // If the previous config had some specific counters and the new one also has specific counters then merge them. + // Otherwise one of the two requested all counters so the union is also all counters. + if (preExistingGroup.CounterNames.Length == 0 || enabledCounters.Length == 0) + { + preExistingGroup.CounterNames = Array.Empty(); + } + else + { + preExistingGroup.CounterNames = preExistingGroup.CounterNames.Union(enabledCounters).ToArray(); + } + } + } private async Task Start(MetricsPipeline pipeline, CancellationToken token) { diff --git a/src/Tools/dotnet-counters/CounterSet.cs b/src/Tools/dotnet-counters/CounterSet.cs deleted file mode 100644 index 26483af51..000000000 --- a/src/Tools/dotnet-counters/CounterSet.cs +++ /dev/null @@ -1,70 +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; -using System.Collections.Generic; -using System.Linq; - -namespace Microsoft.Diagnostics.Tools.Counters -{ - public class CounterSet - { - // a mapping from provider to a list of counters that should be enabled - // an empty List means all counters are enabled - private readonly Dictionary> _providerCounters; - - public CounterSet() - { - _providerCounters = new Dictionary>(); - } - - public bool IsEmpty => _providerCounters.Count == 0; - - public IEnumerable Providers => _providerCounters.Keys; - - public bool IncludesAllCounters(string providerName) - { - return _providerCounters.TryGetValue(providerName, out List enabledCounters) && enabledCounters.Count == 0; - } - - public IEnumerable GetCounters(string providerName) - { - if (!_providerCounters.TryGetValue(providerName, out List enabledCounters)) - { - return Array.Empty(); - } - return enabledCounters; - } - - // Called when we want to enable all counters under a provider name. - public void AddAllProviderCounters(string providerName) - { - _providerCounters[providerName] = new List(); - } - - public void AddProviderCounters(string providerName, string[] counters) - { - if (!_providerCounters.TryGetValue(providerName, out List enabledCounters)) - { - enabledCounters = new List(counters.Distinct()); - _providerCounters.Add(providerName, enabledCounters); - } - else if (enabledCounters.Count != 0) // empty list means all counters are enabled already - { - foreach (string counter in counters) - { - if (!enabledCounters.Contains(counter)) - { - enabledCounters.Add(counter); - } - } - } - } - - public bool Contains(string providerName, string counterName) - { - return _providerCounters.TryGetValue(providerName, out List counters) && - (counters.Count == 0 || counters.Contains(counterName)); - } - } -} diff --git a/src/Tools/dotnet-counters/Exporters/ConsoleWriter.cs b/src/Tools/dotnet-counters/Exporters/ConsoleWriter.cs index 94df5e4dc..0acab0886 100644 --- a/src/Tools/dotnet-counters/Exporters/ConsoleWriter.cs +++ b/src/Tools/dotnet-counters/Exporters/ConsoleWriter.cs @@ -21,12 +21,10 @@ namespace Microsoft.Diagnostics.Tools.Counters.Exporters public ObservedProvider(string name) { Name = name; - KnownData.TryGetProvider(name, out KnownProvider); } public string Name { get; } // Name of the category. public Dictionary Counters { get; } = new Dictionary(); // Counters in this category. - public readonly CounterProvider KnownProvider; } /// Information about an observed counter. @@ -146,7 +144,7 @@ namespace Microsoft.Diagnostics.Tools.Counters.Exporters if (RenderRow(ref row) && // Blank line. RenderTableRow(ref row, "Name", "Current Value", "Last Delta")) // Table header { - foreach (ObservedProvider provider in _providers.Values.OrderBy(p => p.KnownProvider == null).ThenBy(p => p.Name)) // Known providers first. + foreach (ObservedProvider provider in _providers.Values.OrderBy(p => p.Name)) { if (!RenderTableRow(ref row, $"[{provider.Name}]")) { diff --git a/src/Tools/dotnet-counters/KnownData.cs b/src/Tools/dotnet-counters/KnownData.cs deleted file mode 100644 index 4658d6ff6..000000000 --- a/src/Tools/dotnet-counters/KnownData.cs +++ /dev/null @@ -1,175 +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; -using System.Collections.Generic; -using System.Linq; - -namespace Microsoft.Diagnostics.Tools.Counters -{ - internal static class KnownData - { - private const string maxVersion = "8.0"; - - internal static readonly string[] s_AllVersions = new[] { net30, net31, net50, net60, net70, net80 }; - private static readonly string[] s_StartingNet5 = new[] { net50, net60, net70, net80 }; - private static readonly string[] s_StartingNet6 = new[] { net60, net70, net80 }; - private static readonly string[] s_StartingNet7 = new[] { net70, net80 }; - - private static readonly IReadOnlyDictionary _knownProviders = - CreateKnownProviders(maxVersion).ToDictionary(p => p.Name, StringComparer.OrdinalIgnoreCase); - - private const string net80 = "8.0"; - private const string net70 = "7.0"; - private const string net60 = "6.0"; - private const string net50 = "5.0"; - private const string net31 = "3.1"; - private const string net30 = "3.0"; - - private static IEnumerable CreateKnownProviders(string runtimeVersion) - { - yield return new CounterProvider( - "System.Runtime", // Name - "A default set of performance counters provided by the .NET runtime.", // Description - "0xffffffff", // Keywords - "5", // Level - new[] { // Counters - new CounterProfile{ Name="cpu-usage", Description="The percent of process' CPU usage relative to all of the system CPU resources [0-100]", SupportedVersions=s_AllVersions }, - new CounterProfile{ Name="working-set", Description="Amount of working set used by the process (MB)", SupportedVersions=s_AllVersions }, - new CounterProfile{ Name="gc-heap-size", Description="Total heap size reported by the GC (MB)", SupportedVersions=s_AllVersions }, - new CounterProfile{ Name="gen-0-gc-count", Description="Number of Gen 0 GCs between update intervals", SupportedVersions=s_AllVersions }, - new CounterProfile{ Name="gen-1-gc-count", Description="Number of Gen 1 GCs between update intervals", SupportedVersions=s_AllVersions }, - new CounterProfile{ Name="gen-2-gc-count", Description="Number of Gen 2 GCs between update intervals", SupportedVersions=s_AllVersions }, - new CounterProfile{ Name="time-in-gc", Description="% time in GC since the last GC", SupportedVersions=s_AllVersions }, - new CounterProfile{ Name="gen-0-size", Description="Gen 0 Heap Size", SupportedVersions=s_AllVersions }, - new CounterProfile{ Name="gen-1-size", Description="Gen 1 Heap Size", SupportedVersions=s_AllVersions }, - new CounterProfile{ Name="gen-2-size", Description="Gen 2 Heap Size", SupportedVersions=s_AllVersions }, - new CounterProfile{ Name="loh-size", Description="LOH Size", SupportedVersions=s_AllVersions }, - new CounterProfile{ Name="poh-size", Description="POH (Pinned Object Heap) Size", SupportedVersions=s_StartingNet5 }, - new CounterProfile{ Name="alloc-rate", Description="Number of bytes allocated in the managed heap between update intervals", SupportedVersions=s_AllVersions }, - new CounterProfile{ Name="gc-fragmentation", Description="GC Heap Fragmentation", SupportedVersions=s_StartingNet5 }, - new CounterProfile{ Name="assembly-count", Description="Number of Assemblies Loaded", SupportedVersions=s_AllVersions }, - new CounterProfile{ Name="exception-count", Description="Number of Exceptions / sec", SupportedVersions=s_AllVersions }, - new CounterProfile{ Name="threadpool-thread-count", Description="Number of ThreadPool Threads", SupportedVersions=s_AllVersions }, - new CounterProfile{ Name="monitor-lock-contention-count", Description="Number of times there were contention when trying to take the monitor lock between update intervals", SupportedVersions=s_AllVersions }, - new CounterProfile{ Name="threadpool-queue-length", Description="ThreadPool Work Items Queue Length", SupportedVersions=s_AllVersions }, - new CounterProfile{ Name="threadpool-completed-items-count", Description="ThreadPool Completed Work Items Count", SupportedVersions=s_AllVersions }, - new CounterProfile{ Name="active-timer-count", Description="Number of timers that are currently active", SupportedVersions=s_AllVersions }, - new CounterProfile{ Name="il-bytes-jitted", Description="Total IL bytes jitted", SupportedVersions=s_StartingNet5 }, - new CounterProfile{ Name="methods-jitted-count", Description="Number of methods jitted", SupportedVersions=s_StartingNet5 }, - new CounterProfile{ Name="gc-committed", Description="Size of committed memory by the GC (MB)", SupportedVersions=s_StartingNet6 } - }, - runtimeVersion // RuntimeVersion - ); - yield return new CounterProvider( - "Microsoft.AspNetCore.Hosting", // Name - "A set of performance counters provided by ASP.NET Core.", // Description - "0x0", // Keywords - "4", // Level - new[] { // Counters - new CounterProfile{ Name="requests-per-second", Description="Number of requests between update intervals", SupportedVersions=s_AllVersions }, - new CounterProfile{ Name="total-requests", Description="Total number of requests", SupportedVersions=s_AllVersions }, - new CounterProfile{ Name="current-requests", Description="Current number of requests", SupportedVersions=s_AllVersions }, - new CounterProfile{ Name="failed-requests", Description="Failed number of requests", SupportedVersions=s_AllVersions }, - }, - runtimeVersion - ); - yield return new CounterProvider( - "Microsoft-AspNetCore-Server-Kestrel", // Name - "A set of performance counters provided by Kestrel.", // Description - "0x0", // Keywords - "4", // Level - new[] { - new CounterProfile{ Name="connections-per-second", Description="Number of connections between update intervals", SupportedVersions=s_StartingNet5 }, - new CounterProfile{ Name="total-connections", Description="Total Connections", SupportedVersions=s_StartingNet5 }, - new CounterProfile{ Name="tls-handshakes-per-second", Description="Number of TLS Handshakes made between update intervals", SupportedVersions=s_StartingNet5 }, - new CounterProfile{ Name="total-tls-handshakes", Description="Total number of TLS handshakes made", SupportedVersions=s_StartingNet5 }, - new CounterProfile{ Name="current-tls-handshakes", Description="Number of currently active TLS handshakes", SupportedVersions=s_StartingNet5 }, - new CounterProfile{ Name="failed-tls-handshakes", Description="Total number of failed TLS handshakes", SupportedVersions=s_StartingNet5 }, - new CounterProfile{ Name="current-connections", Description="Number of current connections", SupportedVersions=s_StartingNet5 }, - new CounterProfile{ Name="connection-queue-length", Description="Length of Kestrel Connection Queue", SupportedVersions=s_StartingNet5 }, - new CounterProfile{ Name="request-queue-length", Description="Length total HTTP request queue", SupportedVersions=s_StartingNet5 }, - }, - runtimeVersion - ); - yield return new CounterProvider( - "System.Net.Http", - "A set of performance counters for System.Net.Http", - "0x0", // Keywords - "1", // Level - new[] { - new CounterProfile{ Name="requests-started", Description="Total Requests Started", SupportedVersions=s_StartingNet5 }, - new CounterProfile{ Name="requests-started-rate", Description="Number of Requests Started between update intervals", SupportedVersions=s_StartingNet5 }, - new CounterProfile{ Name="requests-aborted", Description="Total Requests Aborted", SupportedVersions=s_StartingNet5 }, - new CounterProfile{ Name="requests-aborted-rate", Description="Number of Requests Aborted between update intervals", SupportedVersions=s_StartingNet5 }, - new CounterProfile{ Name="current-requests", Description="Current Requests", SupportedVersions=s_StartingNet5 }, - new CounterProfile{ Name="http11-connections-current-total", Description="Current number of HTTP 1.1 connections", SupportedVersions=s_StartingNet5 }, - new CounterProfile{ Name="http20-connections-current-total", Description="Current number of HTTP 2.0 connections", SupportedVersions=s_StartingNet5 }, - new CounterProfile{ Name="http30-connections-current-total", Description="Current number of HTTP 3.0 connections", SupportedVersions=s_StartingNet7 }, - new CounterProfile{ Name="http11-requests-queue-duration", Description="Average duration of the time HTTP 1.1 requests spent in the request queue", SupportedVersions=s_StartingNet5 }, - new CounterProfile{ Name="http20-requests-queue-duration", Description="Average duration of the time HTTP 2.0 requests spent in the request queue", SupportedVersions=s_StartingNet5 }, - new CounterProfile{ Name="http30-requests-queue-duration", Description="Average duration of the time HTTP 3.0 requests spent in the request queue", SupportedVersions=s_StartingNet7 }, - }, - runtimeVersion - ); - yield return new CounterProvider( - "System.Net.NameResolution", - "A set of performance counters for DNS lookups", - "0x0", - "1", - new[] { - new CounterProfile{ Name="dns-lookups-requested", Description="The number of DNS lookups requested since the process started", SupportedVersions=s_StartingNet5 }, - new CounterProfile{ Name="dns-lookups-duration", Description="Average DNS Lookup Duration", SupportedVersions=s_StartingNet5 }, - new CounterProfile{ Name="current-dns-lookups", Description="The current number of DNS lookups that have started but not yet completed", SupportedVersions=s_StartingNet6 }, - }, - runtimeVersion - ); - yield return new CounterProvider( - "System.Net.Security", - "A set of performance counters for TLS", - "0x0", - "1", - new[] { - new CounterProfile{ Name="tls-handshake-rate", Description="The number of TLS handshakes completed per update interval", SupportedVersions=s_StartingNet5 }, - new CounterProfile{ Name="total-tls-handshakes", Description="The total number of TLS handshakes completed since the process started", SupportedVersions=s_StartingNet5 }, - new CounterProfile{ Name="current-tls-handshakes", Description="The current number of TLS handshakes that have started but not yet completed", SupportedVersions=s_StartingNet5 }, - new CounterProfile{ Name="failed-tls-handshakes", Description="The total number of TLS handshakes failed since the process started", SupportedVersions=s_StartingNet5 }, - new CounterProfile{ Name="all-tls-sessions-open", Description="The number of active all TLS sessions", SupportedVersions=s_StartingNet5 }, - new CounterProfile{ Name="tls10-sessions-open", Description="The number of active TLS 1.0 sessions", SupportedVersions=s_StartingNet5 }, - new CounterProfile{ Name="tls11-sessions-open", Description="The number of active TLS 1.1 sessions", SupportedVersions=s_StartingNet5 }, - new CounterProfile{ Name="tls12-sessions-open", Description="The number of active TLS 1.2 sessions", SupportedVersions=s_StartingNet5 }, - new CounterProfile{ Name="tls13-sessions-open", Description="The number of active TLS 1.3 sessions", SupportedVersions=s_StartingNet5 }, - new CounterProfile{ Name="all-tls-handshake-duration", Description="The average duration of all TLS handshakes", SupportedVersions=s_StartingNet5 }, - new CounterProfile{ Name="tls10-handshake-duration", Description="The average duration of TLS 1.0 handshakes", SupportedVersions=s_StartingNet5 }, - new CounterProfile{ Name="tls11-handshake-duration", Description="The average duration of TLS 1.1 handshakes", SupportedVersions=s_StartingNet5 }, - new CounterProfile{ Name="tls12-handshake-duration", Description="The average duration of TLS 1.2 handshakes", SupportedVersions=s_StartingNet5 }, - new CounterProfile{ Name="tls13-handshake-duration", Description="The average duration of TLS 1.3 handshakes", SupportedVersions=s_StartingNet5 }, - }, - runtimeVersion - ); - yield return new CounterProvider( - "System.Net.Sockets", - "A set of performance counters for System.Net.Sockets", - "0x0", - "1", - new[] { - new CounterProfile{ Name="outgoing-connections-established", Description="The total number of outgoing connections established since the process started", SupportedVersions=s_StartingNet5 }, - new CounterProfile{ Name="incoming-connections-established", Description="The total number of incoming connections established since the process started", SupportedVersions=s_StartingNet5 }, - new CounterProfile{ Name="current-outgoing-connect-attempts", Description="The current number of outgoing connect attempts that have started but not yet completed", SupportedVersions=s_StartingNet7 }, - new CounterProfile{ Name="bytes-received", Description="The total number of bytes received since the process started", SupportedVersions=s_StartingNet5 }, - new CounterProfile{ Name="bytes-sent", Description="The total number of bytes sent since the process started", SupportedVersions=s_StartingNet5 }, - new CounterProfile{ Name="datagrams-received", Description="The total number of datagrams received since the process started", SupportedVersions=s_StartingNet5 }, - new CounterProfile{ Name="datagrams-sent", Description="The total number of datagrams sent since the process started", SupportedVersions=s_StartingNet5 }, - }, - runtimeVersion - ); - } - - public static IReadOnlyList GetAllProviders(string version) - { - return CreateKnownProviders(version).Where(p => p.Counters.Count > 0).ToDictionary(p => p.Name, StringComparer.OrdinalIgnoreCase).Values.ToList(); - } - - public static bool TryGetProvider(string providerName, out CounterProvider provider) => _knownProviders.TryGetValue(providerName, out provider); - } -} diff --git a/src/Tools/dotnet-counters/Program.cs b/src/Tools/dotnet-counters/Program.cs index 4870e3faf..b9079cefb 100644 --- a/src/Tools/dotnet-counters/Program.cs +++ b/src/Tools/dotnet-counters/Program.cs @@ -138,7 +138,12 @@ namespace Microsoft.Diagnostics.Tools.Counters private static Option CounterOption() => new( alias: "--counters", - description: "A comma-separated list of counter providers. Counter providers can be specified as or [comma_separated_counter_names]. If the provider_name is used without qualifying counter_names then all counters will be shown. For example \"System.Runtime[cpu-usage,working-set],Microsoft.AspNetCore.Hosting\" includes the cpu-usage and working-set counters from the System.Runtime provider and all the counters from the Microsoft.AspNetCore.Hosting provider. To discover provider and counter names, use the list command.") + description: "A comma-separated list of counter providers. Counter providers can be specified as or [comma_separated_counter_names]. If the provider_name" + + " is used without qualifying counter_names then all counters will be shown. For example \"System.Runtime[dotnet.assembly.count,dotnet.gc.pause.time],Microsoft.AspNetCore.Hosting\"" + + " includes the dotnet.assembly.count and dotnet.gc.pause.time counters from the System.Runtime provider and all the counters from the Microsoft.AspNetCore.Hosting provider. Provider" + + " names can either refer to the name of a Meter for the System.Diagnostics.Metrics API or the name of an EventSource for the EventCounters API. If the monitored application has both" + + " a Meter and an EventSource with the same name, the Meter is automatically preferred. Use the prefix \'EventCounters\\\' in front of a provider name to only show the EventCounters." + + " To discover well-known provider and counter names, please visit https://learn.microsoft.com/dotnet/core/diagnostics/built-in-metrics.") { Argument = new Argument(name: "counters") }; @@ -216,30 +221,10 @@ namespace Microsoft.Diagnostics.Tools.Counters " This is useful to monitor the rate of change for a metric.") { }; - private static readonly string[] s_SupportedRuntimeVersions = KnownData.s_AllVersions; - public static int List(IConsole console, string runtimeVersion) { - if (!s_SupportedRuntimeVersions.Contains(runtimeVersion)) - { - Console.WriteLine($"{runtimeVersion} is not a supported version string or a supported runtime version."); - Console.WriteLine("Supported version strings: 3.0, 3.1, 5.0, 6.0, 7.0, 8.0"); - return 0; - } - IReadOnlyList profiles = KnownData.GetAllProviders(runtimeVersion); - int maxNameLength = profiles.Max(p => p.Name.Length); - Console.WriteLine($"Showing well-known counters for .NET (Core) version {runtimeVersion} only. Specific processes may support additional counters."); - foreach (CounterProvider profile in profiles) - { - IReadOnlyList counters = profile.GetAllCounters(); - int maxCounterNameLength = counters.Max(c => c.Name.Length); - Console.WriteLine($"{profile.Name.PadRight(maxNameLength)}"); - foreach (CounterProfile counter in profile.Counters.Values) - { - Console.WriteLine($" {counter.Name.PadRight(maxCounterNameLength)} \t\t {counter.Description}"); - } - Console.WriteLine(""); - } + Console.WriteLine("Counter information has been moved to the online .NET documentation."); + Console.WriteLine("Please visit https://learn.microsoft.com/dotnet/core/diagnostics/built-in-metrics."); return 1; } diff --git a/src/tests/dotnet-counters/ConsoleExporterTests.cs b/src/tests/dotnet-counters/ConsoleExporterTests.cs index 1f671c39b..4fdee4bef 100644 --- a/src/tests/dotnet-counters/ConsoleExporterTests.cs +++ b/src/tests/dotnet-counters/ConsoleExporterTests.cs @@ -70,10 +70,11 @@ namespace DotnetCounters.UnitTests " Status: Running", "", "Name Current Value", - "[System.Runtime]", - " Allocation Rate (B / 1 sec) 1,731", "[Provider2]", - " CounterXyz (Doodads) 0.076"); + " CounterXyz (Doodads) 0.076", + "[System.Runtime]", + " Allocation Rate (B / 1 sec) 1,731"); + } [Fact] @@ -339,8 +340,6 @@ namespace DotnetCounters.UnitTests " Status: Running", "", "Name Current Value Last Delta", - "[System.Runtime]", - " Allocation Rate (B / 1 sec) 1,731", "[Provider1]", " Counter1 ({widget} / 1 sec)", " color", @@ -349,7 +348,9 @@ namespace DotnetCounters.UnitTests " Counter2 ({widget} / 1 sec)", " size temp", " 1 14", - " hot 160"); + " hot 160", + "[System.Runtime]", + " Allocation Rate (B / 1 sec) 1,731"); } [Fact] @@ -368,8 +369,6 @@ namespace DotnetCounters.UnitTests " Status: Running", "", "Name Current Value Last Delta", - "[System.Runtime]", - " Allocation Rate (B / 1 sec) 1,731", "[Provider1]", " Counter1 ({widget} / 1 sec)", " color", @@ -378,7 +377,9 @@ namespace DotnetCounters.UnitTests " Counter2 ({widget} / 1 sec)", " size temp", " 1 14", - " hot 160"); + " hot 160", + "[System.Runtime]", + " Allocation Rate (B / 1 sec) 1,731"); exporter.CounterPayloadReceived(CreateIncrementingEventCounter("System.Runtime", "Allocation Rate", "B", 1732), false); exporter.CounterPayloadReceived(CreateMeterCounterPreNet8("Provider1", "Counter1", "{widget}", "color=red", 0.2), false); @@ -388,8 +389,6 @@ namespace DotnetCounters.UnitTests " Status: Running", "", "Name Current Value Last Delta", - "[System.Runtime]", - " Allocation Rate (B / 1 sec) 1,732 1", "[Provider1]", " Counter1 ({widget} / 1 sec)", " color", @@ -398,7 +397,9 @@ namespace DotnetCounters.UnitTests " Counter2 ({widget} / 1 sec)", " size temp", " 1 10 -4", - " hot 160"); + " hot 160", + "[System.Runtime]", + " Allocation Rate (B / 1 sec) 1,732 1"); } // Starting in .NET 8 MetricsEventSource, Meter counter instruments report both rate of change and diff --git a/src/tests/dotnet-counters/CounterMonitorPayloadTests.cs b/src/tests/dotnet-counters/CounterMonitorPayloadTests.cs index 5194152c8..93ba9c0b1 100644 --- a/src/tests/dotnet-counters/CounterMonitorPayloadTests.cs +++ b/src/tests/dotnet-counters/CounterMonitorPayloadTests.cs @@ -34,7 +34,7 @@ namespace DotnetCounters.UnitTests private ITestOutputHelper _outputHelper; private static readonly TimeSpan DefaultTimeout = TimeSpan.FromMinutes(2); - private static readonly string SystemRuntimeName = "System.Runtime"; + private static readonly string EventCounterSystemRuntimeName = "EventCounters\\System.Runtime"; private static readonly string TagStart = "["; private static HashSet ExpectedCounterTypes = new() { CounterTypes.Metric, CounterTypes.Rate }; @@ -66,27 +66,27 @@ namespace DotnetCounters.UnitTests ValidateCustomMetrics(metricComponents, CountersExportFormat.csv); } - [SkippableTheory(Skip = "https://github.com/dotnet/diagnostics/issues/4806"), MemberData(nameof(Configurations))] - public async Task TestCounterMonitorSystemRuntimeMetricsJSON(TestConfiguration configuration) + [SkippableTheory, MemberData(nameof(Configurations))] + public async Task TestCounterMonitorEventCounterSystemRuntimeMetricsJSON(TestConfiguration configuration) { CheckRuntimeOS(); - List metricComponents = await GetCounterTraceJSON(configuration, new List { SystemRuntimeName }); + List metricComponents = await GetCounterTraceJSON(configuration, new List { EventCounterSystemRuntimeName }); - ValidateSystemRuntimeMetrics(metricComponents); + ValidateEventCounterSystemRuntimeMetrics(metricComponents); } - [SkippableTheory(Skip = "https://github.com/dotnet/diagnostics/issues/4806"), MemberData(nameof(Configurations))] - public async Task TestCounterMonitorSystemRuntimeMetricsCSV(TestConfiguration configuration) + [SkippableTheory, MemberData(nameof(Configurations))] + public async Task TestCounterMonitorEventCounterSystemRuntimeMetricsCSV(TestConfiguration configuration) { CheckRuntimeOS(); - List metricComponents = await GetCounterTraceCSV(configuration, new List { SystemRuntimeName }); + List metricComponents = await GetCounterTraceCSV(configuration, new List { EventCounterSystemRuntimeName }); - ValidateSystemRuntimeMetrics(metricComponents); + ValidateEventCounterSystemRuntimeMetrics(metricComponents); } - private void ValidateSystemRuntimeMetrics(List metricComponents) + private void ValidateEventCounterSystemRuntimeMetrics(List metricComponents) { string[] ExpectedProviders = { "System.Runtime" }; Assert.Equal(ExpectedProviders, metricComponents.Select(c => c.ProviderName).ToHashSet()); diff --git a/src/tests/dotnet-counters/CounterMonitorTests.cs b/src/tests/dotnet-counters/CounterMonitorTests.cs index 7c5f8f7ed..0f6829db7 100644 --- a/src/tests/dotnet-counters/CounterMonitorTests.cs +++ b/src/tests/dotnet-counters/CounterMonitorTests.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Linq; +using Microsoft.Diagnostics.Monitoring.EventPipe; using Microsoft.Diagnostics.Tools; using Microsoft.Diagnostics.Tools.Counters; using Xunit; @@ -18,52 +19,65 @@ namespace DotnetCounters.UnitTests public void GenerateCounterListTestSingleProvider() { CounterMonitor monitor = new(); - CounterSet counters = CounterMonitor.ParseProviderList("MySource"); - Assert.Single(counters.Providers); - Assert.Equal("MySource", counters.Providers.First()); - Assert.True(counters.IncludesAllCounters("MySource")); + List counters = CounterMonitor.ParseProviderList("MySource"); + Assert.Single(counters); + EventPipeCounterGroup mySourceGroup = counters.First(); + Assert.Equal("MySource", mySourceGroup.ProviderName); + Assert.False(mySourceGroup.CounterNames.Any()); } [Fact] public void GenerateCounterListTestSingleProviderWithFilter() { CounterMonitor monitor = new(); - CounterSet counters = CounterMonitor.ParseProviderList("MySource[counter1,counter2,counter3]"); - Assert.Single(counters.Providers); - Assert.Equal("MySource", counters.Providers.First()); - Assert.False(counters.IncludesAllCounters("MySource")); - Assert.True(Enumerable.SequenceEqual(counters.GetCounters("MySource"), new string[] { "counter1", "counter2", "counter3" })); + List counters = CounterMonitor.ParseProviderList("MySource[counter1,counter2,counter3]"); + Assert.Single(counters); + EventPipeCounterGroup mySourceGroup = counters.First(); + Assert.Equal("MySource", mySourceGroup.ProviderName); + Assert.True(Enumerable.SequenceEqual(mySourceGroup.CounterNames, new string[] { "counter1", "counter2", "counter3" })); } [Fact] public void GenerateCounterListTestManyProviders() { CounterMonitor monitor = new(); - CounterSet counters = CounterMonitor.ParseProviderList("MySource1,MySource2,System.Runtime"); - Assert.Equal(3, counters.Providers.Count()); - Assert.Equal("MySource1", counters.Providers.ElementAt(0)); - Assert.Equal("MySource2", counters.Providers.ElementAt(1)); - Assert.Equal("System.Runtime", counters.Providers.ElementAt(2)); + List counters = CounterMonitor.ParseProviderList("MySource1,MySource2,System.Runtime"); + Assert.Equal(3, counters.Count()); + Assert.Equal("MySource1", counters.ElementAt(0).ProviderName); + Assert.Equal("MySource2", counters.ElementAt(1).ProviderName); + Assert.Equal("System.Runtime", counters.ElementAt(2).ProviderName); + } + + [Fact] + public void GenerateCounterListTestEventCountersPrefix() + { + CounterMonitor monitor = new(); + List counters = CounterMonitor.ParseProviderList("MySource1,EventCounters\\MySource2"); + Assert.Equal(2, counters.Count()); + Assert.Equal("MySource1", counters.ElementAt(0).ProviderName); + Assert.Equal(CounterGroupType.All, counters.ElementAt(0).Type); + Assert.Equal("MySource2", counters.ElementAt(1).ProviderName); + Assert.Equal(CounterGroupType.EventCounter, counters.ElementAt(1).Type); } [Fact] public void GenerateCounterListTestManyProvidersWithFilter() { CounterMonitor monitor = new(); - CounterSet counters = CounterMonitor.ParseProviderList("MySource1[mycounter1,mycounter2], MySource2[mycounter1], System.Runtime[cpu-usage,working-set]"); - Assert.Equal(3, counters.Providers.Count()); + List counters = CounterMonitor.ParseProviderList("MySource1[mycounter1,mycounter2], MySource2[mycounter1], System.Runtime[cpu-usage,working-set]"); + Assert.Equal(3, counters.Count()); - Assert.Equal("MySource1", counters.Providers.ElementAt(0)); - Assert.False(counters.IncludesAllCounters("MySource1")); - Assert.True(Enumerable.SequenceEqual(counters.GetCounters("MySource1"), new string[] { "mycounter1", "mycounter2" })); + EventPipeCounterGroup mySource1Group = counters.ElementAt(0); + Assert.Equal("MySource1", mySource1Group.ProviderName); + Assert.True(Enumerable.SequenceEqual(mySource1Group.CounterNames, new string[] { "mycounter1", "mycounter2" })); - Assert.Equal("MySource2", counters.Providers.ElementAt(1)); - Assert.False(counters.IncludesAllCounters("MySource2")); - Assert.True(Enumerable.SequenceEqual(counters.GetCounters("MySource2"), new string[] { "mycounter1" })); + EventPipeCounterGroup mySource2Group = counters.ElementAt(1); + Assert.Equal("MySource2", mySource2Group.ProviderName); + Assert.True(Enumerable.SequenceEqual(mySource2Group.CounterNames, new string[] { "mycounter1" })); - Assert.Equal("System.Runtime", counters.Providers.ElementAt(2)); - Assert.False(counters.IncludesAllCounters("System.Runtime")); - Assert.True(Enumerable.SequenceEqual(counters.GetCounters("System.Runtime"), new string[] { "cpu-usage", "working-set" })); + EventPipeCounterGroup runtimeGroup = counters.ElementAt(2); + Assert.Equal("System.Runtime", runtimeGroup.ProviderName); + Assert.True(Enumerable.SequenceEqual(runtimeGroup.CounterNames, new string[] { "cpu-usage", "working-set" })); } [Fact] @@ -72,11 +86,11 @@ namespace DotnetCounters.UnitTests CounterMonitor monitor = new(); List commandLineProviderArgs = new() { "System.Runtime", "MyEventSource" }; string countersOptionText = "MyEventSource1,MyEventSource2"; - CounterSet counters = monitor.ConfigureCounters(countersOptionText, commandLineProviderArgs); - Assert.Contains("MyEventSource", counters.Providers); - Assert.Contains("MyEventSource1", counters.Providers); - Assert.Contains("MyEventSource2", counters.Providers); - Assert.Contains("System.Runtime", counters.Providers); + List counters = monitor.ConfigureCounters(countersOptionText, commandLineProviderArgs); + Assert.Contains("MyEventSource", counters.Select(g => g.ProviderName)); + Assert.Contains("MyEventSource1", counters.Select(g => g.ProviderName)); + Assert.Contains("MyEventSource2", counters.Select(g => g.ProviderName)); + Assert.Contains("System.Runtime", counters.Select(g => g.ProviderName)); } [Fact] @@ -85,10 +99,10 @@ namespace DotnetCounters.UnitTests CounterMonitor monitor = new(); List commandLineProviderArgs = new() { "System.Runtime", "MyEventSource" }; string countersOptionText = "System.Runtime,MyEventSource"; - CounterSet counters = monitor.ConfigureCounters(countersOptionText, commandLineProviderArgs); - Assert.Equal(2, counters.Providers.Count()); - Assert.Contains("MyEventSource", counters.Providers); - Assert.Contains("System.Runtime", counters.Providers); + List counters = monitor.ConfigureCounters(countersOptionText, commandLineProviderArgs); + Assert.Equal(2, counters.Count()); + Assert.Contains("MyEventSource", counters.Select(g => g.ProviderName)); + Assert.Contains("System.Runtime", counters.Select(g => g.ProviderName)); } [Fact] @@ -136,5 +150,14 @@ namespace DotnetCounters.UnitTests CommandLineErrorException e = Assert.Throws(() => monitor.ConfigureCounters(countersOptionText, null)); Assert.Equal("Error parsing --counters argument: Expected at most one '[' in counter_provider", e.Message); } + + [Fact] + public void ParseErrorMultiplePrefixesOnSameProvider() + { + CounterMonitor monitor = new(); + string countersOptionText = "System.Runtime,MyEventSource,EventCounters\\System.Runtime"; + CommandLineErrorException e = Assert.Throws(() => monitor.ConfigureCounters(countersOptionText, null)); + Assert.Equal("Error parsing --counters argument: Using the same provider name with and without the EventCounters\\ prefix in the counter list is not supported.", e.Message); + } } } diff --git a/src/tests/dotnet-counters/KnownProviderTests.cs b/src/tests/dotnet-counters/KnownProviderTests.cs deleted file mode 100644 index 51a17536b..000000000 --- a/src/tests/dotnet-counters/KnownProviderTests.cs +++ /dev/null @@ -1,45 +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 Xunit; - -namespace Microsoft.Diagnostics.Tools.Counters -{ - /// - /// These test the some of the known providers that we provide as a default configuration for customers to use. - /// - public class KnownProviderTests - { - [Fact] - public void TestRuntimeProvider() - { - KnownData.TryGetProvider("System.Runtime", out CounterProvider runtimeProvider); - - Assert.Equal("System.Runtime", runtimeProvider.Name); - Assert.Equal("0xffffffff", runtimeProvider.Keywords); - Assert.Equal("5", runtimeProvider.Level); - Assert.Equal("System.Runtime:0xffffffff:5:EventCounterIntervalSec=1", runtimeProvider.ToProviderString(1)); - } - - [Fact] - public void TestASPNETProvider() - { - KnownData.TryGetProvider("Microsoft.AspNetCore.Hosting", out CounterProvider aspnetProvider); - - Assert.Equal("Microsoft.AspNetCore.Hosting", aspnetProvider.Name); - Assert.Equal("0x0", aspnetProvider.Keywords); - Assert.Equal("4", aspnetProvider.Level); - Assert.Equal("Microsoft.AspNetCore.Hosting:0x0:4:EventCounterIntervalSec=5", aspnetProvider.ToProviderString(5)); - } - - [Fact] - public void UnknownProvider() - { - KnownData.TryGetProvider("SomeRandomProvider", out CounterProvider randomProvider); - - Assert.Null(randomProvider); - } - - // TODO: Add more as we add more providers as known providers to the tool... - } -} -- 2.34.1