From: Sung Yoon Whang Date: Fri, 26 Apr 2019 06:15:52 +0000 (-0700) Subject: Bringing dotnet counters up to spec [part 1] (#199) X-Git-Tag: submit/tizen/20190813.035844~18 X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=d189f1fad8e1105b2429f2fbc3487dc971094b5d;p=platform%2Fcore%2Fdotnet%2Fdiagnostics.git Bringing dotnet counters up to spec [part 1] (#199) * fix build warnings * For some reason we dont have CounterType field inside the payload... * support multiple providers * Filtering * Fix broken alignment on list command * spaces * support filtering for an entire provider * Removing -r option * Add filter for System.Runtime for default monitoring session --- diff --git a/src/Tools/dotnet-counters/CounterFilter.cs b/src/Tools/dotnet-counters/CounterFilter.cs new file mode 100644 index 000000000..7cecf8106 --- /dev/null +++ b/src/Tools/dotnet-counters/CounterFilter.cs @@ -0,0 +1,39 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; + + +namespace Microsoft.Diagnostics.Tools.Counters +{ + public class CounterFilter + { + private List enabledProviders; + private Dictionary> enabledCounters; + + public CounterFilter() + { + enabledProviders = new List(); + enabledCounters = new Dictionary>(); + } + + // Called when we want to enable all counters under a provider name. + public void AddFilter(string providerName) + { + enabledProviders.Add(providerName); + } + + public void AddFilter(string providerName, string[] counters) + { + enabledCounters[providerName] = new List(counters); + } + + public bool Filter(string providerName, string counterName) + { + return enabledProviders.Contains(providerName) || + (enabledCounters.ContainsKey(providerName) && enabledCounters[providerName].Contains(counterName)); + } + } +} diff --git a/src/Tools/dotnet-counters/CounterMonitor.cs b/src/Tools/dotnet-counters/CounterMonitor.cs index 892b5382f..3c5c00407 100644 --- a/src/Tools/dotnet-counters/CounterMonitor.cs +++ b/src/Tools/dotnet-counters/CounterMonitor.cs @@ -19,14 +19,16 @@ namespace Microsoft.Diagnostics.Tools.Counters { private int _processId; private int _interval; - private string _counterList; + private List _counterList; private CancellationToken _ct; private IConsole _console; private ConsoleWriter writer; + private CounterFilter filter; private ulong _sessionId; public CounterMonitor() { writer = new ConsoleWriter(); + filter = new CounterFilter(); } private void Dynamic_All(TraceEvent obj) @@ -36,16 +38,18 @@ namespace Microsoft.Diagnostics.Tools.Counters IDictionary payloadVal = (IDictionary)(obj.PayloadValue(0)); IDictionary payloadFields = (IDictionary)(payloadVal["Payload"]); + // If it's not a counter we asked for, ignore it. + if (!filter.Filter(obj.ProviderName, payloadFields["Name"].ToString())) return; + // There really isn't a great way to tell whether an EventCounter payload is an instance of // IncrementingCounterPayload or CounterPayload, so here we check the number of fields - // to distinguish the two. - ICounterPayload payload = payloadFields["CounterType"] == "Sum" ? (ICounterPayload)new IncrementingCounterPayload(payloadFields) : (ICounterPayload)new CounterPayload(payloadFields); - + // to distinguish the two. + ICounterPayload payload = payloadFields.Count == 6 ? (ICounterPayload)new IncrementingCounterPayload(payloadFields) : (ICounterPayload)new CounterPayload(payloadFields); writer.Update(obj.ProviderName, payload); } } - public async Task Monitor(CancellationToken ct, string counter_list, IConsole console, int processId, int interval) + public async Task Monitor(CancellationToken ct, List counter_list, IConsole console, int processId, int refreshInterval) { try { @@ -53,7 +57,7 @@ namespace Microsoft.Diagnostics.Tools.Counters _counterList = counter_list; // NOTE: This variable name has an underscore because that's the "name" that the CLI displays. System.CommandLine doesn't like it if we change the variable to camelcase. _console = console; _processId = processId; - _interval = interval; + _interval = refreshInterval; return await StartMonitor(); } @@ -79,13 +83,13 @@ namespace Microsoft.Diagnostics.Tools.Counters } if (_interval == 0) { - _console.Error.WriteLine("interval is required."); + _console.Error.WriteLine("refreshInterval is required."); return 1; } String providerString; - if (string.IsNullOrEmpty(_counterList)) + if (_counterList.Count == 0) { CounterProvider defaultProvider = null; _console.Out.WriteLine($"counter_list is unspecified. Monitoring all counters by default."); @@ -97,27 +101,46 @@ namespace Microsoft.Diagnostics.Tools.Counters return 1; } providerString = defaultProvider.ToProviderString(_interval); + filter.AddFilter("System.Runtime"); } else { - string[] counters = _counterList.Split(" "); CounterProvider provider = null; StringBuilder sb = new StringBuilder(""); - for (var i = 0; i < counters.Length; i++) + for (var i = 0; i < _counterList.Count; i++) { - if (!KnownData.TryGetProvider(counters[i], out provider)) + string counterSpecifier = _counterList[i]; + string[] tokens = counterSpecifier.Split('['); + string providerName = tokens[0]; + if (!KnownData.TryGetProvider(providerName, out provider)) + { + sb.Append(CounterProvider.SerializeUnknownProviderName(providerName, _interval)); + } + else { - _console.Error.WriteLine($"No known provider called {counters[i]}."); - return 1; + sb.Append(provider.ToProviderString(_interval)); } - sb.Append(provider.ToProviderString(_interval)); - if (i != counters.Length - 1) + + if (i != _counterList.Count - 1) { sb.Append(","); } + + if (tokens.Length == 1) + { + filter.AddFilter(providerName); // This means no counter filter was specified. + } + else + { + string counterNames = tokens[1]; + string[] enabledCounters = counterNames.Substring(0, counterNames.Length-1).Split(','); + + filter.AddFilter(providerName, enabledCounters); + } } providerString = sb.ToString(); } + Task monitorTask = new Task(() => { var configuration = new SessionConfiguration( circularBufferSizeMB: 1000, @@ -132,10 +155,15 @@ namespace Microsoft.Diagnostics.Tools.Counters }); monitorTask.Start(); + await monitorTask; - EventPipeClient.StopTracing(_processId, _sessionId); + + try + { + EventPipeClient.StopTracing(_processId, _sessionId); + } + catch (System.IO.EndOfStreamException) {} // If the app we're monitoring exits abrubtly, this may throw in which case we just swallow the exception and exit gracefully. - Task.FromResult(0); return 0; } } diff --git a/src/Tools/dotnet-counters/CounterProvider.cs b/src/Tools/dotnet-counters/CounterProvider.cs index e1b821b11..03c8b68c3 100644 --- a/src/Tools/dotnet-counters/CounterProvider.cs +++ b/src/Tools/dotnet-counters/CounterProvider.cs @@ -40,6 +40,14 @@ namespace Microsoft.Diagnostics.Tools.Counters { return $"{Name}:{Keywords}:{Level}:EventCounterIntervalSec={interval}"; } + + public static string SerializeUnknownProviderName(string unknownCounterProviderName, int interval) + { + return $"{unknownCounterProviderName}:0xffffffff:0x4:EventCounterIntervalSec={interval}"; + } + + public IReadOnlyList GetAllCounters() => Counters.Values.ToList(); + } public class CounterProfile diff --git a/src/Tools/dotnet-counters/KnownData.cs b/src/Tools/dotnet-counters/KnownData.cs index a8ed85b07..e8903b357 100644 --- a/src/Tools/dotnet-counters/KnownData.cs +++ b/src/Tools/dotnet-counters/KnownData.cs @@ -27,10 +27,10 @@ namespace Microsoft.Diagnostics.Tools.Counters new CounterProfile{ Name="cpu-usage", Description="Amount of time the process has utilized the CPU (ms)", DisplayName="CPU Usage (%)" }, new CounterProfile{ Name="working-set", Description="Amount of working set used by the process (MB)", DisplayName="Working Set (MB)" }, new CounterProfile{ Name="gc-heap-size", Description="Total heap size reported by the GC (MB)", DisplayName="GC Heap Size (MB)" }, - new CounterProfile{ Name="gen-0-gc-count", Description="Number of Gen 0 GCs", DisplayName="Gen 0 GC / sec" }, - new CounterProfile{ Name="gen-1-gc-count", Description="Number of Gen 1 GCs", DisplayName="Gen 1 GC / sec" }, - new CounterProfile{ Name="gen-2-gc-count", Description="Number of Gen 2 GCs", DisplayName="Gen 2 GC / sec" }, - new CounterProfile{ Name="exception-count", Description="Number of Exceptions / Sec", DisplayName="Exceptions / sec" }, + new CounterProfile{ Name="gen-0-gc-count", Description="Number of Gen 0 GCs / sec", DisplayName="Gen 0 GC / sec" }, + new CounterProfile{ Name="gen-1-gc-count", Description="Number of Gen 1 GCs / sec", DisplayName="Gen 1 GC / sec" }, + new CounterProfile{ Name="gen-2-gc-count", Description="Number of Gen 2 GCs / sec", DisplayName="Gen 2 GC / sec" }, + new CounterProfile{ Name="exception-count", Description="Number of Exceptions / sec", DisplayName="Exceptions / sec" }, }); // TODO: Add more providers (ex. ASP.NET ones) } diff --git a/src/Tools/dotnet-counters/Program.cs b/src/Tools/dotnet-counters/Program.cs index 6ab5ff985..9529a0ae9 100644 --- a/src/Tools/dotnet-counters/Program.cs +++ b/src/Tools/dotnet-counters/Program.cs @@ -10,6 +10,7 @@ using System.Diagnostics; using System.Linq; using System.Threading; using System.Threading.Tasks; +using System.Collections.Generic; namespace Microsoft.Diagnostics.Tools.Counters @@ -22,7 +23,7 @@ namespace Microsoft.Diagnostics.Tools.Counters "Start monitoring a .NET application", new Option[] { ProcessIdOption(), RefreshIntervalOption() }, argument: CounterList(), - handler: CommandHandler.Create(new CounterMonitor().Monitor)); + handler: CommandHandler.Create, IConsole, int, int>(new CounterMonitor().Monitor)); private static Option ProcessIdOption() => new Option( @@ -32,17 +33,18 @@ namespace Microsoft.Diagnostics.Tools.Counters private static Option RefreshIntervalOption() => new Option( - new[] { "-r", "--interval" }, + new[] { "--refresh-interval" }, "The number of seconds to delay between updating the displayed counters.", - new Argument { Name = "interval" }); + new Argument { Name = "refresh-interval" }); private static Argument CounterList() => - new Argument { + new Argument> { Name = "counter_list", Description = @"A space separated list of counters. Counters can be specified provider_name[:counter_name]. If the provider_name is used without a qualifying counter_name then all counters will be shown. To discover provider and counter names, use the list command. .", + Arity = ArgumentArity.ZeroOrMore }; private static Command ListCommand() => @@ -56,13 +58,15 @@ namespace Microsoft.Diagnostics.Tools.Counters { var profiles = KnownData.GetAllProviders(); var maxNameLength = profiles.Max(p => p.Name.Length); + Console.WriteLine(maxNameLength); Console.WriteLine("Showing well-known counters only. Specific processes may support additional counters.\n"); foreach (var profile in profiles) { - Console.WriteLine($"* {profile.Name.PadRight(maxNameLength)}"); + var counters = profile.GetAllCounters(); + var maxCounterNameLength = counters.Max(c => c.Name.Length); foreach (var counter in profile.Counters.Values) { - Console.WriteLine($" {counter.Name} \t\t {counter.Description}"); + Console.WriteLine($" {counter.Name.PadRight(maxCounterNameLength)} \t\t {counter.Description}"); } Console.WriteLine(""); }