# 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: # # perf script flamegraph -a -F 99 sleep 60 # # Written by Andreas Gerstmayr # Flame Graphs invented by Brendan Gregg # Works in tandem with d3-flame-graph by Martin Spier # # pylint: disable=missing-module-docstring # pylint: disable=missing-class-docstring # pylint: disable=missing-function-docstring from __future__ import print_function import argparse import hashlib import io import json import os import subprocess import sys import urllib.request minimal_html = """
""" # pylint: disable=too-few-public-methods class Node: def __init__(self, name, libtype): self.name = name # "root" | "kernel" | "" # "" indicates user space self.libtype = libtype self.value = 0 self.children = [] def to_json(self): return { "n": self.name, "l": self.libtype, "v": self.value, "c": self.children } class FlameGraphCLI: def __init__(self, args): self.args = args self.stack = Node("all", "root") @staticmethod def get_libtype_from_dso(dso): """ when kernel-debuginfo is installed, dso points to /usr/lib/debug/lib/modules/*/vmlinux """ if dso and (dso == "[kernel.kallsyms]" or dso.endswith("/vmlinux")): return "kernel" return "" @staticmethod def find_or_create_node(node, name, libtype): for child in node.children: if child.name == name: return child child = Node(name, libtype) node.children.append(child) return child def process_event(self, event): pid = event.get("sample", {}).get("pid", 0) # event["dso"] sometimes contains /usr/lib/debug/lib/modules/*/vmlinux # for user-space processes; let's use pid for kernel or user-space distinction if pid == 0: comm = event["comm"] libtype = "kernel" else: comm = "{} ({})".format(event["comm"], pid) libtype = "" node = self.find_or_create_node(self.stack, comm, libtype) if "callchain" in event: for entry in reversed(event["callchain"]): name = entry.get("sym", {}).get("name", "[unknown]") libtype = self.get_libtype_from_dso(entry.get("dso")) node = self.find_or_create_node(node, name, libtype) else: name = event.get("symbol", "[unknown]") libtype = self.get_libtype_from_dso(event.get("dso")) node = self.find_or_create_node(node, name, libtype) node.value += 1 def get_report_header(self): if self.args.input == "-": # when this script is invoked with "perf script flamegraph", # no perf.data is created and we cannot read the header of it return "" try: output = subprocess.check_output(["perf", "report", "--header-only"]) return output.decode("utf-8") except Exception as err: # pylint: disable=broad-except print("Error reading report header: {}".format(err), file=sys.stderr) return "" def trace_end(self): stacks_json = json.dumps(self.stack, default=lambda x: x.to_json()) if self.args.format == "html": report_header = self.get_report_header() options = { "colorscheme": self.args.colorscheme, "context": report_header } options_json = json.dumps(options) template_md5sum = None if self.args.format == "html": if os.path.isfile(self.args.template): template = f"file://{self.args.template}" else: if not self.args.allow_download: print(f"""Warning: Flame Graph template '{self.args.template}' does not exist. To avoid this please install a package such as the js-d3-flame-graph or libjs-d3-flame-graph, specify an existing flame graph template (--template PATH) or use another output format (--format FORMAT).""", file=sys.stderr) if self.args.input == "-": print("""Not attempting to download Flame Graph template as script command line input is disabled due to using live mode. If you want to download the template retry without live mode. For example, use 'perf record -a -g -F 99 sleep 60' and 'perf script report flamegraph'. Alternatively, download the template from: https://cdn.jsdelivr.net/npm/d3-flame-graph@4.1.3/dist/templates/d3-flamegraph-base.html and place it at: /usr/share/d3-flame-graph/d3-flamegraph-base.html""", file=sys.stderr) quit() s = None while s != "y" and s != "n": s = input("Do you wish to download a template from cdn.jsdelivr.net? (this warning can be suppressed with --allow-download) [yn] ").lower() if s == "n": quit() template = "https://cdn.jsdelivr.net/npm/d3-flame-graph@4.1.3/dist/templates/d3-flamegraph-base.html" template_md5sum = "143e0d06ba69b8370b9848dcd6ae3f36" try: with urllib.request.urlopen(template) as template: output_str = "".join([ l.decode("utf-8") for l in template.readlines() ]) except Exception as err: print(f"Error reading template {template}: {err}\n" "a minimal flame graph will be generated", file=sys.stderr) output_str = minimal_html template_md5sum = None if template_md5sum: download_md5sum = hashlib.md5(output_str.encode("utf-8")).hexdigest() if download_md5sum != template_md5sum: s = None while s != "y" and s != "n": s = input(f"""Unexpected template md5sum. {download_md5sum} != {template_md5sum}, for: {output_str} continue?[yn] """).lower() if s == "n": quit() output_str = output_str.replace("/** @options_json **/", options_json) output_str = output_str.replace("/** @flamegraph_json **/", stacks_json) output_fn = self.args.output or "flamegraph.html" else: output_str = stacks_json output_fn = self.args.output or "stacks.json" if output_fn == "-": with io.open(sys.stdout.fileno(), "w", encoding="utf-8", closefd=False) as out: out.write(output_str) else: print("dumping data to {}".format(output_fn)) try: with io.open(output_fn, "w", encoding="utf-8") as out: out.write(output_str) except IOError as err: print("Error writing output file: {}".format(err), 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("--template", default="/usr/share/d3-flame-graph/d3-flamegraph-base.html", help="path to flame graph HTML template") parser.add_argument("--colorscheme", default="blue-green", help="flame graph color scheme", choices=["blue-green", "orange"]) parser.add_argument("-i", "--input", help=argparse.SUPPRESS) parser.add_argument("--allow-download", default=False, action="store_true", help="allow unprompted downloading of HTML template") cli_args = parser.parse_args() cli = FlameGraphCLI(cli_args) process_event = cli.process_event trace_end = cli.trace_end mples?h=linux-6.6.y&id=ff2ae607c6f329d11a3b0528801ea7474be8c3e9'>Merge tag 'spdx-5.7-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/gre...Linus Torvalds 2020-03-31Merge git://git.kernel.org/pub/scm/linux/kernel/git/netdev/net-nextLinus Torvalds 2020-03-25.gitignore: add SPDX License IdentifierMasahiro Yamada 2020-03-23samples, bpf: Refactor perf_event user program with libbpf bpf_linkDaniel T. Lee 2020-03-23samples, bpf: Move read_trace_pipe to trace_helpersDaniel T. Lee 2020-02-24media: rename VFL_TYPE_GRABBER to _VIDEOHans Verkuil 2020-02-09Merge tag 'kbuild-v5.6-2' of git://git.kernel.org/pub/scm/linux/kernel/git/ma...Linus Torvalds 2020-02-08Merge git://git.kernel.org/pub/scm/linux/kernel/git/bpf/bpfDavid S. Miller 2020-02-05samples: bpf: Allow for -ENETDOWN in xdpsockMaciej Fijalkowski 2020-02-05samples: bpf: Drop doubled variable declaration in xdpsockMaciej Fijalkowski 2020-02-04proc: convert everything to "struct proc_ops"Alexey Dobriyan 2020-02-04kbuild: rename hostprogs-y/always to hostprogs/always-yMasahiro Yamada 2020-01-30Merge tag 'drm-next-2020-01-30' of git://anongit.freedesktop.org/drm/drmLinus Torvalds 2020-01-29Merge branch 'core-urgent-for-linus' of git://git.kernel.org/pub/scm/linux/ke...Linus Torvalds 2020-01-28Merge git://git.kernel.org/pub/scm/linux/kernel/git/netdev/net-nextLinus Torvalds 2020-01-27Merge branch 'for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/liv...Linus Torvalds 2020-01-23Merge git://git.kernel.org/pub/scm/linux/kernel/git/bpf/bpf-nextDavid S. Miller 2020-01-22objtool: Skip samples subdirectoryJosh Poimboeuf 2020-01-20samples/bpf: Use consistent include paths for libbpfToke Høiland-Jørgensen 2020-01-20samples/bpf: Don't try to remove user's homedir on cleanToke Høiland-Jørgensen 2020-01-20Backmerge v5.5-rc7 into drm-nextDave Airlie 2020-01-17livepatch: Handle allocation failure in the sample of shadow variable APIPetr Mladek 2020-01-17livepatch/samples/selftest: Use klp_shadow_alloc() API correctlyPetr Mladek 2020-01-17livepatch/sample: Use the right type for the leaking data pointerPetr Mladek 2020-01-16devmap: Adjust tracepoint for map-less queue flushJesper Dangaard Brouer 2020-01-09Merge git://git.kernel.org/pub/scm/linux/kernel/git/netdev/netDavid S. Miller 2020-01-02samples/seccomp: Zero out members based on seccomp_notif_sizesSargun Dhillon 2019-12-27Merge git://git.kernel.org/pub/scm/linux/kernel/git/bpf/bpf-nextDavid S. Miller 2019-12-22Merge git://git.kernel.org/pub/scm/linux/kernel/git/netdev/netLinus Torvalds 2019-12-21samples/trace_printk: Wait for IRQ work to finishSven Schnelle 2019-12-20samples/bpf: Xdp_redirect_cpu fix missing tracepoint attachJesper Dangaard Brouer 2019-12-20samples/bpf: xdpsock: Add option to specify transmit fill patternJay Jayatheerthan 2019-12-20samples/bpf: xdpsock: Add option to specify tx packet sizeJay Jayatheerthan 2019-12-20samples/bpf: xdpsock: Add option to specify number of packets to sendJay Jayatheerthan 2019-12-20samples/bpf: xdpsock: Add option to specify batch sizeJay Jayatheerthan 2019-12-20samples/bpf: xdpsock: Use common code to handle signal and main exitJay Jayatheerthan 2019-12-20samples/bpf: xdpsock: Add duration option to specify how long to runJay Jayatheerthan 2019-12-17Merge tag 'drm-misc-next-2019-12-16' of git://anongit.freedesktop.org/drm/drm...Daniel Vetter 2019-12-16samples/bpf: Attach XDP programs in driver mode by defaultToke Høiland-Jørgensen 2019-12-16samples/bpf: Set -fno-stack-protector when building BPF programsToke Høiland-Jørgensen 2019-12-16samples/bpf: Add missing -lz to TPROGS_LDLIBSToke Høiland-Jørgensen 2019-12-16samples/bpf: Reintroduce missed build targetsPrashant Bhole 2019-12-11samples: bpf: fix syscall_tp due to unused syscallDaniel T. Lee 2019-12-11samples: bpf: Replace symbol compare of trace_eventDaniel T. Lee 2019-12-10samples: vfio-mdev: constify fb opsJani Nikula 2019-12-04samples/bpf: Fix broken xdp_rxq_info due to map order assumptionsJesper Dangaard Brouer 2019-11-27Merge tag 'trace-v5.5' of git://git.kernel.org/pub/scm/linux/kernel/git/roste...Linus Torvalds 2019-11-27Merge tag 'char-misc-5.5-rc1' of git://git.kernel.org/pub/scm/linux/kernel/gi...Linus Torvalds