# 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 import argparse import hashlib import io import json import os import subprocess import sys from typing import Dict, Optional, Union import urllib.request MINIMAL_HTML = """
""" # pylint: disable=too-few-public-methods class Node: def __init__(self, name: str, libtype: str): self.name = name # "root" | "kernel" | "" # "" indicates user space self.libtype = libtype self.value: int = 0 self.children: list[Node] = [] def to_json(self) -> Dict[str, Union[str, int, list[Dict]]]: return { "n": self.name, "l": self.libtype, "v": self.value, "c": [x.to_json() for x in self.children] } class FlameGraphCLI: def __init__(self, args): self.args = args self.stack = Node("all", "root") @staticmethod def get_libtype_from_dso(dso: Optional[str]) -> str: """ 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: Node, name: str, libtype: str) -> Node: 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) -> None: # ignore events where the event name does not match # the one specified by the user if self.args.event_name and event.get("ev_name") != self.args.event_name: return 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 = f"{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) -> str: 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: # if the file name other than perf.data is given, # we read the header of that file if self.args.input: output = subprocess.check_output(["perf", "report", "--header-only", "-i", self.args.input]) else: output = subprocess.check_output(["perf", "report", "--header-only"]) result = output.decode("utf-8") if self.args.event_name: result += "\nFocused event: " + self.args.event_name return result except Exception as err: # pylint: disable=broad-except print(f"Error reading report header: {err}", file=sys.stderr) return "" def trace_end(self) -> None: 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) sys.exit(1) s = None while s not in ["y", "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": sys.exit(1) 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 url_template: output_str = "".join([ l.decode("utf-8") for l in url_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 not in ["y", "n"]: s = input(f"""Unexpected template md5sum. {download_md5sum} != {template_md5sum}, for: {output_str} continue?[yn] """).lower() if s == "n": sys.exit(1) 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(f"dumping data to {output_fn}") try: with io.open(output_fn, "w", encoding="utf-8") as out: out.write(output_str) except IOError as err: print(f"Error writing output file: {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") parser.add_argument("-e", "--event", default="", dest="event_name", type=str, help="specify the event to generate flamegraph for") cli_args = parser.parse_args() cli = FlameGraphCLI(cli_args) process_event = cli.process_event trace_end = cli.trace_end : seq: Allow the modular sequencer registrationTakashi Iwai 2017-06-09ALSA: emu10k1: Constify hw_constraintsTakashi Iwai 2017-05-25ALSA: emu10k1: Deliver indirect-PCM transfer errorTakashi Iwai 2017-05-17ALSA: emu10k1: Get rid of set_fs() usageTakashi Iwai 2017-02-21ALSA: pci: constify snd_kcontrol_new structuresBhumika Goyal 2017-02-19ALSA: emu10k1: constify snd_emux_operators structureBhumika Goyal 2017-01-12ALSA: pci: Constify snd_rawmidi_opsTakashi Iwai 2016-11-15ALSA: emu10k1: Use workqueue instead of kthread for emu1010 fw pollingTakashi Iwai 2016-11-15ALSA: emu10k1: Simplify firmware loader codeTakashi Iwai 2016-11-15ALSA: emu10k1: Fix emu1010 dock attach checkTakashi Iwai 2016-11-13ALSA: pci: don't opencode IS_REACHABLE()Fabian Frederick 2016-09-07ALSA: squash lines for simple wrapper functionsMasahiro Yamada 2016-09-02ALSA: constify snd_pcm_ops structuresJulia Lawall 2016-01-29ALSA: emu10k1: correctly handling failed thread creationInsu Yun 2015-07-08ALSA: emu10k1: rename Audigy Analog Capture Boost controlMaciej S. Szmigiero 2015-07-08ALSA: emu10k1: enable TAD mic out on AudigyMaciej S. Szmigiero 2015-07-08ALSA: emu10k1: remove unused AC'97 mixer controls on AudigyMaciej S. Szmigiero 2015-05-29ALSA: emu10k1: Fix/cleanup ifdef CONFIG_PROC_FSTakashi Iwai 2015-04-29Merge branch 'for-linus' into for-nextTakashi Iwai 2015-04-29ALSA: emu10k1: Emu10k2 32 bit DMA modePeter Zubaj 2015-04-27Merge branch 'for-4.2' into for-nextTakashi Iwai 2015-04-27ALSA: emu10k1: Fix card shortname string buffer overflowTakashi Iwai 2015-04-16ALSA: remove deprecated use of pci apiQuentin Lambert 2015-04-13Merge branch 'for-next' into for-linusTakashi Iwai 2015-04-11ALSA: emu10k1: add toggles for E-mu 1010 optical portsMichael Gernoth 2015-04-11ALSA: emu10k1: handle dock disconnectsMichael Gernoth 2015-04-10ALSA: emu10k1: don't deadlock in proc-functionsMichael Gernoth 2015-04-05ALSA: emu10k1: add Audigy 5/RxMichael Gernoth 2015-02-12ALSA: seq: Define driver object in each driverTakashi Iwai 2015-01-11Merge branch 'topic/pci-cleanup' into for-nextTakashi Iwai 2015-01-09ALSA: emu10k1: Simplify PM callbacksTakashi Iwai 2015-01-04ALSA: emu10k1: Delete an unnecessary check before the function call "kfree"Markus Elfring 2015-01-02ALSA: emu10k1: Remove always NULL parametersLars-Peter Clausen 2014-11-11ALSA: Fix invalid kerneldoc markersTakashi Iwai 2014-11-03ALSA: emu10k1: Deletion of unnecessary checks before three function callsMarkus Elfring 2014-10-21ALSA: emu10k1: Use snd_ctl_enum_info()Takashi Iwai 2014-10-14ALSA: emu10k1: Fix deadlock in synth voice lookupTakashi Iwai 2014-08-12PCI: Remove DEFINE_PCI_DEVICE_TABLE macro useBenoit Taine 2014-03-05ALSA: emu10k1: Fix possible NULL dereferenceTakashi Iwai 2014-02-26ALSA: emu10k1: Use standard printk helpersTakashi Iwai 2014-02-26ALSA: emu10k1x: Use standard printk helpersTakashi Iwai 2014-02-12ALSA: pci: Convert to snd_card_new() with a device pointerTakashi Iwai