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