From nobody Sun Sep 14 16:12:23 2025 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by smtp.lore.kernel.org (Postfix) with ESMTP id 7B7A9C004D4 for ; Thu, 19 Jan 2023 18:31:41 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S230072AbjASSbj (ORCPT ); Thu, 19 Jan 2023 13:31:39 -0500 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:54530 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S230113AbjASSbb (ORCPT ); Thu, 19 Jan 2023 13:31:31 -0500 Received: from mail-pj1-x1049.google.com (mail-pj1-x1049.google.com [IPv6:2607:f8b0:4864:20::1049]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 4C4985CFC4 for ; Thu, 19 Jan 2023 10:31:30 -0800 (PST) Received: by mail-pj1-x1049.google.com with SMTP id om10-20020a17090b3a8a00b002299e350deaso3762798pjb.1 for ; Thu, 19 Jan 2023 10:31:30 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=20210112; h=to:from:subject:mime-version:message-id:date:from:to:cc:subject :date:message-id:reply-to; bh=g5ejasQrFsU/5gVpFRFubfv/+PZvkyzMvOtZ1k9Ls2g=; b=b0YxZwON3IV1/D404DMPS9jrW+M2J2WYkVE+iSbiN0STXINJePxujS9tiGUZi5i9G8 C36lUwVPqY7JFPAiKBudcJbQsi9SVQPM2IydPaO1zAjdnmHDpPWOJQlbCBYhMa7b+PqT X+vVrtxMoiSRxQpfs1FeU6yXXymOIcr5cKECICCm1n5GJrdSUfHzAAK1Xdgn9njEa7e2 AVyoCoEYej+TaYmBOLFNF7043pQxqtJjO0GbnE0Pec8l9KbB7gq7vJbXefZ5jIfs+/Px kIWoi5iZSo5VStr1tZ9lFx7c0/jR2Y2uzITBalGHkowv5JLcvJy1PWbvcdOVG7U7WH15 OyYA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20210112; h=to:from:subject:mime-version:message-id:date:x-gm-message-state :from:to:cc:subject:date:message-id:reply-to; bh=g5ejasQrFsU/5gVpFRFubfv/+PZvkyzMvOtZ1k9Ls2g=; b=1dKZ6qRVvsMSDdELA0GNHnVF66I8BkEHKCIt+K89fYD4UWOEAAwxOL0QKAW1cTK1NN FhWWgRnhS0ht5kfWYfQgO+rXlM8ETgYbkXWdbAmFq31wxezQTCFxD2oisVrh58QpwLRE x/99IsNSoZXzfd37xUftFnSzr0bOk/Zx4hbOoXiGxWATZbDizSyILPDy741KAc7Lxr/a 52kaOlWus0H7OXZE3TbL9ht1W41F8xJj1Pt3JlUNJzAun99zT/sI81MwVwkx0IIYiHiI Lp8b8Jq33bz46XgVXO+sqOH3P7hJ7lN+Y3bunV7ukMIboTXyY+j23U8Pj67GBVHn7BtU 0dJQ== X-Gm-Message-State: AFqh2krL7lNwuorwxqqCGyxNhCFR6qgnYL//bbgyD6UGsY22/kDdW8Ns rGu0gxK6AqIuuk+NWAgRMJUmhV7xhRac X-Google-Smtp-Source: AMrXdXsRZ1WORHapl+vRGimw/AuCvgzBAWX0gXnTXNVMG+s6DSp0/UCLa/EN1y6UP6olfWUfFA4BhUzyokOn X-Received: from irogers.svl.corp.google.com ([2620:15c:2d4:203:93c6:b65e:5f33:bc6b]) (user=irogers job=sendgmr) by 2002:a05:6a00:4c90:b0:58a:1e40:e474 with SMTP id eb16-20020a056a004c9000b0058a1e40e474mr1115087pfb.83.1674153089543; Thu, 19 Jan 2023 10:31:29 -0800 (PST) Date: Thu, 19 Jan 2023 10:31:16 -0800 Message-Id: <20230119183118.126387-1-irogers@google.com> Mime-Version: 1.0 X-Mailer: git-send-email 2.39.0.246.g2a6d74b583-goog Subject: [PATCH v3] perf script flamegraph: Avoid d3-flame-graph package dependency From: Ian Rogers To: Alexei Starovoitov , Daniel Borkmann , Andrii Nakryiko , Martin KaFai Lau , Song Liu , Yonghong Song , John Fastabend , KP Singh , Stanislav Fomichev , Hao Luo , Jiri Olsa , Connor OBrien , Nathan Chancellor , Ian Rogers , Kumar Kartikeya Dwivedi , bpf@vger.kernel.org, linux-kernel@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-kernel@vger.kernel.org Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" 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=3D996839 Signed-off-by: Ian Rogers --- tools/perf/scripts/python/flamegraph.py | 107 +++++++++++++++++++----- 1 file changed, 85 insertions(+), 22 deletions(-) diff --git a/tools/perf/scripts/python/flamegraph.py b/tools/perf/scripts/p= ython/flamegraph.py index b6af1dd5f816..cf7ce8229a6c 100755 --- a/tools/perf/scripts/python/flamegraph.py +++ b/tools/perf/scripts/python/flamegraph.py @@ -19,12 +19,34 @@ # pylint: disable=3Dmissing-function-docstring =20 from __future__ import print_function -import sys -import os -import io import argparse +import hashlib +import io import json +import os import subprocess +import sys +import urllib.request + +minimal_html =3D """ + + + +
+ + + +""" =20 # pylint: disable=3Dtoo-few-public-methods class Node: @@ -50,16 +72,6 @@ class FlameGraphCLI: self.args =3D args self.stack =3D Node("all", "root") =20 - if self.args.format =3D=3D "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 (de= b) " - "package, specify an existing flame graph template " - "(--template PATH) or another output format " - "(--format FORMAT).".format(self.args.template), - file=3Dsys.stderr) - sys.exit(1) - @staticmethod def get_libtype_from_dso(dso): """ @@ -128,16 +140,63 @@ class FlameGraphCLI: } options_json =3D json.dumps(options) =20 + template_md5sum =3D None + if self.args.format =3D=3D "html": + if os.path.isfile(self.args.template): + template =3D f"file://{self.args.template}" + else: + if not self.args.allow_download: + print(f"""Warning: Flame Graph template '{self.arg= s.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=3Dsys.stderr) + if self.args.input =3D=3D "-": + print("""Not attempting to download Flame Grap= h 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-flameg= raph-base.html +and place it at: +/usr/share/d3-flame-graph/d3-flamegraph-base.html""", + file=3Dsys.stderr) + quit() + s =3D None + while s !=3D "y" and s !=3D "n": + s =3D input("Do you wish to download a templat= e from cdn.jsdelivr.net? (this warning can be suppressed with --allow-downl= oad) [yn] ").lower() + if s =3D=3D "n": + quit() + template =3D "https://cdn.jsdelivr.net/npm/d3-flame-gr= aph@4.1.3/dist/templates/d3-flamegraph-base.html" + template_md5sum =3D "143e0d06ba69b8370b9848dcd6ae3f36" + try: - with io.open(self.args.template, encoding=3D"utf-8") as te= mplate: - output_str =3D ( - template.read() - .replace("/** @options_json **/", options_json) - .replace("/** @flamegraph_json **/", stacks_json) - ) - except IOError as err: - print("Error reading template file: {}".format(err), file= =3Dsys.stderr) - sys.exit(1) + with urllib.request.urlopen(template) as template: + output_str =3D "".join([ + l.decode("utf-8") for l in template.readlines() + ]) + except Exception as err: + print(f"Error reading template {template}: {err}\n" + "a minimal flame graph will be generated", file=3Dsy= s.stderr) + output_str =3D minimal_html + template_md5sum =3D None + + if template_md5sum: + download_md5sum =3D hashlib.md5(output_str.encode("utf-8")= ).hexdigest() + if download_md5sum !=3D template_md5sum: + s =3D None + while s !=3D "y" and s !=3D "n": + s =3D input(f"""Unexpected template md5sum. +{download_md5sum} !=3D {template_md5sum}, for: +{output_str} +continue?[yn] """).lower() + if s =3D=3D "n": + quit() + + output_str =3D output_str.replace("/** @options_json **/", opt= ions_json) + output_str =3D output_str.replace("/** @flamegraph_json **/", = stacks_json) + output_fn =3D self.args.output or "flamegraph.html" else: output_str =3D stacks_json @@ -172,6 +231,10 @@ if __name__ =3D=3D "__main__": choices=3D["blue-green", "orange"]) parser.add_argument("-i", "--input", help=3Dargparse.SUPPRESS) + parser.add_argument("--allow-download", + default=3DFalse, + action=3D"store_true", + help=3D"allow unprompted downloading of HTML templ= ate") =20 cli_args =3D parser.parse_args() cli =3D FlameGraphCLI(cli_args) --=20 2.39.0.314.g84b9a713c41-goog