Skip to content

Add support for writing the Brendan Gregg's flamegraph format #2768

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions documentation/dotnet-trace-instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
52 changes: 52 additions & 0 deletions src/Tools/dotnet-trace/CollapsedStacksSourceWriter.cs
Original file line number Diff line number Diff line change
@@ -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<string, float>();
stackSource.ForEach(sample => {
var stack = new List<string>();
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);
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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<TraceFileFormat>(name: "trace-file-format")
};
Expand Down
9 changes: 7 additions & 2 deletions src/Tools/dotnet-trace/TraceFileFormatConverter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<TraceFileFormat, string> TraceFileFormatExtensions = new Dictionary<TraceFileFormat, string>() {
{ 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 = "")
Expand All @@ -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);
Expand Down Expand Up @@ -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}\"");
Expand Down