Bringing dotnet counters up to spec [part 1] (#199)
authorSung Yoon Whang <suwhang@microsoft.com>
Fri, 26 Apr 2019 06:15:52 +0000 (23:15 -0700)
committerGitHub <noreply@github.com>
Fri, 26 Apr 2019 06:15:52 +0000 (23:15 -0700)
* 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

src/Tools/dotnet-counters/CounterFilter.cs [new file with mode: 0644]
src/Tools/dotnet-counters/CounterMonitor.cs
src/Tools/dotnet-counters/CounterProvider.cs
src/Tools/dotnet-counters/KnownData.cs
src/Tools/dotnet-counters/Program.cs

diff --git a/src/Tools/dotnet-counters/CounterFilter.cs b/src/Tools/dotnet-counters/CounterFilter.cs
new file mode 100644 (file)
index 0000000..7cecf81
--- /dev/null
@@ -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<string> enabledProviders;
+        private Dictionary<string, List<string>> enabledCounters;
+
+        public CounterFilter()
+        {
+            enabledProviders = new List<string>();
+            enabledCounters = new Dictionary<string, List<string>>();
+        }
+
+        // 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<string>(counters);
+        }
+
+        public bool Filter(string providerName, string counterName)
+        {
+            return enabledProviders.Contains(providerName) || 
+                (enabledCounters.ContainsKey(providerName) && enabledCounters[providerName].Contains(counterName));
+        }
+    }
+}
index 892b5382fc0d29e6b9c05168dd0b5fece614b062..3c5c004071b807a7c1879c019f4246ba04442692 100644 (file)
@@ -19,14 +19,16 @@ namespace Microsoft.Diagnostics.Tools.Counters
     {
         private int _processId;
         private int _interval;
-        private string _counterList;
+        private List<string> _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<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.
+                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<int> Monitor(CancellationToken ct, string counter_list, IConsole console, int processId, int interval)
+        public async Task<int> Monitor(CancellationToken ct, List<string> 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;
         }
     }
index e1b821b11651e4994e573abd48d8a9b3bd7171e1..03c8b68c39cac29648c891f6a1a36b6db92f5679 100644 (file)
@@ -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<CounterProfile> GetAllCounters() => Counters.Values.ToList();
+
     }
 
     public class CounterProfile
index a8ed85b07d2da0151c464bb4fe276a144659e1e5..e8903b357b6227970089951e4490a2b8c9caa9ee 100644 (file)
@@ -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)
         }
index 6ab5ff985343ebb372b02ff299a30022e1d32df4..9529a0ae9363bfc6046f708e7aad3b1bb198052c 100644 (file)
@@ -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<CancellationToken, string, IConsole, int, int>(new CounterMonitor().Monitor));
+                handler: CommandHandler.Create<CancellationToken, List<string>, 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<int> { Name = "interval" });
+                new Argument<int> { Name = "refresh-interval" });
 
         private static Argument CounterList() =>
-            new Argument<string> {
+            new Argument<List<string>> {
                 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("");
             }