--- /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 ConsoleWriter
+ {
+ private Dictionary<string, (int, int)> displayPosition; // Display position (x-y coordiates) of each counter values.
+ private int origRow;
+ private int origCol;
+ private int maxRow; // Running maximum of row number
+ private int maxCol; // Running maximum of col number
+ private Dictionary<string, int> knownProvidersRowNum;
+
+ public ConsoleWriter()
+ {
+ displayPosition = new Dictionary<string, (int, int)>();
+ knownProvidersRowNum = new Dictionary<string, int>();
+
+ foreach(CounterProvider provider in KnownData.GetAllProviders())
+ {
+ knownProvidersRowNum[provider.Name] = -1;
+ }
+ }
+
+ public void InitializeDisplay()
+ {
+ Console.Clear();
+ origRow = Console.CursorTop;
+ origCol = Console.CursorLeft;
+
+ maxRow = origRow;
+ maxCol = origCol;
+ }
+
+ public void Update(string providerName, ICounterPayload payload)
+ {
+ string name = payload.GetName();
+
+ // We already know what this counter is! Just update the value string on the console.
+ if (displayPosition.ContainsKey(name))
+ {
+ (int left, int row) = displayPosition[name];
+ Console.SetCursorPosition(left, row);
+ Console.Write(new String(' ', 16));
+
+ Console.SetCursorPosition(left, row);
+ Console.Write(payload.GetValue());
+ }
+ // Got a payload from a new counter that hasn't been written to the console yet.
+ else
+ {
+ bool isWellKnownProvider = knownProvidersRowNum.ContainsKey(providerName);
+
+ if (isWellKnownProvider)
+ {
+ if (knownProvidersRowNum[providerName] < 0)
+ {
+ knownProvidersRowNum[providerName] = maxRow + 1;
+ Console.SetCursorPosition(0, maxRow);
+ Console.WriteLine(providerName);
+ maxRow += 1;
+ }
+
+ KnownData.TryGetProvider(providerName, out CounterProvider counterProvider);
+ string displayName = counterProvider.TryGetDisplayName(name);
+ if (displayName == null)
+ {
+ displayName = payload.GetDisplay();
+ }
+
+ int left = displayName.Length + 7; // displayName + " : "
+ int row = maxRow;
+ displayPosition[name] = (left, row);
+ Console.WriteLine($" {displayName} : {payload.GetValue()}");
+ maxRow += 1;
+ }
+ else
+ {
+ // If it's from an provider, just append it at the end.
+ string displayName = payload.GetDisplay();
+ int left = displayName.Length + 7; // displayName + " : "
+ int row = maxRow;
+ displayPosition[name] = (left, row);
+ Console.WriteLine($" {displayName} : {payload.GetValue()}");
+ maxRow += 1;
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
-// Copyright (c) .NET Foundation. All rights reserved.
-// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+// 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 Microsoft.Diagnostics.Tools.RuntimeClient;
using System;
+using System.Collections.Generic;
using System.CommandLine;
using System.IO;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
+using Microsoft.Diagnostics.Tracing;
+
namespace Microsoft.Diagnostics.Tools.Counters
{
public class CounterMonitor
{
- private string outputPath;
- private ulong sessionId;
-
private int _processId;
private int _interval;
private string _counterList;
private CancellationToken _ct;
private IConsole _console;
-
+ private ConsoleWriter writer;
+ private ulong _sessionId;
public CounterMonitor()
{
+ writer = new ConsoleWriter();
+ }
+
+ private void Dynamic_All(TraceEvent obj)
+ {
+ if (obj.EventName.Equals("EventCounters"))
+ {
+ IDictionary<string, object> payloadVal = (IDictionary<string, object>)(obj.PayloadValue(0));
+ IDictionary<string, object> payloadFields = (IDictionary<string, object>)(payloadVal["Payload"]);
+
+ // 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);
+
+ writer.Update(obj.ProviderName, payload);
+ }
}
- public async Task<int> Monitor(CancellationToken ct, string counterList, IConsole console, int processId, int interval)
+ public async Task<int> Monitor(CancellationToken ct, string counter_list, IConsole console, int processId, int interval)
{
try
{
_ct = ct;
- _counterList = counterList;
+ _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;
{
try
{
- EventPipeClient.StopTracing(_processId, sessionId);
+ EventPipeClient.StopTracing(_processId, _sessionId);
}
catch (Exception) {} // Swallow all exceptions for now.
- console.Out.WriteLine($"Tracing stopped. Trace files written to {outputPath}");
console.Out.WriteLine($"Complete");
return 1;
}
return 1;
}
- outputPath = Path.Combine(Directory.GetCurrentDirectory(), $"dotnet-counters-{_processId}.netperf"); // TODO: This can be removed once events can be streamed in real time.
-
String providerString;
if (string.IsNullOrEmpty(_counterList))
}
providerString = sb.ToString();
}
-
- var configuration = new SessionConfiguration(
- circularBufferSizeMB: 1000,
- outputPath: outputPath,
- providers: Trace.Extensions.ToProviders(providerString));
-
- sessionId = EventPipeClient.StartTracingToFile(_processId, configuration);
-
- // Write the config file contents
- _console.Out.WriteLine("Tracing has started. Press Ctrl-C to stop.");
- await Task.Delay(int.MaxValue, _ct);
+ Task monitorTask = new Task(() => {
+ var configuration = new SessionConfiguration(
+ circularBufferSizeMB: 1000,
+ outputPath: "",
+ providers: Trace.Extensions.ToProviders(providerString));
+
+ var binaryReader = EventPipeClient.CollectTracing(_processId, configuration, out _sessionId);
+ EventPipeEventSource source = new EventPipeEventSource(binaryReader);
+ writer.InitializeDisplay();
+ source.Dynamic.All += Dynamic_All;
+ source.Process();
+ });
+
+ monitorTask.Start();
+ await monitorTask;
+ EventPipeClient.StopTracing(_processId, _sessionId);
+
+ Task.FromResult(0);
return 0;
}
}
--- /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;
+using System.Text;
+
+namespace Microsoft.Diagnostics.Tools.Counters
+{
+ public interface ICounterPayload
+ {
+ string GetName();
+ string GetValue();
+ string GetDisplay();
+ }
+
+
+ class CounterPayload : ICounterPayload
+ {
+ public string m_Name;
+ public string m_Value;
+ public string m_DisplayName;
+ public CounterPayload(IDictionary<string, object> payloadFields)
+ {
+ m_Name = payloadFields["Name"].ToString();
+ m_Value = payloadFields["Mean"].ToString();
+ m_DisplayName = payloadFields["DisplayName"].ToString();
+ }
+
+ public string GetName()
+ {
+ return m_Name;
+ }
+
+ public string GetValue()
+ {
+ return m_Value;
+ }
+
+ public string GetDisplay()
+ {
+ return m_DisplayName;
+ }
+ }
+
+ class IncrementingCounterPayload : ICounterPayload
+ {
+ public string m_Name;
+ public string m_Value;
+ public string m_DisplayName;
+ public string m_DisplayRateTimeScale;
+ public IncrementingCounterPayload(IDictionary<string, object> payloadFields)
+ {
+ m_Name = payloadFields["Name"].ToString();
+ m_Value = payloadFields["Increment"].ToString();
+ m_DisplayName = payloadFields["DisplayName"].ToString();
+ m_DisplayRateTimeScale = TimeSpan.Parse(payloadFields["DisplayRateTimeScale"].ToString()).ToString("%s' sec'");
+ }
+
+ public string GetName()
+ {
+ return m_Name;
+ }
+
+ public string GetValue()
+ {
+ return m_Value;
+ }
+
+ public string GetDisplay()
+ {
+ return $"{m_DisplayName} / {m_DisplayRateTimeScale}";
+ }
+ }
+}
-// Copyright (c) .NET Foundation. All rights reserved.
-// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+// 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;
using System.Linq;
{
public class CounterProvider
{
- public static readonly string DefaultProviderName = "System.Runtime";
-
public string Name { get; }
public string Description { get; }
public string Keywords { get; }
public string Level { get; }
- public IReadOnlyList<CounterProfile> Counters { get; }
+ public Dictionary<string, CounterProfile> Counters { get; }
public CounterProvider(string name, string description, string keywords, string level, IEnumerable<CounterProfile> counters)
{
Description = description;
Keywords = keywords;
Level = level;
- Counters = counters.ToList();
+ Counters = new Dictionary<string, CounterProfile>();
+ foreach (CounterProfile counter in counters)
+ {
+ Counters.Add(counter.Name, counter);
+ }
+ }
+
+ public string TryGetDisplayName(string counterName)
+ {
+ if (Counters.ContainsKey(counterName))
+ return Counters[counterName].DisplayName;
+ return null;
}
public string ToProviderString(int interval)
public class CounterProfile
{
public string Name { get; set; }
+ public string DisplayName { get; set; }
public string Description { get; set; }
}
}
-// Copyright (c) .NET Foundation. All rights reserved.
-// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+// 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;
"System.Runtime", // Name
"A default set of performance counters provided by the .NET runtime.", // Description
"0xffffffff", // Keywords
- "0x5", // Level
+ "5", // Level
new[] { // Counters
- // NOTE: For now, the set of counters below doesn't really matter because
- // we don't really display any counters in real time. (We just collect .netperf files)
- // In the future (with IPC), we should filter counter payloads by name provided below to display.
- // These are mainly here as placeholders.
- new CounterProfile{ Name="total-processor-time", Description="Amount of time the process has utilized the CPU (ms)" },
- new CounterProfile{ Name="private-memory", Description="Amount of private virtual memory used by the process (KB)" },
- new CounterProfile{ Name="working-set", Description="Amount of working set used by the process (KB)" },
- new CounterProfile{ Name="virtual-memory", Description="Amount of virtual memory used by the process (KB)" },
- new CounterProfile{ Name="gc-total-memory", Description="Amount of committed virtual memory used by the GC (KB)" },
- new CounterProfile{ Name="exceptions-thrown-rate", Description="Number of exceptions thrown in a recent 1 minute window (exceptions/min)" },
+ 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" },
});
-
// TODO: Add more providers (ex. ASP.NET ones)
}
-// Copyright (c) .NET Foundation. All rights reserved.
-// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+// 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.CommandLine;
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.
- ."
+ .",
};
private static Command ListCommand() =>
foreach (var profile in profiles)
{
Console.WriteLine($"* {profile.Name.PadRight(maxNameLength)}");
- foreach (var counter in profile.Counters)
+ foreach (var counter in profile.Counters.Values)
{
Console.WriteLine($" {counter.Name} \t\t {counter.Description}");
}
<ItemGroup>
<ProjectReference Include="$(MSBuildThisFileDirectory)..\..\Microsoft.Diagnostic.Repl\Microsoft.Diagnostic.Repl.csproj" />
<ProjectReference Include="$(MSBuildThisFileDirectory)..\..\Microsoft.Diagnostics.Tools.RuntimeClient\Microsoft.Diagnostics.Tools.RuntimeClient.csproj" />
+ <PackageReference Include="Microsoft.Diagnostics.Tracing.TraceEvent" Version="2.0.41" />
</ItemGroup>
</Project>
\ No newline at end of file
<Project>
<PropertyGroup>
- <MicrosoftDiagnosticsTracingLibraryVersion>2.0.40</MicrosoftDiagnosticsTracingLibraryVersion>
+ <MicrosoftDiagnosticsTracingLibraryVersion>2.0.41</MicrosoftDiagnosticsTracingLibraryVersion>
<SystemCommandLineExperimentalVersion>0.2.0-alpha.19213.1</SystemCommandLineExperimentalVersion>
<SystemCommandLineRenderingVersion>0.2.0-alpha.19213.1</SystemCommandLineRenderingVersion>
</PropertyGroup>