Re: [RFC] perf script: add flamegraph.py script

From: Andreas Gerstmayr
Date: Thu Feb 27 2020 - 15:17:04 EST


On 25.02.20 21:36, Andreas Gerstmayr wrote:
On 25.02.20 21:20, Jiri Olsa wrote:
On Tue, Feb 25, 2020 at 09:03:19PM +0100, Andreas Gerstmayr wrote:
On 25.02.20 20:54, Jiri Olsa wrote:
On Fri, Feb 21, 2020 at 06:55:01PM +0100, Andreas Gerstmayr wrote:
Usage:

ÂÂÂÂÂ perf script flamegraph -a -F 99 sleep 60

Alternative usage:

ÂÂÂÂÂ perf record -a -g -F 99 sleep 60
ÂÂÂÂÂ perf script report flamegraph

nice, could this output the output file, like:

ÂÂÂÂÂÂ # perf script report flamegraph --output krava.html
ÂÂÂÂÂÂ dumping data to krava.html

I meant the actual line ^^^^, saying that it's writing to the file

Ah! Sorry, I misunderstood.
Yep, sure, I can add that.

I also have one other change lined up to reduce the JSON output, and I'm testing it with huge flamegraphs right now. Will send an update this week.

Ok, I'll need some more time to properly test this with huge flame graphs and different browsers. I'll get back to this in ~2 weeks after my PTO.


Cheers,
Andreas




Cheers,
Andreas



thanks,
jirka


or something in that sense

other than that it looks good to me

Yes, it's already implemented.

$ perf script report flamegraph --output krava.html

writes the output to krava.html

$ perf script report flamegraph --help

shows the supported arguments.

The only gotcha is that you need to have a perf.data in the same directory
when calling this command, otherwise perf complains about a missing
perf.data and doesn't call the flamegraph.py script.


Cheers,
Andreas



thanks,
jirka



Signed-off-by: Andreas Gerstmayr <agerstmayr@xxxxxxxxxx>
Cc: Peter Zijlstra <peterz@xxxxxxxxxxxxx>
Cc: Ingo Molnar <mingo@xxxxxxxxxx>
Cc: Arnaldo Carvalho de Melo <acme@xxxxxxxxxx>
Cc: Mark Rutland <mark.rutland@xxxxxxx>
Cc: Alexander Shishkin <alexander.shishkin@xxxxxxxxxxxxxxx>
Cc: Jiri Olsa <jolsa@xxxxxxxxxx>
Cc: Namhyung Kim <namhyung@xxxxxxxxxx>
---

I'm currently preparing packages for d3-flame-graph. For Fedora, the copr
at
https://copr.fedorainfracloud.org/coprs/agerstmayr/reviews/package/js-d3-flame-graph/
can be installed, or alternatively the prebuilt standalone d3-flame-graph
template can be downloaded from
https://raw.githubusercontent.com/andreasgerstmayr/specs/master/reviews/js-d3-flame-graph/template.html
and moved into /usr/share/d3-flame-graph/template.html

ÂÂ .../perf/scripts/python/bin/flamegraph-record |ÂÂ 2 +
ÂÂ .../perf/scripts/python/bin/flamegraph-report |ÂÂ 3 +
ÂÂ tools/perf/scripts/python/flamegraph.pyÂÂÂÂÂÂ | 117 ++++++++++++++++++
ÂÂ 3 files changed, 122 insertions(+)
ÂÂ create mode 100755 tools/perf/scripts/python/bin/flamegraph-record
ÂÂ create mode 100755 tools/perf/scripts/python/bin/flamegraph-report
ÂÂ create mode 100755 tools/perf/scripts/python/flamegraph.py

