15287f926SAndreas Gerstmayr# flamegraph.py - create flame graphs from perf samples 25287f926SAndreas Gerstmayr# SPDX-License-Identifier: GPL-2.0 35287f926SAndreas Gerstmayr# 45287f926SAndreas Gerstmayr# Usage: 55287f926SAndreas Gerstmayr# 65287f926SAndreas Gerstmayr# perf record -a -g -F 99 sleep 60 75287f926SAndreas Gerstmayr# perf script report flamegraph 85287f926SAndreas Gerstmayr# 95287f926SAndreas Gerstmayr# Combined: 105287f926SAndreas Gerstmayr# 115287f926SAndreas Gerstmayr# perf script flamegraph -a -F 99 sleep 60 125287f926SAndreas Gerstmayr# 135287f926SAndreas Gerstmayr# Written by Andreas Gerstmayr <[email protected]> 145287f926SAndreas Gerstmayr# Flame Graphs invented by Brendan Gregg <[email protected]> 155287f926SAndreas Gerstmayr# Works in tandem with d3-flame-graph by Martin Spier <[email protected]> 16*c611e4f2SAndreas Gerstmayr# 17*c611e4f2SAndreas Gerstmayr# pylint: disable=missing-module-docstring 18*c611e4f2SAndreas Gerstmayr# pylint: disable=missing-class-docstring 19*c611e4f2SAndreas Gerstmayr# pylint: disable=missing-function-docstring 205287f926SAndreas Gerstmayr 215287f926SAndreas Gerstmayrfrom __future__ import print_function 225287f926SAndreas Gerstmayrimport sys 235287f926SAndreas Gerstmayrimport os 24c42ad5d4SAndreas Gerstmayrimport io 255287f926SAndreas Gerstmayrimport argparse 265287f926SAndreas Gerstmayrimport json 27*c611e4f2SAndreas Gerstmayrimport subprocess 285287f926SAndreas Gerstmayr 29*c611e4f2SAndreas Gerstmayr# pylint: disable=too-few-public-methods 305287f926SAndreas Gerstmayrclass Node: 31*c611e4f2SAndreas Gerstmayr def __init__(self, name, libtype): 325287f926SAndreas Gerstmayr self.name = name 33*c611e4f2SAndreas Gerstmayr # "root" | "kernel" | "" 34*c611e4f2SAndreas Gerstmayr # "" indicates user space 355287f926SAndreas Gerstmayr self.libtype = libtype 365287f926SAndreas Gerstmayr self.value = 0 375287f926SAndreas Gerstmayr self.children = [] 385287f926SAndreas Gerstmayr 39*c611e4f2SAndreas Gerstmayr def to_json(self): 405287f926SAndreas Gerstmayr return { 415287f926SAndreas Gerstmayr "n": self.name, 425287f926SAndreas Gerstmayr "l": self.libtype, 435287f926SAndreas Gerstmayr "v": self.value, 445287f926SAndreas Gerstmayr "c": self.children 455287f926SAndreas Gerstmayr } 465287f926SAndreas Gerstmayr 475287f926SAndreas Gerstmayr 485287f926SAndreas Gerstmayrclass FlameGraphCLI: 495287f926SAndreas Gerstmayr def __init__(self, args): 505287f926SAndreas Gerstmayr self.args = args 51*c611e4f2SAndreas Gerstmayr self.stack = Node("all", "root") 525287f926SAndreas Gerstmayr 535287f926SAndreas Gerstmayr if self.args.format == "html" and \ 545287f926SAndreas Gerstmayr not os.path.isfile(self.args.template): 555287f926SAndreas Gerstmayr print("Flame Graph template {} does not exist. Please install " 565287f926SAndreas Gerstmayr "the js-d3-flame-graph (RPM) or libjs-d3-flame-graph (deb) " 575287f926SAndreas Gerstmayr "package, specify an existing flame graph template " 585287f926SAndreas Gerstmayr "(--template PATH) or another output format " 595287f926SAndreas Gerstmayr "(--format FORMAT).".format(self.args.template), 605287f926SAndreas Gerstmayr file=sys.stderr) 615287f926SAndreas Gerstmayr sys.exit(1) 625287f926SAndreas Gerstmayr 63*c611e4f2SAndreas Gerstmayr @staticmethod 64*c611e4f2SAndreas Gerstmayr def get_libtype_from_dso(dso): 65*c611e4f2SAndreas Gerstmayr """ 66*c611e4f2SAndreas Gerstmayr when kernel-debuginfo is installed, 67*c611e4f2SAndreas Gerstmayr dso points to /usr/lib/debug/lib/modules/*/vmlinux 68*c611e4f2SAndreas Gerstmayr """ 69*c611e4f2SAndreas Gerstmayr if dso and (dso == "[kernel.kallsyms]" or dso.endswith("/vmlinux")): 70*c611e4f2SAndreas Gerstmayr return "kernel" 715287f926SAndreas Gerstmayr 72*c611e4f2SAndreas Gerstmayr return "" 73*c611e4f2SAndreas Gerstmayr 74*c611e4f2SAndreas Gerstmayr @staticmethod 75*c611e4f2SAndreas Gerstmayr def find_or_create_node(node, name, libtype): 765287f926SAndreas Gerstmayr for child in node.children: 77*c611e4f2SAndreas Gerstmayr if child.name == name: 785287f926SAndreas Gerstmayr return child 795287f926SAndreas Gerstmayr 805287f926SAndreas Gerstmayr child = Node(name, libtype) 815287f926SAndreas Gerstmayr node.children.append(child) 825287f926SAndreas Gerstmayr return child 835287f926SAndreas Gerstmayr 845287f926SAndreas Gerstmayr def process_event(self, event): 85*c611e4f2SAndreas Gerstmayr pid = event.get("sample", {}).get("pid", 0) 86*c611e4f2SAndreas Gerstmayr # event["dso"] sometimes contains /usr/lib/debug/lib/modules/*/vmlinux 87*c611e4f2SAndreas Gerstmayr # for user-space processes; let's use pid for kernel or user-space distinction 88*c611e4f2SAndreas Gerstmayr if pid == 0: 89*c611e4f2SAndreas Gerstmayr comm = event["comm"] 90*c611e4f2SAndreas Gerstmayr libtype = "kernel" 915287f926SAndreas Gerstmayr else: 92*c611e4f2SAndreas Gerstmayr comm = "{} ({})".format(event["comm"], pid) 93*c611e4f2SAndreas Gerstmayr libtype = "" 94*c611e4f2SAndreas Gerstmayr node = self.find_or_create_node(self.stack, comm, libtype) 95*c611e4f2SAndreas Gerstmayr 96*c611e4f2SAndreas Gerstmayr if "callchain" in event: 97*c611e4f2SAndreas Gerstmayr for entry in reversed(event["callchain"]): 98*c611e4f2SAndreas Gerstmayr name = entry.get("sym", {}).get("name", "[unknown]") 99*c611e4f2SAndreas Gerstmayr libtype = self.get_libtype_from_dso(entry.get("dso")) 100*c611e4f2SAndreas Gerstmayr node = self.find_or_create_node(node, name, libtype) 101*c611e4f2SAndreas Gerstmayr else: 102*c611e4f2SAndreas Gerstmayr name = event.get("symbol", "[unknown]") 103*c611e4f2SAndreas Gerstmayr libtype = self.get_libtype_from_dso(event.get("dso")) 104*c611e4f2SAndreas Gerstmayr node = self.find_or_create_node(node, name, libtype) 1055287f926SAndreas Gerstmayr node.value += 1 1065287f926SAndreas Gerstmayr 107*c611e4f2SAndreas Gerstmayr def get_report_header(self): 108*c611e4f2SAndreas Gerstmayr if self.args.input == "-": 109*c611e4f2SAndreas Gerstmayr # when this script is invoked with "perf script flamegraph", 110*c611e4f2SAndreas Gerstmayr # no perf.data is created and we cannot read the header of it 111*c611e4f2SAndreas Gerstmayr return "" 112*c611e4f2SAndreas Gerstmayr 113*c611e4f2SAndreas Gerstmayr try: 114*c611e4f2SAndreas Gerstmayr output = subprocess.check_output(["perf", "report", "--header-only"]) 115*c611e4f2SAndreas Gerstmayr return output.decode("utf-8") 116*c611e4f2SAndreas Gerstmayr except Exception as err: # pylint: disable=broad-except 117*c611e4f2SAndreas Gerstmayr print("Error reading report header: {}".format(err), file=sys.stderr) 118*c611e4f2SAndreas Gerstmayr return "" 119*c611e4f2SAndreas Gerstmayr 1205287f926SAndreas Gerstmayr def trace_end(self): 121*c611e4f2SAndreas Gerstmayr stacks_json = json.dumps(self.stack, default=lambda x: x.to_json()) 1225287f926SAndreas Gerstmayr 1235287f926SAndreas Gerstmayr if self.args.format == "html": 124*c611e4f2SAndreas Gerstmayr report_header = self.get_report_header() 125*c611e4f2SAndreas Gerstmayr options = { 126*c611e4f2SAndreas Gerstmayr "colorscheme": self.args.colorscheme, 127*c611e4f2SAndreas Gerstmayr "context": report_header 128*c611e4f2SAndreas Gerstmayr } 129*c611e4f2SAndreas Gerstmayr options_json = json.dumps(options) 130*c611e4f2SAndreas Gerstmayr 1315287f926SAndreas Gerstmayr try: 132*c611e4f2SAndreas Gerstmayr with io.open(self.args.template, encoding="utf-8") as template: 133*c611e4f2SAndreas Gerstmayr output_str = ( 134*c611e4f2SAndreas Gerstmayr template.read() 135*c611e4f2SAndreas Gerstmayr .replace("/** @options_json **/", options_json) 136*c611e4f2SAndreas Gerstmayr .replace("/** @flamegraph_json **/", stacks_json) 137*c611e4f2SAndreas Gerstmayr ) 138*c611e4f2SAndreas Gerstmayr except IOError as err: 139*c611e4f2SAndreas Gerstmayr print("Error reading template file: {}".format(err), file=sys.stderr) 1405287f926SAndreas Gerstmayr sys.exit(1) 1415287f926SAndreas Gerstmayr output_fn = self.args.output or "flamegraph.html" 1425287f926SAndreas Gerstmayr else: 143*c611e4f2SAndreas Gerstmayr output_str = stacks_json 1445287f926SAndreas Gerstmayr output_fn = self.args.output or "stacks.json" 1455287f926SAndreas Gerstmayr 1465287f926SAndreas Gerstmayr if output_fn == "-": 147c42ad5d4SAndreas Gerstmayr with io.open(sys.stdout.fileno(), "w", encoding="utf-8", closefd=False) as out: 148c42ad5d4SAndreas Gerstmayr out.write(output_str) 1495287f926SAndreas Gerstmayr else: 1505287f926SAndreas Gerstmayr print("dumping data to {}".format(output_fn)) 1515287f926SAndreas Gerstmayr try: 152c42ad5d4SAndreas Gerstmayr with io.open(output_fn, "w", encoding="utf-8") as out: 1535287f926SAndreas Gerstmayr out.write(output_str) 154*c611e4f2SAndreas Gerstmayr except IOError as err: 155*c611e4f2SAndreas Gerstmayr print("Error writing output file: {}".format(err), file=sys.stderr) 1565287f926SAndreas Gerstmayr sys.exit(1) 1575287f926SAndreas Gerstmayr 1585287f926SAndreas Gerstmayr 1595287f926SAndreas Gerstmayrif __name__ == "__main__": 1605287f926SAndreas Gerstmayr parser = argparse.ArgumentParser(description="Create flame graphs.") 1615287f926SAndreas Gerstmayr parser.add_argument("-f", "--format", 1625287f926SAndreas Gerstmayr default="html", choices=["json", "html"], 1635287f926SAndreas Gerstmayr help="output file format") 1645287f926SAndreas Gerstmayr parser.add_argument("-o", "--output", 1655287f926SAndreas Gerstmayr help="output file name") 1665287f926SAndreas Gerstmayr parser.add_argument("--template", 1675287f926SAndreas Gerstmayr default="/usr/share/d3-flame-graph/d3-flamegraph-base.html", 1685287f926SAndreas Gerstmayr help="path to flame graph HTML template") 169*c611e4f2SAndreas Gerstmayr parser.add_argument("--colorscheme", 170*c611e4f2SAndreas Gerstmayr default="blue-green", 171*c611e4f2SAndreas Gerstmayr help="flame graph color scheme", 172*c611e4f2SAndreas Gerstmayr choices=["blue-green", "orange"]) 1735287f926SAndreas Gerstmayr parser.add_argument("-i", "--input", 1745287f926SAndreas Gerstmayr help=argparse.SUPPRESS) 1755287f926SAndreas Gerstmayr 176*c611e4f2SAndreas Gerstmayr cli_args = parser.parse_args() 177*c611e4f2SAndreas Gerstmayr cli = FlameGraphCLI(cli_args) 1785287f926SAndreas Gerstmayr 1795287f926SAndreas Gerstmayr process_event = cli.process_event 1805287f926SAndreas Gerstmayr trace_end = cli.trace_end 181