From 34453a135a842bb8e8f2bb5b44a8707eb1b36670 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Standa=20Luke=C5=A1?= Date: Thu, 18 Nov 2021 17:34:23 +0000 Subject: [PATCH] Add support for writing the Brendan Gregg's flamegraph format --- documentation/dotnet-trace-instructions.md | 12 +++++ .../CollapsedStacksSourceWriter.cs | 52 +++++++++++++++++++ .../CommandLine/Options/CommonOptions.cs | 2 +- .../dotnet-trace/TraceFileFormatConverter.cs | 9 +++- 4 files changed, 72 insertions(+), 3 deletions(-) create mode 100644 src/Tools/dotnet-trace/CollapsedStacksSourceWriter.cs diff --git a/documentation/dotnet-trace-instructions.md b/documentation/dotnet-trace-instructions.md index 154996a927..ba90aa937c 100644 --- a/documentation/dotnet-trace-instructions.md +++ b/documentation/dotnet-trace-instructions.md @@ -98,6 +98,18 @@ On Windows, `.nettrace` files can be viewed on PerfView (https://github.com/micr If you would rather view the trace on a Linux machine, you can do this by changing the output format of `dotnet-trace` to `speedscope`. You can change the output file format using the `-f|--format` option - `-f speedscope` will make `dotnet-trace` to produce a speedscope file. You can currently choose between `nettrace` (the default option) and `speedscope`. Speedscope files can be opened at https://www.speedscope.app. +Another option for visualization is to use [Brendan Gregg's flame graph tool](https://github.com/brendangregg/FlameGraph). +It expects folded stack traces in a simple text format which can be produced by `dotnet-trace convert --format CollapsedStacks` and it will produce an SVG flame graph. +Note that compared to speedscope and chromium tracing, this will collapse same stack traces together regardless of the time when they were executed. +That makes it more suitable for CPU profiling while SpeedScope and Chromium are probably better for tracing. + + +``` +dotnet trace collect -- dotnet myapp.dll +dotnet trace convert --format CollapsedStacks -o mytrace dotnet_X_Y.nettrace +flamegraph.pl < mytrace.stacks > myflamegraph.svg +``` + Note: The .NET Core runtime generates traces in the `nettrace` format, and are converted to speedscope (if specified) after the trace is completed. Since some conversions may result in loss of data, the original `nettrace` file is preserved next to the converted file. ## Known Caveats diff --git a/src/Tools/dotnet-trace/CollapsedStacksSourceWriter.cs b/src/Tools/dotnet-trace/CollapsedStacksSourceWriter.cs new file mode 100644 index 0000000000..6dd4ab14c1 --- /dev/null +++ b/src/Tools/dotnet-trace/CollapsedStacksSourceWriter.cs @@ -0,0 +1,52 @@ +// 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.IO; +using System.Linq; +using Microsoft.Diagnostics.Tracing.Stacks; + +namespace Microsoft.Diagnostics.Tools.Trace +{ + static class CollapsedStacksSourceWriter + { + internal static void Write(StackSource stackSource, string outputFilename) + { + var dict = new Dictionary(); + stackSource.ForEach(sample => { + var stack = new List(); + var stackIndex = sample.StackIndex; + var metric = sample.Metric; + while (stackIndex != StackSourceCallStackIndex.Invalid) + { + var frameName = stackSource.GetFrameName(stackSource.GetFrameIndex(stackIndex), false); + if (frameName.StartsWith("Thread (")) + break; + stack.Add(frameName); + + stackIndex = stackSource.GetCallerIndex(stackIndex); + } + stack.Reverse(); + var stackStr = string.Join(";", stack); + if (dict.TryGetValue(stackStr, out var currentValue)) + dict[stackStr] = currentValue + metric; + else + dict[stackStr] = metric; + }); + var result = dict.ToArray(); + if (result.Length == 0) + Console.WriteLine("Warning: No stacks collected."); + // sort for deterministic output + Array.Sort(result, (a, b) => a.Key.CompareTo(b.Key)); + using (var writeStream = File.CreateText(outputFilename)) + foreach (var stack in result) + { + writeStream.Write(stack.Key); + writeStream.Write(" "); + writeStream.WriteLine((int)stack.Value); + } + } + } +} diff --git a/src/Tools/dotnet-trace/CommandLine/Options/CommonOptions.cs b/src/Tools/dotnet-trace/CommandLine/Options/CommonOptions.cs index a5170569cd..58e0b7e288 100644 --- a/src/Tools/dotnet-trace/CommandLine/Options/CommonOptions.cs +++ b/src/Tools/dotnet-trace/CommandLine/Options/CommonOptions.cs @@ -37,7 +37,7 @@ public static Option FormatOption() => public static Option ConvertFormatOption() => new Option( alias: "--format", - description: $"Sets the output format for the trace file conversion.") + description: $"Sets the output format for the trace file conversion. Chromium can be used on https://ui.perfetto.dev/ or in chrome://tracing/. SpeedScope can be used on https://www.speedscope.app/. CollapsedStacks is a text format for https://github.com/brendangregg/FlameGraph.") { Argument = new Argument(name: "trace-file-format") }; diff --git a/src/Tools/dotnet-trace/TraceFileFormatConverter.cs b/src/Tools/dotnet-trace/TraceFileFormatConverter.cs index 58017d5622..d021ef9907 100644 --- a/src/Tools/dotnet-trace/TraceFileFormatConverter.cs +++ b/src/Tools/dotnet-trace/TraceFileFormatConverter.cs @@ -13,14 +13,15 @@ namespace Microsoft.Diagnostics.Tools.Trace { - internal enum TraceFileFormat { NetTrace, Speedscope, Chromium }; + internal enum TraceFileFormat { NetTrace, Speedscope, Chromium, CollapsedStacks }; internal static class TraceFileFormatConverter { private static IReadOnlyDictionary TraceFileFormatExtensions = new Dictionary() { { TraceFileFormat.NetTrace, "nettrace" }, { TraceFileFormat.Speedscope, "speedscope.json" }, - { TraceFileFormat.Chromium, "chromium.json" } + { TraceFileFormat.Chromium, "chromium.json" }, + { TraceFileFormat.CollapsedStacks, "stacks" } }; public static void ConvertToFormat(TraceFileFormat format, string fileToConvert, string outputFilename = "") @@ -37,6 +38,7 @@ public static void ConvertToFormat(TraceFileFormat format, string fileToConvert, break; case TraceFileFormat.Speedscope: case TraceFileFormat.Chromium: + case TraceFileFormat.CollapsedStacks: try { Convert(format, fileToConvert, outputFilename); @@ -88,6 +90,9 @@ private static void Convert(TraceFileFormat format, string fileToConvert, string case TraceFileFormat.Chromium: ChromiumStackSourceWriter.WriteStackViewAsJson(stackSource, outputFilename, compress: false); break; + case TraceFileFormat.CollapsedStacks: + CollapsedStacksSourceWriter.Write(stackSource, outputFilename); + break; default: // we should never get here throw new ArgumentException($"Invalid TraceFileFormat \"{format}\"");