--- /dev/null
+// 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));
+ }
+ }
+}
{
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)
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
{
_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();
}
}
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.");
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,
});
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;
}
}
{
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
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)
}
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
+using System.Collections.Generic;
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(
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() =>
{
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("");
}