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 # << 17 # pylint: disable=missing-module-docstring << 18 # pylint: disable=missing-class-docstring << 19 # pylint: disable=missing-function-docstring << 20 16 21 from __future__ import print_function 17 from __future__ import print_function 22 import argparse !! 18 import sys 23 import hashlib !! 19 import os 24 import io 20 import io >> 21 import argparse 25 import json 22 import json 26 import os << 27 import subprocess << 28 import sys << 29 import urllib.request << 30 23 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 24 51 # pylint: disable=too-few-public-methods << 52 class Node: 25 class Node: 53 def __init__(self, name, libtype): !! 26 def __init__(self, name, libtype=""): 54 self.name = name 27 self.name = name 55 # "root" | "kernel" | "" << 56 # "" indicates user space << 57 self.libtype = libtype 28 self.libtype = libtype 58 self.value = 0 29 self.value = 0 59 self.children = [] 30 self.children = [] 60 31 61 def to_json(self): !! 32 def toJSON(self): 62 return { 33 return { 63 "n": self.name, 34 "n": self.name, 64 "l": self.libtype, 35 "l": self.libtype, 65 "v": self.value, 36 "v": self.value, 66 "c": self.children 37 "c": self.children 67 } 38 } 68 39 69 40 70 class FlameGraphCLI: 41 class FlameGraphCLI: 71 def __init__(self, args): 42 def __init__(self, args): 72 self.args = args 43 self.args = args 73 self.stack = Node("all", "root") !! 44 self.stack = Node("root") 74 << 75 @staticmethod << 76 def get_libtype_from_dso(dso): << 77 """ << 78 when kernel-debuginfo is installed, << 79 dso points to /usr/lib/debug/lib/modul << 80 """ << 81 if dso and (dso == "[kernel.kallsyms]" << 82 return "kernel" << 83 45 84 return "" !! 46 if self.args.format == "html" and \ >> 47 not os.path.isfile(self.args.template): >> 48 print("Flame Graph template {} does not exist. Please install " >> 49 "the js-d3-flame-graph (RPM) or libjs-d3-flame-graph (deb) " >> 50 "package, specify an existing flame graph template " >> 51 "(--template PATH) or another output format " >> 52 "(--format FORMAT).".format(self.args.template), >> 53 file=sys.stderr) >> 54 sys.exit(1) >> 55 >> 56 def find_or_create_node(self, node, name, dso): >> 57 libtype = "kernel" if dso == "[kernel.kallsyms]" else "" >> 58 if name is None: >> 59 name = "[unknown]" 85 60 86 @staticmethod << 87 def find_or_create_node(node, name, libtyp << 88 for child in node.children: 61 for child in node.children: 89 if child.name == name: !! 62 if child.name == name and child.libtype == libtype: 90 return child 63 return child 91 64 92 child = Node(name, libtype) 65 child = Node(name, libtype) 93 node.children.append(child) 66 node.children.append(child) 94 return child 67 return child 95 68 96 def process_event(self, event): 69 def process_event(self, event): 97 pid = event.get("sample", {}).get("pid !! 70 node = self.find_or_create_node(self.stack, event["comm"], None) 98 # event["dso"] sometimes contains /usr << 99 # for user-space processes; let's use << 100 if pid == 0: << 101 comm = event["comm"] << 102 libtype = "kernel" << 103 else: << 104 comm = "{} ({})".format(event["com << 105 libtype = "" << 106 node = self.find_or_create_node(self.s << 107 << 108 if "callchain" in event: 71 if "callchain" in event: 109 for entry in reversed(event["callc !! 72 for entry in reversed(event['callchain']): 110 name = entry.get("sym", {}).ge !! 73 node = self.find_or_create_node( 111 libtype = self.get_libtype_fro !! 74 node, entry.get("sym", {}).get("name"), event.get("dso")) 112 node = self.find_or_create_nod << 113 else: 75 else: 114 name = event.get("symbol", "[unkno !! 76 node = self.find_or_create_node( 115 libtype = self.get_libtype_from_ds !! 77 node, entry.get("symbol"), event.get("dso")) 116 node = self.find_or_create_node(no << 117 node.value += 1 78 node.value += 1 118 79 119 def get_report_header(self): << 120 if self.args.input == "-": << 121 # when this script is invoked with << 122 # no perf.data is created and we c << 123 return "" << 124 << 125 try: << 126 output = subprocess.check_output([ << 127 return output.decode("utf-8") << 128 except Exception as err: # pylint: di << 129 print("Error reading report header << 130 return "" << 131 << 132 def trace_end(self): 80 def trace_end(self): 133 stacks_json = json.dumps(self.stack, d !! 81 json_str = json.dumps(self.stack, default=lambda x: x.toJSON()) 134 82 135 if self.args.format == "html": 83 if self.args.format == "html": 136 report_header = self.get_report_he << 137 options = { << 138 "colorscheme": self.args.color << 139 "context": report_header << 140 } << 141 options_json = json.dumps(options) << 142 << 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: 84 try: 175 with urllib.request.urlopen(te !! 85 with io.open(self.args.template, encoding="utf-8") as f: 176 output_str = "".join([ !! 86 output_str = f.read().replace("/** @flamegraph_json **/", 177 l.decode("utf-8") for !! 87 json_str) 178 ]) !! 88 except IOError as e: 179 except Exception as err: !! 89 print("Error reading template file: {}".format(e), file=sys.stderr) 180 print(f"Error reading template !! 90 sys.exit(1) 181 "a minimal flame graph w << 182 output_str = minimal_html << 183 template_md5sum = None << 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 91 output_fn = self.args.output or "flamegraph.html" 201 else: 92 else: 202 output_str = stacks_json !! 93 output_str = json_str 203 output_fn = self.args.output or "s 94 output_fn = self.args.output or "stacks.json" 204 95 205 if output_fn == "-": 96 if output_fn == "-": 206 with io.open(sys.stdout.fileno(), 97 with io.open(sys.stdout.fileno(), "w", encoding="utf-8", closefd=False) as out: 207 out.write(output_str) 98 out.write(output_str) 208 else: 99 else: 209 print("dumping data to {}".format( 100 print("dumping data to {}".format(output_fn)) 210 try: 101 try: 211 with io.open(output_fn, "w", e 102 with io.open(output_fn, "w", encoding="utf-8") as out: 212 out.write(output_str) 103 out.write(output_str) 213 except IOError as err: !! 104 except IOError as e: 214 print("Error writing output fi !! 105 print("Error writing output file: {}".format(e), file=sys.stderr) 215 sys.exit(1) 106 sys.exit(1) 216 107 217 108 218 if __name__ == "__main__": 109 if __name__ == "__main__": 219 parser = argparse.ArgumentParser(descripti 110 parser = argparse.ArgumentParser(description="Create flame graphs.") 220 parser.add_argument("-f", "--format", 111 parser.add_argument("-f", "--format", 221 default="html", choice 112 default="html", choices=["json", "html"], 222 help="output file form 113 help="output file format") 223 parser.add_argument("-o", "--output", 114 parser.add_argument("-o", "--output", 224 help="output file name 115 help="output file name") 225 parser.add_argument("--template", 116 parser.add_argument("--template", 226 default="/usr/share/d3 117 default="/usr/share/d3-flame-graph/d3-flamegraph-base.html", 227 help="path to flame gr !! 118 help="path to flamegraph HTML template") 228 parser.add_argument("--colorscheme", << 229 default="blue-green", << 230 help="flame graph colo << 231 choices=["blue-green", << 232 parser.add_argument("-i", "--input", 119 parser.add_argument("-i", "--input", 233 help=argparse.SUPPRESS 120 help=argparse.SUPPRESS) 234 parser.add_argument("--allow-download", << 235 default=False, << 236 action="store_true", << 237 help="allow unprompted << 238 121 239 cli_args = parser.parse_args() !! 122 args = parser.parse_args() 240 cli = FlameGraphCLI(cli_args) !! 123 cli = FlameGraphCLI(args) 241 124 242 process_event = cli.process_event 125 process_event = cli.process_event 243 trace_end = cli.trace_end 126 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.