^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 1) # flamegraph.py - create flame graphs from perf samples
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 2) # SPDX-License-Identifier: GPL-2.0
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 3) #
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 4) # Usage:
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 5) #
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 6) # perf record -a -g -F 99 sleep 60
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 7) # perf script report flamegraph
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 8) #
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 9) # Combined:
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 10) #
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 11) # perf script flamegraph -a -F 99 sleep 60
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 12) #
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 13) # Written by Andreas Gerstmayr <agerstmayr@redhat.com>
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 14) # Flame Graphs invented by Brendan Gregg <bgregg@netflix.com>
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 15) # Works in tandem with d3-flame-graph by Martin Spier <mspier@netflix.com>
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 16)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 17) from __future__ import print_function
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 18) import sys
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 19) import os
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 20) import io
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 21) import argparse
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 22) import json
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 23)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 24)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 25) class Node:
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 26) def __init__(self, name, libtype=""):
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 27) self.name = name
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 28) self.libtype = libtype
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 29) self.value = 0
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 30) self.children = []
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 31)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 32) def toJSON(self):
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 33) return {
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 34) "n": self.name,
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 35) "l": self.libtype,
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 36) "v": self.value,
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 37) "c": self.children
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 38) }
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 39)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 40)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 41) class FlameGraphCLI:
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 42) def __init__(self, args):
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 43) self.args = args
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 44) self.stack = Node("root")
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 45)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 46) if self.args.format == "html" and \
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 47) not os.path.isfile(self.args.template):
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 48) print("Flame Graph template {} does not exist. Please install "
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 49) "the js-d3-flame-graph (RPM) or libjs-d3-flame-graph (deb) "
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 50) "package, specify an existing flame graph template "
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 51) "(--template PATH) or another output format "
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 52) "(--format FORMAT).".format(self.args.template),
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 53) file=sys.stderr)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 54) sys.exit(1)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 55)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 56) def find_or_create_node(self, node, name, dso):
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 57) libtype = "kernel" if dso == "[kernel.kallsyms]" else ""
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 58) if name is None:
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 59) name = "[unknown]"
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 60)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 61) for child in node.children:
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 62) if child.name == name and child.libtype == libtype:
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 63) return child
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 64)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 65) child = Node(name, libtype)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 66) node.children.append(child)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 67) return child
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 68)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 69) def process_event(self, event):
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 70) node = self.find_or_create_node(self.stack, event["comm"], None)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 71) if "callchain" in event:
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 72) for entry in reversed(event['callchain']):
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 73) node = self.find_or_create_node(
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 74) node, entry.get("sym", {}).get("name"), event.get("dso"))
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 75) else:
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 76) node = self.find_or_create_node(
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 77) node, entry.get("symbol"), event.get("dso"))
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 78) node.value += 1
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 79)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 80) def trace_end(self):
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 81) json_str = json.dumps(self.stack, default=lambda x: x.toJSON())
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 82)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 83) if self.args.format == "html":
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 84) try:
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 85) with io.open(self.args.template, encoding="utf-8") as f:
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 86) output_str = f.read().replace("/** @flamegraph_json **/",
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 87) json_str)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 88) except IOError as e:
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 89) print("Error reading template file: {}".format(e), file=sys.stderr)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 90) sys.exit(1)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 91) output_fn = self.args.output or "flamegraph.html"
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 92) else:
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 93) output_str = json_str
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 94) output_fn = self.args.output or "stacks.json"
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 95)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 96) if output_fn == "-":
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 97) with io.open(sys.stdout.fileno(), "w", encoding="utf-8", closefd=False) as out:
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 98) out.write(output_str)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 99) else:
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 100) print("dumping data to {}".format(output_fn))
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 101) try:
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 102) with io.open(output_fn, "w", encoding="utf-8") as out:
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 103) out.write(output_str)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 104) except IOError as e:
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 105) print("Error writing output file: {}".format(e), file=sys.stderr)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 106) sys.exit(1)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 107)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 108)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 109) if __name__ == "__main__":
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 110) parser = argparse.ArgumentParser(description="Create flame graphs.")
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 111) parser.add_argument("-f", "--format",
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 112) default="html", choices=["json", "html"],
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 113) help="output file format")
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 114) parser.add_argument("-o", "--output",
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 115) help="output file name")
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 116) parser.add_argument("--template",
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 117) default="/usr/share/d3-flame-graph/d3-flamegraph-base.html",
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 118) help="path to flamegraph HTML template")
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 119) parser.add_argument("-i", "--input",
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 120) help=argparse.SUPPRESS)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 121)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 122) args = parser.parse_args()
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 123) cli = FlameGraphCLI(args)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 124)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 125) process_event = cli.process_event
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 126) trace_end = cli.trace_end