1 # flamegraph.py - create flame graphs from per 1 # flamegraph.py - create flame graphs from perf samples 2 # SPDX-License-Identifier: GPL-2.0 2 # SPDX-License-Identifier: GPL-2.0 3 # 3 # 4 # Usage: 4 # Usage: 5 # 5 # 6 # perf record -a -g -F 99 sleep 60 6 # perf record -a -g -F 99 sleep 60 7 # perf script report flamegraph 7 # perf script report flamegraph 8 # 8 # 9 # Combined: 9 # Combined: 10 # 10 # 11 # perf script flamegraph -a -F 99 sleep 60 11 # perf script flamegraph -a -F 99 sleep 60 12 # 12 # 13 # Written by Andreas Gerstmayr <agerstmayr@redh 13 # Written by Andreas Gerstmayr <agerstmayr@redhat.com> 14 # Flame Graphs invented by Brendan Gregg <bgreg 14 # Flame Graphs invented by Brendan Gregg <bgregg@netflix.com> 15 # Works in tandem with d3-flame-graph by Marti< 15 # Works in tandem with d3-flame-graph by Martin Spier <mspier@netflix.com> 16 # 16 # 17 # pylint: disable=missing-module-docstring 17 # pylint: disable=missing-module-docstring 18 # pylint: disable=missing-class-docstring 18 # pylint: disable=missing-class-docstring 19 # pylint: disable=missing-function-docstring 19 # pylint: disable=missing-function-docstring 20 20 21 from __future__ import print_function 21 from __future__ import print_function 22 import argparse !! 22 import sys 23 import hashlib !! 23 import os 24 import io 24 import io >> 25 import argparse 25 import json 26 import json 26 import os << 27 import subprocess 27 import subprocess 28 import sys << 29 import urllib.request << 30 << 31 minimal_html = """<head> << 32 <https://cdn.jsdelivr.net/npm/d3-flame-graph@ << 33 </head> << 34 <body> << 35 <div id="chart"></div> << 36 <script type="text/javascript" src="https:// << 37 <https://cdn.jsdelivr.net/npm/d3-flame-graph@ << 38 <script type="text/javascript"> << 39 const stacks = [/** @flamegraph_json **/]; << 40 // Note, options is unused. << 41 const options = [/** @options_json **/]; << 42 << 43 var chart = flamegraph(); << 44 d3.select("#chart") << 45 .datum(stacks[0]) << 46 .call(chart); << 47 </script> << 48 </body> << 49 """ << 50 28 51 # pylint: disable=too-few-public-methods 29 # pylint: disable=too-few-public-methods 52 class Node: 30 class Node: 53 def __init__(self, name, libtype): 31 def __init__(self, name, libtype): 54 self.name = name 32 self.name = name 55 # "root" | "kernel" | "" 33 # "root" | "kernel" | "" 56 # "" indicates user space 34 # "" indicates user space 57 self.libtype = libtype 35 self.libtype = libtype 58 self.value = 0 36 self.value = 0 59 self.children = [] 37 self.children = [] 60 38 61 def to_json(self): 39 def to_json(self): 62 return { 40 return { 63 "n": self.name, 41 "n": self.name, 64 "l": self.libtype, 42 "l": self.libtype, 65 "v": self.value, 43 "v": self.value, 66 "c": self.children 44 "c": self.children 67 } 45 } 68 46 69 47 70 class FlameGraphCLI: 48 class FlameGraphCLI: 71 def __init__(self, args): 49 def __init__(self, args): 72 self.args = args 50 self.args = args 73 self.stack = Node("all", "root") 51 self.stack = Node("all", "root") 74 52 >> 53 if self.args.format == "html" and \ >> 54 not os.path.isfile(self.args.template): >> 55 print("Flame Graph template {} does not exist. Please install " >> 56 "the js-d3-flame-graph (RPM) or libjs-d3-flame-graph (deb) " >> 57 "package, specify an existing flame graph template " >> 58 "(--template PATH) or another output format " >> 59 "(--format FORMAT).".format(self.args.template), >> 60 file=sys.stderr) >> 61 sys.exit(1) >> 62 75 @staticmethod 63 @staticmethod 76 def get_libtype_from_dso(dso): 64 def get_libtype_from_dso(dso): 77 """ 65 """ 78 when kernel-debuginfo is installed, 66 when kernel-debuginfo is installed, 79 dso points to /usr/lib/debug/lib/modul 67 dso points to /usr/lib/debug/lib/modules/*/vmlinux 80 """ 68 """ 81 if dso and (dso == "[kernel.kallsyms]" 69 if dso and (dso == "[kernel.kallsyms]" or dso.endswith("/vmlinux")): 82 return "kernel" 70 return "kernel" 83 71 84 return "" 72 return "" 85 73 86 @staticmethod 74 @staticmethod 87 def find_or_create_node(node, name, libtyp 75 def find_or_create_node(node, name, libtype): 88 for child in node.children: 76 for child in node.children: 89 if child.name == name: 77 if child.name == name: 90 return child 78 return child 91 79 92 child = Node(name, libtype) 80 child = Node(name, libtype) 93 node.children.append(child) 81 node.children.append(child) 94 return child 82 return child 95 83 96 def process_event(self, event): 84 def process_event(self, event): 97 pid = event.get("sample", {}).get("pid 85 pid = event.get("sample", {}).get("pid", 0) 98 # event["dso"] sometimes contains /usr 86 # event["dso"] sometimes contains /usr/lib/debug/lib/modules/*/vmlinux 99 # for user-space processes; let's use 87 # for user-space processes; let's use pid for kernel or user-space distinction 100 if pid == 0: 88 if pid == 0: 101 comm = event["comm"] 89 comm = event["comm"] 102 libtype = "kernel" 90 libtype = "kernel" 103 else: 91 else: 104 comm = "{} ({})".format(event["com 92 comm = "{} ({})".format(event["comm"], pid) 105 libtype = "" 93 libtype = "" 106 node = self.find_or_create_node(self.s 94 node = self.find_or_create_node(self.stack, comm, libtype) 107 95 108 if "callchain" in event: 96 if "callchain" in event: 109 for entry in reversed(event["callc 97 for entry in reversed(event["callchain"]): 110 name = entry.get("sym", {}).ge 98 name = entry.get("sym", {}).get("name", "[unknown]") 111 libtype = self.get_libtype_fro 99 libtype = self.get_libtype_from_dso(entry.get("dso")) 112 node = self.find_or_create_nod 100 node = self.find_or_create_node(node, name, libtype) 113 else: 101 else: 114 name = event.get("symbol", "[unkno 102 name = event.get("symbol", "[unknown]") 115 libtype = self.get_libtype_from_ds 103 libtype = self.get_libtype_from_dso(event.get("dso")) 116 node = self.find_or_create_node(no 104 node = self.find_or_create_node(node, name, libtype) 117 node.value += 1 105 node.value += 1 118 106 119 def get_report_header(self): 107 def get_report_header(self): 120 if self.args.input == "-": 108 if self.args.input == "-": 121 # when this script is invoked with 109 # when this script is invoked with "perf script flamegraph", 122 # no perf.data is created and we c 110 # no perf.data is created and we cannot read the header of it 123 return "" 111 return "" 124 112 125 try: 113 try: 126 output = subprocess.check_output([ 114 output = subprocess.check_output(["perf", "report", "--header-only"]) 127 return output.decode("utf-8") 115 return output.decode("utf-8") 128 except Exception as err: # pylint: di 116 except Exception as err: # pylint: disable=broad-except 129 print("Error reading report header 117 print("Error reading report header: {}".format(err), file=sys.stderr) 130 return "" 118 return "" 131 119 132 def trace_end(self): 120 def trace_end(self): 133 stacks_json = json.dumps(self.stack, d 121 stacks_json = json.dumps(self.stack, default=lambda x: x.to_json()) 134 122 135 if self.args.format == "html": 123 if self.args.format == "html": 136 report_header = self.get_report_he 124 report_header = self.get_report_header() 137 options = { 125 options = { 138 "colorscheme": self.args.color 126 "colorscheme": self.args.colorscheme, 139 "context": report_header 127 "context": report_header 140 } 128 } 141 options_json = json.dumps(options) 129 options_json = json.dumps(options) 142 130 143 template_md5sum = None << 144 if self.args.format == "html": << 145 if os.path.isfile(self.args.te << 146 template = f"file://{self. << 147 else: << 148 if not self.args.allow_dow << 149 print(f"""Warning: Fla << 150 does not exist. To avoid this please install a << 151 js-d3-flame-graph or libjs-d3-flame-graph, spe << 152 graph template (--template PATH) or use anothe << 153 FORMAT).""", << 154 file=sys.stderr) << 155 if self.args.input == << 156 print("""Not attem << 157 input is disabled due to using live mode. If y << 158 template retry without live mode. For example, << 159 -F 99 sleep 60' and 'perf script report flameg << 160 download the template from: << 161 https://cdn.jsdelivr.net/npm/d3-flame-graph@4. << 162 and place it at: << 163 /usr/share/d3-flame-graph/d3-flamegraph-base.h << 164 file=sys.std << 165 quit() << 166 s = None << 167 while s != "y" and s ! << 168 s = input("Do you << 169 if s == "n": << 170 quit() << 171 template = "https://cdn.js << 172 template_md5sum = "143e0d0 << 173 << 174 try: 131 try: 175 with urllib.request.urlopen(te !! 132 with io.open(self.args.template, encoding="utf-8") as template: 176 output_str = "".join([ !! 133 output_str = ( 177 l.decode("utf-8") for !! 134 template.read() 178 ]) !! 135 .replace("/** @options_json **/", options_json) 179 except Exception as err: !! 136 .replace("/** @flamegraph_json **/", stacks_json) 180 print(f"Error reading template !! 137 ) 181 "a minimal flame graph w !! 138 except IOError as err: 182 output_str = minimal_html !! 139 print("Error reading template file: {}".format(err), file=sys.stderr) 183 template_md5sum = None !! 140 sys.exit(1) 184 << 185 if template_md5sum: << 186 download_md5sum = hashlib.md5( << 187 if download_md5sum != template << 188 s = None << 189 while s != "y" and s != "n << 190 s = input(f"""Unexpect << 191 {download_md5sum} != {template_md5sum}, for: << 192 {output_str} << 193 continue?[yn] """).lower() << 194 if s == "n": << 195 quit() << 196 << 197 output_str = output_str.replace("/ << 198 output_str = output_str.replace("/ << 199 << 200 output_fn = self.args.output or "f 141 output_fn = self.args.output or "flamegraph.html" 201 else: 142 else: 202 output_str = stacks_json 143 output_str = stacks_json 203 output_fn = self.args.output or "s 144 output_fn = self.args.output or "stacks.json" 204 145 205 if output_fn == "-": 146 if output_fn == "-": 206 with io.open(sys.stdout.fileno(), 147 with io.open(sys.stdout.fileno(), "w", encoding="utf-8", closefd=False) as out: 207 out.write(output_str) 148 out.write(output_str) 208 else: 149 else: 209 print("dumping data to {}".format( 150 print("dumping data to {}".format(output_fn)) 210 try: 151 try: 211 with io.open(output_fn, "w", e 152 with io.open(output_fn, "w", encoding="utf-8") as out: 212 out.write(output_str) 153 out.write(output_str) 213 except IOError as err: 154 except IOError as err: 214 print("Error writing output fi 155 print("Error writing output file: {}".format(err), file=sys.stderr) 215 sys.exit(1) 156 sys.exit(1) 216 157 217 158 218 if __name__ == "__main__": 159 if __name__ == "__main__": 219 parser = argparse.ArgumentParser(descripti 160 parser = argparse.ArgumentParser(description="Create flame graphs.") 220 parser.add_argument("-f", "--format", 161 parser.add_argument("-f", "--format", 221 default="html", choice 162 default="html", choices=["json", "html"], 222 help="output file form 163 help="output file format") 223 parser.add_argument("-o", "--output", 164 parser.add_argument("-o", "--output", 224 help="output file name 165 help="output file name") 225 parser.add_argument("--template", 166 parser.add_argument("--template", 226 default="/usr/share/d3 167 default="/usr/share/d3-flame-graph/d3-flamegraph-base.html", 227 help="path to flame gr 168 help="path to flame graph HTML template") 228 parser.add_argument("--colorscheme", 169 parser.add_argument("--colorscheme", 229 default="blue-green", 170 default="blue-green", 230 help="flame graph colo 171 help="flame graph color scheme", 231 choices=["blue-green", 172 choices=["blue-green", "orange"]) 232 parser.add_argument("-i", "--input", 173 parser.add_argument("-i", "--input", 233 help=argparse.SUPPRESS 174 help=argparse.SUPPRESS) 234 parser.add_argument("--allow-download", << 235 default=False, << 236 action="store_true", << 237 help="allow unprompted << 238 175 239 cli_args = parser.parse_args() 176 cli_args = parser.parse_args() 240 cli = FlameGraphCLI(cli_args) 177 cli = FlameGraphCLI(cli_args) 241 178 242 process_event = cli.process_event 179 process_event = cli.process_event 243 trace_end = cli.trace_end 180 trace_end = cli.trace_end
Linux® is a registered trademark of Linus Torvalds in the United States and other countries.
TOMOYO® is a registered trademark of NTT DATA CORPORATION.