diff options
Diffstat (limited to 'tools/perf/scripts/python')
-rw-r--r-- | tools/perf/scripts/python/Perf-Trace-Util/Context.c | 20 | ||||
-rwxr-xr-x | tools/perf/scripts/python/bin/flamegraph-report | 2 | ||||
-rwxr-xr-x | tools/perf/scripts/python/exported-sql-viewer.py | 5 | ||||
-rwxr-xr-x | tools/perf/scripts/python/flamegraph.py | 82 | ||||
-rw-r--r-- | tools/perf/scripts/python/mem-phys-addr.py | 177 |
5 files changed, 161 insertions, 125 deletions
diff --git a/tools/perf/scripts/python/Perf-Trace-Util/Context.c b/tools/perf/scripts/python/Perf-Trace-Util/Context.c index 01f54d6724a5..60dcfe56d4d9 100644 --- a/tools/perf/scripts/python/Perf-Trace-Util/Context.c +++ b/tools/perf/scripts/python/Perf-Trace-Util/Context.c @@ -24,16 +24,6 @@ #include "../../../util/srcline.h" #include "../../../util/srccode.h" -#if PY_MAJOR_VERSION < 3 -#define _PyCapsule_GetPointer(arg1, arg2) \ - PyCObject_AsVoidPtr(arg1) -#define _PyBytes_FromStringAndSize(arg1, arg2) \ - PyString_FromStringAndSize((arg1), (arg2)) -#define _PyUnicode_AsUTF8(arg) \ - PyString_AsString(arg) - -PyMODINIT_FUNC initperf_trace_context(void); -#else #define _PyCapsule_GetPointer(arg1, arg2) \ PyCapsule_GetPointer((arg1), (arg2)) #define _PyBytes_FromStringAndSize(arg1, arg2) \ @@ -42,7 +32,6 @@ PyMODINIT_FUNC initperf_trace_context(void); PyUnicode_AsUTF8(arg) PyMODINIT_FUNC PyInit_perf_trace_context(void); -#endif static struct scripting_context *get_args(PyObject *args, const char *name, PyObject **arg2) { @@ -104,7 +93,7 @@ static PyObject *perf_sample_insn(PyObject *obj, PyObject *args) if (c->sample->ip && !c->sample->insn_len && thread__maps(c->al->thread)) { struct machine *machine = maps__machine(thread__maps(c->al->thread)); - script_fetch_insn(c->sample, c->al->thread, machine); + script_fetch_insn(c->sample, c->al->thread, machine, /*native_arch=*/true); } if (!c->sample->insn_len) Py_RETURN_NONE; /* N.B. This is a return statement */ @@ -213,12 +202,6 @@ static PyMethodDef ContextMethods[] = { { NULL, NULL, 0, NULL} }; -#if PY_MAJOR_VERSION < 3 -PyMODINIT_FUNC initperf_trace_context(void) -{ - (void) Py_InitModule("perf_trace_context", ContextMethods); -} -#else PyMODINIT_FUNC PyInit_perf_trace_context(void) { static struct PyModuleDef moduledef = { @@ -240,4 +223,3 @@ PyMODINIT_FUNC PyInit_perf_trace_context(void) return mod; } -#endif diff --git a/tools/perf/scripts/python/bin/flamegraph-report b/tools/perf/scripts/python/bin/flamegraph-report index 53c5dc90c87e..453a6918afbe 100755 --- a/tools/perf/scripts/python/bin/flamegraph-report +++ b/tools/perf/scripts/python/bin/flamegraph-report @@ -1,3 +1,3 @@ #!/bin/bash # description: create flame graphs -perf script -s "$PERF_EXEC_PATH"/scripts/python/flamegraph.py -- "$@" +perf script -s "$PERF_EXEC_PATH"/scripts/python/flamegraph.py "$@" diff --git a/tools/perf/scripts/python/exported-sql-viewer.py b/tools/perf/scripts/python/exported-sql-viewer.py index 121cf61ba1b3..e0b2e7268ef6 100755 --- a/tools/perf/scripts/python/exported-sql-viewer.py +++ b/tools/perf/scripts/python/exported-sql-viewer.py @@ -680,7 +680,10 @@ class CallGraphModelBase(TreeModel): s = value.replace("%", "\\%") s = s.replace("_", "\\_") # Translate * and ? into SQL LIKE pattern characters % and _ - trans = string.maketrans("*?", "%_") + if sys.version_info[0] == 3: + trans = str.maketrans("*?", "%_") + else: + trans = string.maketrans("*?", "%_") match = " LIKE '" + str(s).translate(trans) + "'" else: match = " GLOB '" + str(value) + "'" diff --git a/tools/perf/scripts/python/flamegraph.py b/tools/perf/scripts/python/flamegraph.py index cf7ce8229a6c..ad735990c5be 100755 --- a/tools/perf/scripts/python/flamegraph.py +++ b/tools/perf/scripts/python/flamegraph.py @@ -18,7 +18,6 @@ # pylint: disable=missing-class-docstring # pylint: disable=missing-function-docstring -from __future__ import print_function import argparse import hashlib import io @@ -26,9 +25,10 @@ import json import os import subprocess import sys +from typing import Dict, Optional, Union import urllib.request -minimal_html = """<head> +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> @@ -50,20 +50,20 @@ minimal_html = """<head> # pylint: disable=too-few-public-methods class Node: - def __init__(self, name, libtype): + def __init__(self, name: str, libtype: str): self.name = name # "root" | "kernel" | "" # "" indicates user space self.libtype = libtype - self.value = 0 - self.children = [] + self.value: int = 0 + self.children: list[Node] = [] - def to_json(self): + def to_json(self) -> Dict[str, Union[str, int, list[Dict]]]: return { "n": self.name, "l": self.libtype, "v": self.value, - "c": self.children + "c": [x.to_json() for x in self.children] } @@ -73,7 +73,7 @@ class FlameGraphCLI: self.stack = Node("all", "root") @staticmethod - def get_libtype_from_dso(dso): + def get_libtype_from_dso(dso: Optional[str]) -> str: """ when kernel-debuginfo is installed, dso points to /usr/lib/debug/lib/modules/*/vmlinux @@ -84,7 +84,7 @@ class FlameGraphCLI: return "" @staticmethod - def find_or_create_node(node, name, libtype): + def find_or_create_node(node: Node, name: str, libtype: str) -> Node: for child in node.children: if child.name == name: return child @@ -93,7 +93,12 @@ class FlameGraphCLI: node.children.append(child) return child - def process_event(self, event): + def process_event(self, event) -> None: + # ignore events where the event name does not match + # the one specified by the user + if self.args.event_name and event.get("ev_name") != self.args.event_name: + return + pid = event.get("sample", {}).get("pid", 0) # event["dso"] sometimes contains /usr/lib/debug/lib/modules/*/vmlinux # for user-space processes; let's use pid for kernel or user-space distinction @@ -101,7 +106,7 @@ class FlameGraphCLI: comm = event["comm"] libtype = "kernel" else: - comm = "{} ({})".format(event["comm"], pid) + comm = f"{event['comm']} ({pid})" libtype = "" node = self.find_or_create_node(self.stack, comm, libtype) @@ -116,20 +121,30 @@ class FlameGraphCLI: node = self.find_or_create_node(node, name, libtype) node.value += 1 - def get_report_header(self): + def get_report_header(self) -> str: if self.args.input == "-": # when this script is invoked with "perf script flamegraph", # no perf.data is created and we cannot read the header of it return "" try: - output = subprocess.check_output(["perf", "report", "--header-only"]) - return output.decode("utf-8") + # if the file name other than perf.data is given, + # we read the header of that file + if self.args.input: + output = subprocess.check_output(["perf", "report", "--header-only", + "-i", self.args.input]) + else: + output = subprocess.check_output(["perf", "report", "--header-only"]) + + result = output.decode("utf-8") + if self.args.event_name: + result += "\nFocused event: " + self.args.event_name + return result except Exception as err: # pylint: disable=broad-except - print("Error reading report header: {}".format(err), file=sys.stderr) + print(f"Error reading report header: {err}", file=sys.stderr) return "" - def trace_end(self): + def trace_end(self) -> None: stacks_json = json.dumps(self.stack, default=lambda x: x.to_json()) if self.args.format == "html": @@ -153,7 +168,8 @@ 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 + 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, @@ -162,37 +178,40 @@ https://cdn.jsdelivr.net/npm/d3-flame-graph@4.1.3/dist/templates/d3-flamegraph-b and place it at: /usr/share/d3-flame-graph/d3-flamegraph-base.html""", file=sys.stderr) - quit() + sys.exit(1) 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() + while s not in ["y", "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" + sys.exit(1) + template = ("https://cdn.jsdelivr.net/npm/d3-flame-graph@4.1.3/dist/templates/" + "d3-flamegraph-base.html") template_md5sum = "143e0d06ba69b8370b9848dcd6ae3f36" try: - with urllib.request.urlopen(template) as template: + with urllib.request.urlopen(template) as url_template: output_str = "".join([ - l.decode("utf-8") for l in template.readlines() + l.decode("utf-8") for l in url_template.readlines() ]) except Exception as err: print(f"Error reading template {template}: {err}\n" "a minimal flame graph will be generated", file=sys.stderr) - output_str = minimal_html + output_str = MINIMAL_HTML 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": + while s not in ["y", "n"]: s = input(f"""Unexpected template md5sum. {download_md5sum} != {template_md5sum}, for: {output_str} continue?[yn] """).lower() if s == "n": - quit() + sys.exit(1) output_str = output_str.replace("/** @options_json **/", options_json) output_str = output_str.replace("/** @flamegraph_json **/", stacks_json) @@ -206,12 +225,12 @@ continue?[yn] """).lower() with io.open(sys.stdout.fileno(), "w", encoding="utf-8", closefd=False) as out: out.write(output_str) else: - print("dumping data to {}".format(output_fn)) + print(f"dumping data to {output_fn}") try: with io.open(output_fn, "w", encoding="utf-8") as out: out.write(output_str) except IOError as err: - print("Error writing output file: {}".format(err), file=sys.stderr) + print(f"Error writing output file: {err}", file=sys.stderr) sys.exit(1) @@ -235,6 +254,11 @@ if __name__ == "__main__": default=False, action="store_true", help="allow unprompted downloading of HTML template") + parser.add_argument("-e", "--event", + default="", + dest="event_name", + type=str, + help="specify the event to generate flamegraph for") cli_args = parser.parse_args() cli = FlameGraphCLI(cli_args) diff --git a/tools/perf/scripts/python/mem-phys-addr.py b/tools/perf/scripts/python/mem-phys-addr.py index 1f332e72b9b0..5e237a5a5f1b 100644 --- a/tools/perf/scripts/python/mem-phys-addr.py +++ b/tools/perf/scripts/python/mem-phys-addr.py @@ -3,98 +3,125 @@ # # Copyright (c) 2018, Intel Corporation. -from __future__ import division -from __future__ import print_function - import os import sys -import struct import re import bisect import collections +from dataclasses import dataclass +from typing import (Dict, Optional) sys.path.append(os.environ['PERF_EXEC_PATH'] + \ - '/scripts/python/Perf-Trace-Util/lib/Perf/Trace') + '/scripts/python/Perf-Trace-Util/lib/Perf/Trace') + +@dataclass(frozen=True) +class IomemEntry: + """Read from a line in /proc/iomem""" + begin: int + end: int + indent: int + label: str -#physical address ranges for System RAM -system_ram = [] -#physical address ranges for Persistent Memory -pmem = [] -#file object for proc iomem -f = None -#Count for each type of memory -load_mem_type_cnt = collections.Counter() -#perf event name -event_name = None +# Physical memory layout from /proc/iomem. Key is the indent and then +# a list of ranges. +iomem: Dict[int, list[IomemEntry]] = collections.defaultdict(list) +# Child nodes from the iomem parent. +children: Dict[IomemEntry, set[IomemEntry]] = collections.defaultdict(set) +# Maximum indent seen before an entry in the iomem file. +max_indent: int = 0 +# Count for each range of memory. +load_mem_type_cnt: Dict[IomemEntry, int] = collections.Counter() +# Perf event name set from the first sample in the data. +event_name: Optional[str] = None def parse_iomem(): - global f - f = open('/proc/iomem', 'r') - for i, j in enumerate(f): - m = re.split('-|:',j,2) - if m[2].strip() == 'System RAM': - system_ram.append(int(m[0], 16)) - system_ram.append(int(m[1], 16)) - if m[2].strip() == 'Persistent Memory': - pmem.append(int(m[0], 16)) - pmem.append(int(m[1], 16)) + """Populate iomem from /proc/iomem file""" + global iomem + global max_indent + global children + with open('/proc/iomem', 'r', encoding='ascii') as f: + for line in f: + indent = 0 + while line[indent] == ' ': + indent += 1 + if indent > max_indent: + max_indent = indent + m = re.split('-|:', line, 2) + begin = int(m[0], 16) + end = int(m[1], 16) + label = m[2].strip() + entry = IomemEntry(begin, end, indent, label) + # Before adding entry, search for a parent node using its begin. + if indent > 0: + parent = find_memory_type(begin) + assert parent, f"Given indent expected a parent for {label}" + children[parent].add(entry) + iomem[indent].append(entry) -def print_memory_type(): - print("Event: %s" % (event_name)) - print("%-40s %10s %10s\n" % ("Memory type", "count", "percentage"), end='') - print("%-40s %10s %10s\n" % ("----------------------------------------", - "-----------", "-----------"), - end=''); - total = sum(load_mem_type_cnt.values()) - for mem_type, count in sorted(load_mem_type_cnt.most_common(), \ - key = lambda kv: (kv[1], kv[0]), reverse = True): - print("%-40s %10d %10.1f%%\n" % - (mem_type, count, 100 * count / total), - end='') +def find_memory_type(phys_addr) -> Optional[IomemEntry]: + """Search iomem for the range containing phys_addr with the maximum indent""" + for i in range(max_indent, -1, -1): + if i not in iomem: + continue + position = bisect.bisect_right(iomem[i], phys_addr, + key=lambda entry: entry.begin) + if position is None: + continue + iomem_entry = iomem[i][position-1] + if iomem_entry.begin <= phys_addr <= iomem_entry.end: + return iomem_entry + print(f"Didn't find {phys_addr}") + return None -def trace_begin(): - parse_iomem() +def print_memory_type(): + print(f"Event: {event_name}") + print(f"{'Memory type':<40} {'count':>10} {'percentage':>10}") + print(f"{'-' * 40:<40} {'-' * 10:>10} {'-' * 10:>10}") + total = sum(load_mem_type_cnt.values()) + # Add count from children into the parent. + for i in range(max_indent, -1, -1): + if i not in iomem: + continue + for entry in iomem[i]: + global children + for child in children[entry]: + if load_mem_type_cnt[child] > 0: + load_mem_type_cnt[entry] += load_mem_type_cnt[child] -def trace_end(): - print_memory_type() - f.close() + def print_entries(entries): + """Print counts from parents down to their children""" + global children + for entry in sorted(entries, + key = lambda entry: load_mem_type_cnt[entry], + reverse = True): + count = load_mem_type_cnt[entry] + if count > 0: + mem_type = ' ' * entry.indent + f"{entry.begin:x}-{entry.end:x} : {entry.label}" + percent = 100 * count / total + print(f"{mem_type:<40} {count:>10} {percent:>10.1f}") + print_entries(children[entry]) -def is_system_ram(phys_addr): - #/proc/iomem is sorted - position = bisect.bisect(system_ram, phys_addr) - if position % 2 == 0: - return False - return True + print_entries(iomem[0]) -def is_persistent_mem(phys_addr): - position = bisect.bisect(pmem, phys_addr) - if position % 2 == 0: - return False - return True +def trace_begin(): + parse_iomem() -def find_memory_type(phys_addr): - if phys_addr == 0: - return "N/A" - if is_system_ram(phys_addr): - return "System RAM" +def trace_end(): + print_memory_type() - if is_persistent_mem(phys_addr): - return "Persistent Memory" +def process_event(param_dict): + if "sample" not in param_dict: + return - #slow path, search all - f.seek(0, 0) - for j in f: - m = re.split('-|:',j,2) - if int(m[0], 16) <= phys_addr <= int(m[1], 16): - return m[2] - return "N/A" + sample = param_dict["sample"] + if "phys_addr" not in sample: + return -def process_event(param_dict): - name = param_dict["ev_name"] - sample = param_dict["sample"] - phys_addr = sample["phys_addr"] + phys_addr = sample["phys_addr"] + entry = find_memory_type(phys_addr) + if entry: + load_mem_type_cnt[entry] += 1 - global event_name - if event_name == None: - event_name = name - load_mem_type_cnt[find_memory_type(phys_addr)] += 1 + global event_name + if event_name is None: + event_name = param_dict["ev_name"] |