Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

perf flamegraph: flamegraph.py script improvements

* display perf.data header
* display PIDs of user stacks
* added option to change color scheme
* default to blue/green color scheme to improve accessibility
* correctly identify kernel stacks when kernel-debuginfo is installed

Signed-off-by: Andreas Gerstmayr <agerstmayr@redhat.com>
Cc: Alexander Shishkin <alexander.shishkin@linux.intel.com>
Cc: Jiri Olsa <jolsa@redhat.com>
Cc: Mark Rutland <mark.rutland@arm.com>
Cc: Namhyung Kim <namhyung@kernel.org>
Cc: Peter Zijlstra <peterz@infradead.org>
Link: http://lore.kernel.org/lkml/20210830164729.116049-1-agerstmayr@redhat.com
Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>

authored by

Andreas Gerstmayr and committed by
Arnaldo Carvalho de Melo
c611e4f2 bb07d62e

+82 -28
+82 -28
tools/perf/scripts/python/flamegraph.py
··· 13 13 # Written by Andreas Gerstmayr <agerstmayr@redhat.com> 14 14 # Flame Graphs invented by Brendan Gregg <bgregg@netflix.com> 15 15 # Works in tandem with d3-flame-graph by Martin Spier <mspier@netflix.com> 16 + # 17 + # pylint: disable=missing-module-docstring 18 + # pylint: disable=missing-class-docstring 19 + # pylint: disable=missing-function-docstring 16 20 17 21 from __future__ import print_function 18 22 import sys ··· 24 20 import io 25 21 import argparse 26 22 import json 23 + import subprocess 27 24 28 - 25 + # pylint: disable=too-few-public-methods 29 26 class Node: 30 - def __init__(self, name, libtype=""): 27 + def __init__(self, name, libtype): 31 28 self.name = name 29 + # "root" | "kernel" | "" 30 + # "" indicates user space 32 31 self.libtype = libtype 33 32 self.value = 0 34 33 self.children = [] 35 34 36 - def toJSON(self): 35 + def to_json(self): 37 36 return { 38 37 "n": self.name, 39 38 "l": self.libtype, ··· 48 41 class FlameGraphCLI: 49 42 def __init__(self, args): 50 43 self.args = args 51 - self.stack = Node("root") 44 + self.stack = Node("all", "root") 52 45 53 46 if self.args.format == "html" and \ 54 47 not os.path.isfile(self.args.template): ··· 60 53 file=sys.stderr) 61 54 sys.exit(1) 62 55 63 - def find_or_create_node(self, node, name, dso): 64 - libtype = "kernel" if dso == "[kernel.kallsyms]" else "" 65 - if name is None: 66 - name = "[unknown]" 56 + @staticmethod 57 + def get_libtype_from_dso(dso): 58 + """ 59 + when kernel-debuginfo is installed, 60 + dso points to /usr/lib/debug/lib/modules/*/vmlinux 61 + """ 62 + if dso and (dso == "[kernel.kallsyms]" or dso.endswith("/vmlinux")): 63 + return "kernel" 67 64 65 + return "" 66 + 67 + @staticmethod 68 + def find_or_create_node(node, name, libtype): 68 69 for child in node.children: 69 - if child.name == name and child.libtype == libtype: 70 + if child.name == name: 70 71 return child 71 72 72 73 child = Node(name, libtype) ··· 82 67 return child 83 68 84 69 def process_event(self, event): 85 - node = self.find_or_create_node(self.stack, event["comm"], None) 86 - if "callchain" in event: 87 - for entry in reversed(event['callchain']): 88 - node = self.find_or_create_node( 89 - node, entry.get("sym", {}).get("name"), event.get("dso")) 70 + pid = event.get("sample", {}).get("pid", 0) 71 + # event["dso"] sometimes contains /usr/lib/debug/lib/modules/*/vmlinux 72 + # for user-space processes; let's use pid for kernel or user-space distinction 73 + if pid == 0: 74 + comm = event["comm"] 75 + libtype = "kernel" 90 76 else: 91 - node = self.find_or_create_node( 92 - node, entry.get("symbol"), event.get("dso")) 77 + comm = "{} ({})".format(event["comm"], pid) 78 + libtype = "" 79 + node = self.find_or_create_node(self.stack, comm, libtype) 80 + 81 + if "callchain" in event: 82 + for entry in reversed(event["callchain"]): 83 + name = entry.get("sym", {}).get("name", "[unknown]") 84 + libtype = self.get_libtype_from_dso(entry.get("dso")) 85 + node = self.find_or_create_node(node, name, libtype) 86 + else: 87 + name = event.get("symbol", "[unknown]") 88 + libtype = self.get_libtype_from_dso(event.get("dso")) 89 + node = self.find_or_create_node(node, name, libtype) 93 90 node.value += 1 94 91 92 + def get_report_header(self): 93 + if self.args.input == "-": 94 + # when this script is invoked with "perf script flamegraph", 95 + # no perf.data is created and we cannot read the header of it 96 + return "" 97 + 98 + try: 99 + output = subprocess.check_output(["perf", "report", "--header-only"]) 100 + return output.decode("utf-8") 101 + except Exception as err: # pylint: disable=broad-except 102 + print("Error reading report header: {}".format(err), file=sys.stderr) 103 + return "" 104 + 95 105 def trace_end(self): 96 - json_str = json.dumps(self.stack, default=lambda x: x.toJSON()) 106 + stacks_json = json.dumps(self.stack, default=lambda x: x.to_json()) 97 107 98 108 if self.args.format == "html": 109 + report_header = self.get_report_header() 110 + options = { 111 + "colorscheme": self.args.colorscheme, 112 + "context": report_header 113 + } 114 + options_json = json.dumps(options) 115 + 99 116 try: 100 - with io.open(self.args.template, encoding="utf-8") as f: 101 - output_str = f.read().replace("/** @flamegraph_json **/", 102 - json_str) 103 - except IOError as e: 104 - print("Error reading template file: {}".format(e), file=sys.stderr) 117 + with io.open(self.args.template, encoding="utf-8") as template: 118 + output_str = ( 119 + template.read() 120 + .replace("/** @options_json **/", options_json) 121 + .replace("/** @flamegraph_json **/", stacks_json) 122 + ) 123 + except IOError as err: 124 + print("Error reading template file: {}".format(err), file=sys.stderr) 105 125 sys.exit(1) 106 126 output_fn = self.args.output or "flamegraph.html" 107 127 else: 108 - output_str = json_str 128 + output_str = stacks_json 109 129 output_fn = self.args.output or "stacks.json" 110 130 111 131 if output_fn == "-": ··· 151 101 try: 152 102 with io.open(output_fn, "w", encoding="utf-8") as out: 153 103 out.write(output_str) 154 - except IOError as e: 155 - print("Error writing output file: {}".format(e), file=sys.stderr) 104 + except IOError as err: 105 + print("Error writing output file: {}".format(err), file=sys.stderr) 156 106 sys.exit(1) 157 107 158 108 ··· 165 115 help="output file name") 166 116 parser.add_argument("--template", 167 117 default="/usr/share/d3-flame-graph/d3-flamegraph-base.html", 168 - help="path to flamegraph HTML template") 118 + help="path to flame graph HTML template") 119 + parser.add_argument("--colorscheme", 120 + default="blue-green", 121 + help="flame graph color scheme", 122 + choices=["blue-green", "orange"]) 169 123 parser.add_argument("-i", "--input", 170 124 help=argparse.SUPPRESS) 171 125 172 - args = parser.parse_args() 173 - cli = FlameGraphCLI(args) 126 + cli_args = parser.parse_args() 127 + cli = FlameGraphCLI(cli_args) 174 128 175 129 process_event = cli.process_event 176 130 trace_end = cli.trace_end