Commit b430d243 authored by Ian Rogers's avatar Ian Rogers Committed by Arnaldo Carvalho de Melo

perf script flamegraph: Avoid d3-flame-graph package dependency

Currently flame graph generation requires a d3-flame-graph template to
be installed. Unfortunately this is hard to come by for things like
Debian [1].

If the template isn't installed then ask if it should be downloaded from
jsdelivr CDN. The downloaded HTML file is validated against an md5sum.
If the download fails, generate a minimal flame graph with the
javascript coming from links to jsdelivr CDN.

v3. Adds a warning message and quits before download in live mode.
v2. Change the warning to a prompt about downloading and add the
    --allow-download command line flag. Add an md5sum check for the
    downloaded HTML.

[1] https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=996839Reviewed-by: default avatarAndreas Gerstmayr <agerstmayr@redhat.com>
Signed-off-by: default avatarIan Rogers <irogers@google.com>
Cc: 996839@bugs.debian.org
Cc: Alexander Shishkin <alexander.shishkin@linux.intel.com>
Cc: Brendan Gregg <brendan@intel.com>
Cc: Ingo Molnar <mingo@redhat.com>
Cc: Jiri Olsa <jolsa@kernel.org>
Cc: Mark Rutland <mark.rutland@arm.com>
Cc: Martin Spier <spiermar@gmail.com>
Cc: Namhyung Kim <namhyung@kernel.org>
Cc: Peter Zijlstra <peterz@infradead.org>
Link: https://lore.kernel.org/r/20230118072409.147786-1-irogers@google.com # v3 discussion
Link: https://lore.kernel.org/r/20230112220024.32709-1-irogers@google.com # v2 discussion
Link: https://lore.kernel.org/r/CAP-5=fXi_9zdhTAoYApiFQoLURAvpEatFzU3uL23o3zs=z25ZQ@mail.gmail.com # v1 discussion
Signed-off-by: default avatarArnaldo Carvalho de Melo <acme@redhat.com>
parent 7287904c
...@@ -19,12 +19,34 @@ ...@@ -19,12 +19,34 @@
# pylint: disable=missing-function-docstring # pylint: disable=missing-function-docstring
from __future__ import print_function from __future__ import print_function
import sys
import os
import io
import argparse import argparse
import hashlib
import io
import json import json
import os
import subprocess import subprocess
import sys
import urllib.request
minimal_html = """<head>
<link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/npm/d3-flame-graph@4.1.3/dist/d3-flamegraph.css">
</head>
<body>
<div id="chart"></div>
<script type="text/javascript" src="https://d3js.org/d3.v7.js"></script>
<script type="text/javascript" src="https://cdn.jsdelivr.net/npm/d3-flame-graph@4.1.3/dist/d3-flamegraph.min.js"></script>
<script type="text/javascript">
const stacks = [/** @flamegraph_json **/];
// Note, options is unused.
const options = [/** @options_json **/];
var chart = flamegraph();
d3.select("#chart")
.datum(stacks[0])
.call(chart);
</script>
</body>
"""
# pylint: disable=too-few-public-methods # pylint: disable=too-few-public-methods
class Node: class Node:
...@@ -50,16 +72,6 @@ class FlameGraphCLI: ...@@ -50,16 +72,6 @@ class FlameGraphCLI:
self.args = args self.args = args
self.stack = Node("all", "root") self.stack = Node("all", "root")
if self.args.format == "html" and \
not os.path.isfile(self.args.template):
print("Flame Graph template {} does not exist. Please install "
"the js-d3-flame-graph (RPM) or libjs-d3-flame-graph (deb) "
"package, specify an existing flame graph template "
"(--template PATH) or another output format "
"(--format FORMAT).".format(self.args.template),
file=sys.stderr)
sys.exit(1)
@staticmethod @staticmethod
def get_libtype_from_dso(dso): def get_libtype_from_dso(dso):
""" """
...@@ -128,16 +140,63 @@ class FlameGraphCLI: ...@@ -128,16 +140,63 @@ class FlameGraphCLI:
} }
options_json = json.dumps(options) options_json = json.dumps(options)
template_md5sum = None
if self.args.format == "html":
if os.path.isfile(self.args.template):
template = f"file://{self.args.template}"
else:
if not self.args.allow_download:
print(f"""Warning: Flame Graph template '{self.args.template}'
does not exist. To avoid this please install a package such as the
js-d3-flame-graph or libjs-d3-flame-graph, specify an existing flame
graph template (--template PATH) or use another output format (--format
FORMAT).""",
file=sys.stderr)
if self.args.input == "-":
print("""Not attempting to download Flame Graph template as script command line
input is disabled due to using live mode. If you want to download the
template retry without live mode. For example, use 'perf record -a -g
-F 99 sleep 60' and 'perf script report flamegraph'. Alternatively,
download the template from:
https://cdn.jsdelivr.net/npm/d3-flame-graph@4.1.3/dist/templates/d3-flamegraph-base.html
and place it at:
/usr/share/d3-flame-graph/d3-flamegraph-base.html""",
file=sys.stderr)
quit()
s = None
while s != "y" and s != "n":
s = input("Do you wish to download a template from cdn.jsdelivr.net? (this warning can be suppressed with --allow-download) [yn] ").lower()
if s == "n":
quit()
template = "https://cdn.jsdelivr.net/npm/d3-flame-graph@4.1.3/dist/templates/d3-flamegraph-base.html"
template_md5sum = "143e0d06ba69b8370b9848dcd6ae3f36"
try: try:
with io.open(self.args.template, encoding="utf-8") as template: with urllib.request.urlopen(template) as template:
output_str = ( output_str = "".join([
template.read() l.decode("utf-8") for l in template.readlines()
.replace("/** @options_json **/", options_json) ])
.replace("/** @flamegraph_json **/", stacks_json) except Exception as err:
) print(f"Error reading template {template}: {err}\n"
except IOError as err: "a minimal flame graph will be generated", file=sys.stderr)
print("Error reading template file: {}".format(err), file=sys.stderr) output_str = minimal_html
sys.exit(1) template_md5sum = None
if template_md5sum:
download_md5sum = hashlib.md5(output_str.encode("utf-8")).hexdigest()
if download_md5sum != template_md5sum:
s = None
while s != "y" and s != "n":
s = input(f"""Unexpected template md5sum.
{download_md5sum} != {template_md5sum}, for:
{output_str}
continue?[yn] """).lower()
if s == "n":
quit()
output_str = output_str.replace("/** @options_json **/", options_json)
output_str = output_str.replace("/** @flamegraph_json **/", stacks_json)
output_fn = self.args.output or "flamegraph.html" output_fn = self.args.output or "flamegraph.html"
else: else:
output_str = stacks_json output_str = stacks_json
...@@ -172,6 +231,10 @@ if __name__ == "__main__": ...@@ -172,6 +231,10 @@ if __name__ == "__main__":
choices=["blue-green", "orange"]) choices=["blue-green", "orange"])
parser.add_argument("-i", "--input", parser.add_argument("-i", "--input",
help=argparse.SUPPRESS) help=argparse.SUPPRESS)
parser.add_argument("--allow-download",
default=False,
action="store_true",
help="allow unprompted downloading of HTML template")
cli_args = parser.parse_args() cli_args = parser.parse_args()
cli = FlameGraphCLI(cli_args) cli = FlameGraphCLI(cli_args)
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment