]>
exis.tech > repos - linux.git/blob - tools/perf/scripts/python/flamegraph.py
1 # flamegraph.py - create flame graphs from perf samples
2 # SPDX-License-Identifier: GPL-2.0
6 # perf record -a -g -F 99 sleep 60
7 # perf script report flamegraph
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
28 from typing
import Dict
, Optional
, Union
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">
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();
51 # pylint: disable=too-few-public-methods
53 def __init__(self
, name
: str, libtype
: str):
55 # "root" | "kernel" | ""
56 # "" indicates user space
57 self
.libtype
= libtype
59 self
.children
: list[Node
] = []
61 def to_json(self
) -> Dict
[str, Union
[str, int, list[Dict
]]]:
66 "c": [x
.to_json() for x
in self
.children
]
71 def __init__(self
, args
):
73 self
.stack
= Node("all", "root")
76 def get_libtype_from_dso(dso
: Optional
[str]) -> str:
78 when kernel-debuginfo is installed,
79 dso points to /usr/lib/debug/lib/modules/*/vmlinux
81 if dso
and (dso
== "[kernel.kallsyms]" or dso
.endswith("/vmlinux")):
87 def find_or_create_node(node
: Node
, name
: str, libtype
: str) -> Node
:
88 for child
in node
.children
:
89 if child
.name
== name
:
92 child
= Node(name
, libtype
)
93 node
.children
.append(child
)
96 def process_event(self
, event
) -> None:
97 # ignore events where the event name does not match
98 # the one specified by the user
99 if self
.args
.event_name
and event
.get("ev_name") != self
.args
.event_name
:
102 pid
= event
.get("sample", {}).get("pid", 0)
103 # event["dso"] sometimes contains /usr/lib/debug/lib/modules/*/vmlinux
104 # for user-space processes; let's use pid for kernel or user-space distinction
109 comm
= f
"{event['comm']} ({pid})"
111 node
= self
.find_or_create_node(self
.stack
, comm
, libtype
)
113 if "callchain" in event
:
114 for entry
in reversed(event
["callchain"]):
115 name
= entry
.get("sym", {}).get("name", "[unknown]")
116 libtype
= self
.get_libtype_from_dso(entry
.get("dso"))
117 node
= self
.find_or_create_node(node
, name
, libtype
)
119 name
= event
.get("symbol", "[unknown]")
120 libtype
= self
.get_libtype_from_dso(event
.get("dso"))
121 node
= self
.find_or_create_node(node
, name
, libtype
)
124 def get_report_header(self
) -> str:
125 if self
.args
.input == "-":
126 # when this script is invoked with "perf script flamegraph",
127 # no perf.data is created and we cannot read the header of it
131 # if the file name other than perf.data is given,
132 # we read the header of that file
134 output
= subprocess
.check_output(["perf", "report", "--header-only",
135 "-i", self
.args
.input])
137 output
= subprocess
.check_output(["perf", "report", "--header-only"])
139 result
= output
.decode("utf-8")
140 if self
.args
.event_name
:
141 result
+= "\nFocused event: " + self
.args
.event_name
143 except Exception as err
: # pylint: disable=broad-except
144 print(f
"Error reading report header: {err}", file=sys
.stderr
)
147 def trace_end(self
) -> None:
148 stacks_json
= json
.dumps(self
.stack
, default
=lambda x
: x
.to_json())
150 if self
.args
.format
== "html":
151 report_header
= self
.get_report_header()
153 "colorscheme": self
.args
.colorscheme
,
154 "context": report_header
156 options_json
= json
.dumps(options
)
158 template_md5sum
= None
159 if self
.args
.format
== "html":
160 if os
.path
.isfile(self
.args
.template
):
161 template
= f
"file://{self.args.template}"
163 if not self
.args
.allow_download
:
164 print(f
"""Warning: Flame Graph template '{self.args.template}'
165 does not exist. To avoid this please install a package such as the
166 js-d3-flame-graph or libjs-d3-flame-graph, specify an existing flame
167 graph template (--template PATH) or use another output format (--format
170 if self
.args
.input == "-":
172 """Not attempting to download Flame Graph template as script command line
173 input is disabled due to using live mode. If you want to download the
174 template retry without live mode. For example, use 'perf record -a -g
175 -F 99 sleep 60' and 'perf script report flamegraph'. Alternatively,
176 download the template from:
177 https://cdn.jsdelivr.net/npm/d3-flame-graph@4.1.3/dist/templates/d3-flamegraph-base.html
179 /usr/share/d3-flame-graph/d3-flamegraph-base.html""",
183 while s
not in ["y", "n"]:
184 s
= input("Do you wish to download a template from cdn.jsdelivr.net?" +
185 "(this warning can be suppressed with --allow-download) [yn] "
189 template
= ("https://cdn.jsdelivr.net/npm/d3-flame-graph@4.1.3/dist/templates/"
190 "d3-flamegraph-base.html")
191 template_md5sum
= "143e0d06ba69b8370b9848dcd6ae3f36"
194 with urllib
.request
.urlopen(template
) as url_template
:
195 output_str
= "".join([
196 l
.decode("utf-8") for l
in url_template
.readlines()
198 except Exception as err
:
199 print(f
"Error reading template {template}: {err}\n"
200 "a minimal flame graph will be generated", file=sys
.stderr
)
201 output_str
= MINIMAL_HTML
202 template_md5sum
= None
205 download_md5sum
= hashlib
.md5(output_str
.encode("utf-8")).hexdigest()
206 if download_md5sum
!= template_md5sum
:
208 while s
not in ["y", "n"]:
209 s
= input(f
"""Unexpected template md5sum.
210 {download_md5sum} != {template_md5sum}, for:
212 continue?[yn] """).lower()
216 output_str
= output_str
.replace("/** @options_json **/", options_json
)
217 output_str
= output_str
.replace("/** @flamegraph_json **/", stacks_json
)
219 output_fn
= self
.args
.output
or "flamegraph.html"
221 output_str
= stacks_json
222 output_fn
= self
.args
.output
or "stacks.json"
225 with io
.open(sys
.stdout
.fileno(), "w", encoding
="utf-8", closefd
=False) as out
:
226 out
.write(output_str
)
228 print(f
"dumping data to {output_fn}")
230 with io
.open(output_fn
, "w", encoding
="utf-8") as out
:
231 out
.write(output_str
)
232 except IOError as err
:
233 print(f
"Error writing output file: {err}", file=sys
.stderr
)
237 if __name__
== "__main__":
238 parser
= argparse
.ArgumentParser(description
="Create flame graphs.")
239 parser
.add_argument("-f", "--format",
240 default
="html", choices
=["json", "html"],
241 help="output file format")
242 parser
.add_argument("-o", "--output",
243 help="output file name")
244 parser
.add_argument("--template",
245 default
="/usr/share/d3-flame-graph/d3-flamegraph-base.html",
246 help="path to flame graph HTML template")
247 parser
.add_argument("--colorscheme",
248 default
="blue-green",
249 help="flame graph color scheme",
250 choices
=["blue-green", "orange"])
251 parser
.add_argument("-i", "--input",
252 help=argparse
.SUPPRESS
)
253 parser
.add_argument("--allow-download",
256 help="allow unprompted downloading of HTML template")
257 parser
.add_argument("-e", "--event",
261 help="specify the event to generate flamegraph for")
263 cli_args
= parser
.parse_args()
264 cli
= FlameGraphCLI(cli_args
)
266 process_event
= cli
.process_event
267 trace_end
= cli
.trace_end