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