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
21 from __future__
import print_function
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
, libtype
):
55 # "root" | "kernel" | ""
56 # "" indicates user space
57 self
.libtype
= libtype
71 def __init__(self
, args
):
73 self
.stack
= Node("all", "root")
76 def get_libtype_from_dso(dso
):
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
, name
, libtype
):
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
):
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
104 comm
= "{} ({})".format(event
["comm"], pid
)
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
)
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
)
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
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
)
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()
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}"
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
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
163 /usr/share/d3-flame-graph/d3-flamegraph-base.html""",
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()
171 template
= "https://cdn.jsdelivr.net/npm/d3-flame-graph@4.1.3/dist/templates/d3-flamegraph-base.html"
172 template_md5sum
= "143e0d06ba69b8370b9848dcd6ae3f36"
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
186 download_md5sum
= hashlib
.md5(output_str
.encode("utf-8")).hexdigest()
187 if download_md5sum
!= template_md5sum
:
189 while s
!= "y" and s
!= "n":
190 s
= input(f
"""Unexpected template md5sum.
191 {download_md5sum} != {template_md5sum}, for:
193 continue?[yn] """).lower()
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"
202 output_str
= stacks_json
203 output_fn
= self
.args
.output
or "stacks.json"
206 with io
.open(sys
.stdout
.fileno(), "w", encoding
="utf-8", closefd
=False) as out
:
207 out
.write(output_str
)
209 print("dumping data to {}".format(output_fn
))
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
)
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",
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