diff --git a/tools/perf/scripts/python/bin/flamegraph-record b/tools/perf/scripts/python/bin/flamegraph-record
new file mode 100755
index 000000000000..725d66e71570
--- /dev/null
+++ b/tools/perf/scripts/python/bin/flamegraph-record
@@ -0,0 +1,2 @@
+#!/usr/bin/sh
+perf record -g "$@"
diff --git a/tools/perf/scripts/python/bin/flamegraph-report b/tools/perf/scripts/python/bin/flamegraph-report
new file mode 100755
index 000000000000..b1a79afd903b
--- /dev/null
+++ b/tools/perf/scripts/python/bin/flamegraph-report
@@ -0,0 +1,3 @@
+#!/usr/bin/sh
+# description: create flame graphs
+perf script -s "$PERF_EXEC_PATH"/scripts/python/flamegraph.py -- "$@"
diff --git a/tools/perf/scripts/python/flamegraph.py b/tools/perf/scripts/python/flamegraph.py
new file mode 100755
index 000000000000..2e9139ef2c4a
--- /dev/null
+++ b/tools/perf/scripts/python/flamegraph.py
@@ -0,0 +1,117 @@
+# flamegraph.py - create flame graphs from perf samples
+# SPDX-License-Identifier: GPL-2.0
+#
+# Usage:
+#
+#Â perf record -a -g -F 99 sleep 60
+#Â perf script report flamegraph
+#
+# Combined data collection and flamegraph generation:
+#
+#Â perf script flamegraph -a -F 99 sleep 60
+#
+# Written by Andreas Gerstmayr <agerstmayr@xxxxxxxxxx>
+# Flame Graphs invented by Brendan Gregg <bgregg@xxxxxxxxxxx>
+# Works in tandem with d3-flame-graph by Martin Spier <mspier@xxxxxxxxxxx>
+
+import sys
+import os
+import argparse
+import json
+
+
+class Node:
+ÂÂÂ def __init__(self, name, libtype=""):
+ÂÂÂÂÂÂÂ self.name = name
+ÂÂÂÂÂÂÂ self.libtype = libtype
+ÂÂÂÂÂÂÂ self.value = 0
+ÂÂÂÂÂÂÂ self.children = []
+
+
+class FlameGraphCLI:
+ÂÂÂ def __init__(self, args):
+ÂÂÂÂÂÂÂ self.args = args
+ÂÂÂÂÂÂÂ self.stack = Node("root")
+
+ÂÂÂÂÂÂÂ if self.args.format == "html" and \
+ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ not os.path.isfile(self.args.template):
+ÂÂÂÂÂÂÂÂÂÂÂ print(f"Flame Graph template '{self.args.template}' does not " +
+ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ f"exist. Please install the d3-flame-graph package, " +
+ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ f"specify an existing flame graph template " +
+ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ f"(--template PATH) or another output format " +
+ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ f"(--format FORMAT).", file=sys.stderr)
+ÂÂÂÂÂÂÂÂÂÂÂ sys.exit(1)
+
+ÂÂÂ def find_or_create_node(self, node, name, dso):
+ÂÂÂÂÂÂÂ libtype = "kernel" if dso == "[kernel.kallsyms]" else ""
+ÂÂÂÂÂÂÂ if name is None:
+ÂÂÂÂÂÂÂÂÂÂÂ name = "[unknown]"
+
+ÂÂÂÂÂÂÂ for child in node.children:
+ÂÂÂÂÂÂÂÂÂÂÂ if child.name == name and child.libtype == libtype:
+ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ return child
+
+ÂÂÂÂÂÂÂ child = Node(name, libtype)
+ÂÂÂÂÂÂÂ node.children.append(child)
+ÂÂÂÂÂÂÂ return child
+
+ÂÂÂ def process_event(self, event):
+ÂÂÂÂÂÂÂ node = self.find_or_create_node(self.stack, event["comm"], None)
+ÂÂÂÂÂÂÂ if "callchain" in event:
+ÂÂÂÂÂÂÂÂÂÂÂ for entry in reversed(event['callchain']):
+ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ node = self.find_or_create_node(
+ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ node, entry.get("sym", {}).get("name"), event.get("dso"))
+ÂÂÂÂÂÂÂ else:
+ÂÂÂÂÂÂÂÂÂÂÂ node = self.find_or_create_node(
+ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ node, entry.get("symbol"), event.get("dso"))
+ÂÂÂÂÂÂÂ node.value += 1
+
+ÂÂÂ def trace_end(self):
+ÂÂÂÂÂÂÂ def encoder(x): return x.__dict__
+ÂÂÂÂÂÂÂ json_str = json.dumps(self.stack, default=encoder,
+ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ indent=self.args.indent)
+
+ÂÂÂÂÂÂÂ if self.args.format == "html":
+ÂÂÂÂÂÂÂÂÂÂÂ try:
+ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ with open(self.args.template) as f:
+ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ output_str = f.read().replace("/** @flamegraph_params **/",
+ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ json_str)
+ÂÂÂÂÂÂÂÂÂÂÂ except IOError as e:
+ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ print(f"Error reading template file: {e}", file=sys.stderr)
+ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ sys.exit(1)
+ÂÂÂÂÂÂÂÂÂÂÂ output_fn = self.args.output or "flamegraph.html"
+ÂÂÂÂÂÂÂ else:
+ÂÂÂÂÂÂÂÂÂÂÂ output_str = json_str
+ÂÂÂÂÂÂÂÂÂÂÂ output_fn = self.args.output or "stacks.json"
+
+ÂÂÂÂÂÂÂ if output_fn == "-":
+ÂÂÂÂÂÂÂÂÂÂÂ sys.stdout.write(output_str)
+ÂÂÂÂÂÂÂ else:
+ÂÂÂÂÂÂÂÂÂÂÂ try:
+ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ with open(output_fn, "w") as out:
+ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ out.write(output_str)
+ÂÂÂÂÂÂÂÂÂÂÂ except IOError as e:
+ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ print(f"Error writing output file: {e}", file=sys.stderr)
+ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ sys.exit(1)
+
+
+if __name__ == "__main__":
+ÂÂÂ parser = argparse.ArgumentParser(description="Create flame graphs.")
+ÂÂÂ parser.add_argument("-F", "--format",
+ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ default="html", choices=["json", "html"],
+ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ help="output file format")
+ÂÂÂ parser.add_argument("-o", "--output",
+ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ help="output file name")
+ÂÂÂ parser.add_argument("--indent",
+ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ type=int, help="JSON indentation")
+ÂÂÂ parser.add_argument("--template",
+ default="/usr/share/d3-flame-graph/template.html",
+ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ help="path to flamegraph HTML template")
+ÂÂÂ parser.add_argument("-i", "--input",
+ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ help=argparse.SUPPRESS)
+
+ÂÂÂ args = parser.parse_args()
+ÂÂÂ cli = FlameGraphCLI(args)
+
+ÂÂÂ process_event = cli.process_event
+ÂÂÂ trace_end = cli.trace_end
--
2.24.1