From: Sung Yoon Whang Date: Thu, 25 Apr 2019 22:50:00 +0000 (-0700) Subject: Make dotnet-counter stream values in real time (#197) X-Git-Tag: submit/tizen/20190813.035844~19 X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=f0edad227991f668b7a2cd7ec876a1e364c0cb5e;p=platform%2Fcore%2Fdotnet%2Fdiagnostics.git Make dotnet-counter stream values in real time (#197) * Streaming counters * DisplayName stuff * fix build * few demo changes * parsing command-line provided counter list works now * No more string parsing!!! * some more fixes in writer * update license header, display rates * Removing useless files * Use known displaynames for known providers * cleanup * spaces * more cleanup * more cleanup * PR feedback --- diff --git a/src/Tools/dotnet-counters/ConsoleWriter.cs b/src/Tools/dotnet-counters/ConsoleWriter.cs new file mode 100644 index 000000000..4f2a50e87 --- /dev/null +++ b/src/Tools/dotnet-counters/ConsoleWriter.cs @@ -0,0 +1,96 @@ +// 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 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 knownProvidersRowNum; + + public ConsoleWriter() + { + displayPosition = new Dictionary(); + knownProvidersRowNum = new Dictionary(); + + 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 diff --git a/src/Tools/dotnet-counters/CounterMonitor.cs b/src/Tools/dotnet-counters/CounterMonitor.cs index d911498a0..892b5382f 100644 --- a/src/Tools/dotnet-counters/CounterMonitor.cs +++ b/src/Tools/dotnet-counters/CounterMonitor.cs @@ -1,37 +1,56 @@ -// 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 payloadVal = (IDictionary)(obj.PayloadValue(0)); + IDictionary payloadFields = (IDictionary)(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 Monitor(CancellationToken ct, string counterList, IConsole console, int processId, int interval) + public async Task 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; @@ -43,11 +62,10 @@ namespace Microsoft.Diagnostics.Tools.Counters { 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; } @@ -65,8 +83,6 @@ namespace Microsoft.Diagnostics.Tools.Counters 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)) @@ -102,17 +118,24 @@ namespace Microsoft.Diagnostics.Tools.Counters } 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; } } diff --git a/src/Tools/dotnet-counters/CounterPayload.cs b/src/Tools/dotnet-counters/CounterPayload.cs new file mode 100644 index 000000000..8a8e75131 --- /dev/null +++ b/src/Tools/dotnet-counters/CounterPayload.cs @@ -0,0 +1,76 @@ +// 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 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 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}"; + } + } +} diff --git a/src/Tools/dotnet-counters/CounterProvider.cs b/src/Tools/dotnet-counters/CounterProvider.cs index 5c6e62835..e1b821b11 100644 --- a/src/Tools/dotnet-counters/CounterProvider.cs +++ b/src/Tools/dotnet-counters/CounterProvider.cs @@ -1,6 +1,8 @@ -// 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; @@ -8,13 +10,11 @@ namespace Microsoft.Diagnostics.Tools.Counters { 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 Counters { get; } + public Dictionary Counters { get; } public CounterProvider(string name, string description, string keywords, string level, IEnumerable counters) { @@ -22,7 +22,18 @@ namespace Microsoft.Diagnostics.Tools.Counters Description = description; Keywords = keywords; Level = level; - Counters = counters.ToList(); + Counters = new Dictionary(); + 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) @@ -34,6 +45,7 @@ namespace Microsoft.Diagnostics.Tools.Counters public class CounterProfile { public string Name { get; set; } + public string DisplayName { get; set; } public string Description { get; set; } } } diff --git a/src/Tools/dotnet-counters/KnownData.cs b/src/Tools/dotnet-counters/KnownData.cs index a8e50c5ce..a8ed85b07 100644 --- a/src/Tools/dotnet-counters/KnownData.cs +++ b/src/Tools/dotnet-counters/KnownData.cs @@ -1,5 +1,6 @@ -// 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; @@ -21,20 +22,16 @@ namespace Microsoft.Diagnostics.Tools.Counters "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) } diff --git a/src/Tools/dotnet-counters/Program.cs b/src/Tools/dotnet-counters/Program.cs index 7d6a53c15..6ab5ff985 100644 --- a/src/Tools/dotnet-counters/Program.cs +++ b/src/Tools/dotnet-counters/Program.cs @@ -1,5 +1,6 @@ -// 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; @@ -41,7 +42,7 @@ namespace Microsoft.Diagnostics.Tools.Counters 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() => @@ -59,7 +60,7 @@ namespace Microsoft.Diagnostics.Tools.Counters 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}"); } diff --git a/src/Tools/dotnet-counters/dotnet-counters.csproj b/src/Tools/dotnet-counters/dotnet-counters.csproj index b30daa603..ceff9fafb 100644 --- a/src/Tools/dotnet-counters/dotnet-counters.csproj +++ b/src/Tools/dotnet-counters/dotnet-counters.csproj @@ -29,6 +29,7 @@ + \ No newline at end of file diff --git a/src/Versions.props b/src/Versions.props index 4d4f5c717..140bd32e2 100644 --- a/src/Versions.props +++ b/src/Versions.props @@ -1,6 +1,6 @@ - 2.0.40 + 2.0.41 0.2.0-alpha.19213.1 0.2.0-alpha.19213.1