Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

perf flamegraph: Fix minor pylint/type hint issues

Switch to assuming python3. Fix minor pylint issues on line length,
repeated compares, not using f-strings and variable case. Add type
hints and check with mypy.

Signed-off-by: Ian Rogers <irogers@google.com>
Tested-by: Namhyung Kim <namhyung@kernel.org>
Link: https://lore.kernel.org/r/20250716004635.31161-1-irogers@google.com
Signed-off-by: Namhyung Kim <namhyung@kernel.org>

authored by

Ian Rogers and committed by
Namhyung Kim
95d692f9 8db1d772

+33 -28
+33 -28
tools/perf/scripts/python/flamegraph.py
··· 18 18 # pylint: disable=missing-class-docstring 19 19 # pylint: disable=missing-function-docstring 20 20 21 - from __future__ import print_function 22 21 import argparse 23 22 import hashlib 24 23 import io ··· 25 26 import os 26 27 import subprocess 27 28 import sys 29 + from typing import Dict, Optional, Union 28 30 import urllib.request 29 31 30 - minimal_html = """<head> 32 + MINIMAL_HTML = """<head> 31 33 <link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/npm/d3-flame-graph@4.1.3/dist/d3-flamegraph.css"> 32 34 </head> 33 35 <body> ··· 50 50 51 51 # pylint: disable=too-few-public-methods 52 52 class Node: 53 - def __init__(self, name, libtype): 53 + def __init__(self, name: str, libtype: str): 54 54 self.name = name 55 55 # "root" | "kernel" | "" 56 56 # "" indicates user space 57 57 self.libtype = libtype 58 - self.value = 0 59 - self.children = [] 58 + self.value: int = 0 59 + self.children: list[Node] = [] 60 60 61 - def to_json(self): 61 + def to_json(self) -> Dict[str, Union[str, int, list[Dict]]]: 62 62 return { 63 63 "n": self.name, 64 64 "l": self.libtype, 65 65 "v": self.value, 66 - "c": self.children 66 + "c": [x.to_json() for x in self.children] 67 67 } 68 68 69 69 ··· 73 73 self.stack = Node("all", "root") 74 74 75 75 @staticmethod 76 - def get_libtype_from_dso(dso): 76 + def get_libtype_from_dso(dso: Optional[str]) -> str: 77 77 """ 78 78 when kernel-debuginfo is installed, 79 79 dso points to /usr/lib/debug/lib/modules/*/vmlinux ··· 84 84 return "" 85 85 86 86 @staticmethod 87 - def find_or_create_node(node, name, libtype): 87 + def find_or_create_node(node: Node, name: str, libtype: str) -> Node: 88 88 for child in node.children: 89 89 if child.name == name: 90 90 return child ··· 93 93 node.children.append(child) 94 94 return child 95 95 96 - def process_event(self, event): 96 + def process_event(self, event) -> None: 97 97 # ignore events where the event name does not match 98 98 # the one specified by the user 99 99 if self.args.event_name and event.get("ev_name") != self.args.event_name: ··· 106 106 comm = event["comm"] 107 107 libtype = "kernel" 108 108 else: 109 - comm = "{} ({})".format(event["comm"], pid) 109 + comm = f"{event['comm']} ({pid})" 110 110 libtype = "" 111 111 node = self.find_or_create_node(self.stack, comm, libtype) 112 112 ··· 121 121 node = self.find_or_create_node(node, name, libtype) 122 122 node.value += 1 123 123 124 - def get_report_header(self): 124 + def get_report_header(self) -> str: 125 125 if self.args.input == "-": 126 126 # when this script is invoked with "perf script flamegraph", 127 127 # no perf.data is created and we cannot read the header of it ··· 131 131 # if the file name other than perf.data is given, 132 132 # we read the header of that file 133 133 if self.args.input: 134 - output = subprocess.check_output(["perf", "report", "--header-only", "-i", self.args.input]) 134 + output = subprocess.check_output(["perf", "report", "--header-only", 135 + "-i", self.args.input]) 135 136 else: 136 137 output = subprocess.check_output(["perf", "report", "--header-only"]) 137 138 ··· 141 140 result += "\nFocused event: " + self.args.event_name 142 141 return result 143 142 except Exception as err: # pylint: disable=broad-except 144 - print("Error reading report header: {}".format(err), file=sys.stderr) 143 + print(f"Error reading report header: {err}", file=sys.stderr) 145 144 return "" 146 145 147 - def trace_end(self): 146 + def trace_end(self) -> None: 148 147 stacks_json = json.dumps(self.stack, default=lambda x: x.to_json()) 149 148 150 149 if self.args.format == "html": ··· 168 167 FORMAT).""", 169 168 file=sys.stderr) 170 169 if self.args.input == "-": 171 - print("""Not attempting to download Flame Graph template as script command line 170 + print( 171 + """Not attempting to download Flame Graph template as script command line 172 172 input is disabled due to using live mode. If you want to download the 173 173 template retry without live mode. For example, use 'perf record -a -g 174 174 -F 99 sleep 60' and 'perf script report flamegraph'. Alternatively, ··· 178 176 and place it at: 179 177 /usr/share/d3-flame-graph/d3-flamegraph-base.html""", 180 178 file=sys.stderr) 181 - quit() 179 + sys.exit(1) 182 180 s = None 183 - while s != "y" and s != "n": 184 - s = input("Do you wish to download a template from cdn.jsdelivr.net? (this warning can be suppressed with --allow-download) [yn] ").lower() 181 + while s not in ["y", "n"]: 182 + s = input("Do you wish to download a template from cdn.jsdelivr.net?" + 183 + "(this warning can be suppressed with --allow-download) [yn] " 184 + ).lower() 185 185 if s == "n": 186 - quit() 187 - template = "https://cdn.jsdelivr.net/npm/d3-flame-graph@4.1.3/dist/templates/d3-flamegraph-base.html" 186 + sys.exit(1) 187 + template = ("https://cdn.jsdelivr.net/npm/d3-flame-graph@4.1.3/dist/templates/" 188 + "d3-flamegraph-base.html") 188 189 template_md5sum = "143e0d06ba69b8370b9848dcd6ae3f36" 189 190 190 191 try: 191 - with urllib.request.urlopen(template) as template: 192 + with urllib.request.urlopen(template) as url_template: 192 193 output_str = "".join([ 193 - l.decode("utf-8") for l in template.readlines() 194 + l.decode("utf-8") for l in url_template.readlines() 194 195 ]) 195 196 except Exception as err: 196 197 print(f"Error reading template {template}: {err}\n" 197 198 "a minimal flame graph will be generated", file=sys.stderr) 198 - output_str = minimal_html 199 + output_str = MINIMAL_HTML 199 200 template_md5sum = None 200 201 201 202 if template_md5sum: 202 203 download_md5sum = hashlib.md5(output_str.encode("utf-8")).hexdigest() 203 204 if download_md5sum != template_md5sum: 204 205 s = None 205 - while s != "y" and s != "n": 206 + while s not in ["y", "n"]: 206 207 s = input(f"""Unexpected template md5sum. 207 208 {download_md5sum} != {template_md5sum}, for: 208 209 {output_str} 209 210 continue?[yn] """).lower() 210 211 if s == "n": 211 - quit() 212 + sys.exit(1) 212 213 213 214 output_str = output_str.replace("/** @options_json **/", options_json) 214 215 output_str = output_str.replace("/** @flamegraph_json **/", stacks_json) ··· 225 220 with io.open(sys.stdout.fileno(), "w", encoding="utf-8", closefd=False) as out: 226 221 out.write(output_str) 227 222 else: 228 - print("dumping data to {}".format(output_fn)) 223 + print(f"dumping data to {output_fn}") 229 224 try: 230 225 with io.open(output_fn, "w", encoding="utf-8") as out: 231 226 out.write(output_str) 232 227 except IOError as err: 233 - print("Error writing output file: {}".format(err), file=sys.stderr) 228 + print(f"Error writing output file: {err}", file=sys.stderr) 234 229 sys.exit(1) 235 230 236 231