From 6cfe875939e7f386cdd13d26794455b215ecd8d1 Mon Sep 17 00:00:00 2001 From: Ryan Gonzalez Date: Sun, 20 Aug 2023 19:16:18 -0500 Subject: [PATCH] converter: Add support for outputting folded stacks and flamegraphs --- .gitignore | 3 ++- README.md | 31 +++++++++++++++++++++-- tools/alys_converter.cr | 55 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 86 insertions(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index 0d03a79..6b42623 100644 --- a/.gitignore +++ b/.gitignore @@ -12,6 +12,7 @@ *.alys.* .gdb_history /demo -profile*.svg +*.folded +*.svg tools/llvm-project-* tools/pprof/profile.proto diff --git a/README.md b/README.md index 78f24ce..6c84d2d 100644 --- a/README.md +++ b/README.md @@ -44,8 +44,9 @@ ALYS_TRACING=file:myfile.alys ./myapp ### Examing Traces with `alys_converter` -The `.alys` file is in a custom binary format, but it can be converted into a -JSON file or a Go pprof file via: +The `.alys` file is in a custom binary format, but it can be converted into +JSON, Go pprof, folded stacks (for use with flamegraph), or a direct flamegraph +via: ```bash # Outputs myfile.alys.json: @@ -60,6 +61,16 @@ bin/alys_converter --indent myfile.alys myfile.json bin/alys_converter -f pprof myfile.alys # You can rename the output file: bin/alys_converter -f pprof myfile.alys myfile.pb.gz + +# Outputs myfile.folded (folded stacks): +bin/alys_converter -f folded-stacks myfile.alys +# You can rename the output file: +bin/alys_converter -f folded-stacks myfile.alys myfile.folded + +# Outputs myfile.svg (flamegraph, via inferno-flamegraph): +bin/alys_converter -f inferno-flamegraph myfile.alys +# You can rename the output file: +bin/alys_converter -f inferno-flamegraph myfile.alys myfile.svg ``` (Note that .alys files are only compatible with an identical alys_converter @@ -130,6 +141,22 @@ on `PORT`, use: pprof -http localhost:PORT myfile.pb.gz ``` +#### Folded stacks and Inferno + +Folded stacks files are usually converted to flamegraphs +[flamegraphs](https://www.brendangregg.com/flamegraphs.html); you can also go +straight from .alys -> flamegraph via the `inferno-flamegraph` format (requires +[Inferno](https://github.com/jonhoo/inferno) to be installed). In the latter +case, additional options can be passed to the `inferno-flamegraph` CLI via +`--inferno-opt`: + +```bash +# Pass --flamechart and --title=test to inferno-flamegraph: +bin/alys_converter -f inferno-flamegraph myfile.alys \ + --inferno-opt flamechart --inferno-opt title=test + +``` + ### Backtraces By default, Alys will also capture a backtrace on each allocation or diff --git a/tools/alys_converter.cr b/tools/alys_converter.cr index d8b6477..b7a16c4 100644 --- a/tools/alys_converter.cr +++ b/tools/alys_converter.cr @@ -517,9 +517,38 @@ class PProfEventWriter < EventVisitor end end +class FoldedStacksEventWriter < EventVisitor + def self.write(from reader : EventReader, to dest : IO) + visitor = FoldedStacksEventWriter.new dest + reader.read visitor + end + + protected def initialize(@io : IO) + end + + private def format_stack(stack : StackTrace) : String + stack.map { |(frame, symbol)| symbol.name || "#<#{frame.ip}>" }.join ';' + end + + def visit_alloc(event, time_offset, info, stack) + @io << "#{format_stack stack} #{info.size}\n" + end + + def visit_realloc(event, time_offset, prev_info, info, stack) + unless info.size <= prev_info.size + @io << "#{format_stack stack} #{info.size - prev_info.size}\n" + end + end + + def visit_free(event, time_offset, info) + end +end + enum Format JSON PProf + FoldedStacks + InfernoFlamegraph def ext case self @@ -527,12 +556,17 @@ enum Format "json" when PProf "pb.gz" + when FoldedStacks + "folded" + when InfernoFlamegraph + "svg" end end end struct Options property indent_json = false + property inferno_opts = [] of String property symbolize_with : String? = nil property format = Format::JSON end @@ -544,6 +578,23 @@ def convert(*, from reader : EventReader, to dest : IO, options : Options) indent_json: options.indent_json when Format::PProf PProfEventWriter.write from: reader, to: dest + when Format::FoldedStacks + FoldedStacksEventWriter.write from: reader, to: dest + when Format::InfernoFlamegraph + IO.pipe do |stacks_reader, stacks_writer| + p = Process.new "inferno-flamegraph", + options.inferno_opts.map { |o| "--#{o}" }, + input: stacks_reader, + output: dest, + error: :inherit + spawn do + FoldedStacksEventWriter.write from: reader, to: stacks_writer + stacks_writer.close + end + + status = p.wait + raise "inferno-flamegraph failed" if !status.success? + end end end @@ -573,6 +624,10 @@ def run options.indent_json = true end + parser.on "--inferno-opt=opt", "Pass the given option to inferno-flamegraph" do |opt| + options.inferno_opts << opt + end + parser.on "--symbolize=exe", "Resolve addresses with the given executable" do |exe| options.symbolize_with = exe end -- 2.45.2