* display perf.data header * display PIDs of user stacks * added option to change color scheme * default to blue/green color scheme to improve accessibility * correctly identify kernel stacks when kernel-debuginfo is installed
Showing the changes to tools/perf/scripts/python (1 of 1 file). Show all
Unified · Split
| @@ -13,6 +13,10 @@ · 12 unmodified lines | |||
| 13 | # Written by Andreas Gerstmayr <agerstmayr@redhat.com> | 13 | # Written by Andreas Gerstmayr <agerstmayr@redhat.com> |
| 14 | # Flame Graphs invented by Brendan Gregg <bgregg@netflix.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> | 15 | # Works in tandem with d3-flame-graph by Martin Spier <mspier@netflix.com> |
| 16 | # | ||
| 17 | # pylint: disable=missing-module-docstring | ||
| 18 | # pylint: disable=missing-class-docstring | ||
| 19 | # pylint: disable=missing-function-docstring | ||
| 16 | 20 | ||
| 17 | from __future__ import print_function | 21 | from __future__ import print_function |
| 18 | import sys | 22 | import sys |
| @@ -20,16 +24,19 @@ import os · 1 unmodified line | |||
| 20 | import io | 24 | import io |
| 21 | import argparse | 25 | import argparse |
| 22 | import json | 26 | import json |
| 27 | import subprocess | ||
| 23 | 28 | ||
| 24 | 29 | # pylint: disable=too-few-public-methods | |
| 25 | class Node: | 30 | class Node: |
| 26 | def __init__(self, name, libtype=""): | 31 | def __init__(self, name, libtype): |
| 27 | self.name = name | 32 | self.name = name |
| 33 | # "root" | "kernel" | "" | ||
| 34 | # "" indicates user space | ||
| 28 | self.libtype = libtype | 35 | self.libtype = libtype |
| 29 | self.value = 0 | 36 | self.value = 0 |
| 30 | self.children = [] | 37 | self.children = [] |
| 31 | 38 | ||
| 32 | def toJSON(self): | 39 | def to_json(self): |
| 33 | return { | 40 | return { |
| 34 | "n": self.name, | 41 | "n": self.name, |
| 35 | "l": self.libtype, | 42 | "l": self.libtype, |
| @@ -41,7 +48,7 @@ class Node: · 5 unmodified lines | |||
| 41 | class FlameGraphCLI: | 48 | class FlameGraphCLI: |
| 42 | def __init__(self, args): | 49 | def __init__(self, args): |
| 43 | self.args = args | 50 | self.args = args |
| 44 | self.stack = Node("root") | 51 | self.stack = Node("all", "root") |
| 45 | 52 | ||
| 46 | if self.args.format == "html" and \ | 53 | if self.args.format == "html" and \ |
| 47 | not os.path.isfile(self.args.template): | 54 | not os.path.isfile(self.args.template): |
| @@ -53,13 +60,21 @@ class FlameGraphCLI: · 5 unmodified lines | |||
| 53 | file=sys.stderr) | 60 | file=sys.stderr) |
| 54 | sys.exit(1) | 61 | sys.exit(1) |
| 55 | 62 | ||
| 56 | def find_or_create_node(self, node, name, dso): | 63 | @staticmethod |
| 57 | libtype = "kernel" if dso == "[kernel.kallsyms]" else "" | 64 | def get_libtype_from_dso(dso): |
| 58 | if name is None: | 65 | """ |
| 59 | name = "[unknown]" | 66 | when kernel-debuginfo is installed, |
| 67 | dso points to /usr/lib/debug/lib/modules/*/vmlinux | ||
| 68 | """ | ||
| 69 | if dso and (dso == "[kernel.kallsyms]" or dso.endswith("/vmlinux")): | ||
| 70 | return "kernel" | ||
| 60 | 71 | ||
| 72 | return "" | ||
| 73 | |||
| 74 | @staticmethod | ||
| 75 | def find_or_create_node(node, name, libtype): | ||
| 61 | for child in node.children: | 76 | for child in node.children: |
| 62 | if child.name == name and child.libtype == libtype: | 77 | if child.name == name: |
| 63 | return child | 78 | return child |
| 64 | 79 | ||
| 65 | child = Node(name, libtype) | 80 | child = Node(name, libtype) |
| @@ -67,30 +82,65 @@ class FlameGraphCLI: · 1 unmodified line | |||
| 67 | return child | 82 | return child |
| 68 | 83 | ||
| 69 | def process_event(self, event): | 84 | def process_event(self, event): |
| 70 | node = self.find_or_create_node(self.stack, event["comm"], None) | 85 | pid = event.get("sample", {}).get("pid", 0) |
| 86 | # event["dso"] sometimes contains /usr/lib/debug/lib/modules/*/vmlinux | ||
| 87 | # for user-space processes; let's use pid for kernel or user-space distinction | ||
| 88 | if pid == 0: | ||
| 89 | comm = event["comm"] | ||
| 90 | libtype = "kernel" | ||
| 91 | else: | ||
| 92 | comm = "{} ({})".format(event["comm"], pid) | ||
| 93 | libtype = "" | ||
| 94 | node = self.find_or_create_node(self.stack, comm, libtype) | ||
| 95 | |||
| 71 | if "callchain" in event: | 96 | if "callchain" in event: |
| 72 | for entry in reversed(event['callchain']): | 97 | for entry in reversed(event["callchain"]): |
| 73 | node = self.find_or_create_node( | 98 | name = entry.get("sym", {}).get("name", "[unknown]") |
| 74 | node, entry.get("sym", {}).get("name"), event.get("dso")) | 99 | libtype = self.get_libtype_from_dso(entry.get("dso")) |
| 100 | node = self.find_or_create_node(node, name, libtype) | ||
| 75 | else: | 101 | else: |
| 76 | node = self.find_or_create_node( | 102 | name = event.get("symbol", "[unknown]") |
| 77 | node, entry.get("symbol"), event.get("dso")) | 103 | libtype = self.get_libtype_from_dso(event.get("dso")) |
| 104 | node = self.find_or_create_node(node, name, libtype) | ||
| 78 | node.value += 1 | 105 | node.value += 1 |
| 79 | 106 | ||
| 107 | def get_report_header(self): | ||
| 108 | if self.args.input == "-": | ||
| 109 | # when this script is invoked with "perf script flamegraph", | ||
| 110 | # no perf.data is created and we cannot read the header of it | ||
| 111 | return "" | ||
| 112 | |||
| 113 | try: | ||
| 114 | output = subprocess.check_output(["perf", "report", "--header-only"]) | ||
| 115 | return output.decode("utf-8") | ||
| 116 | except Exception as err: # pylint: disable=broad-except | ||
| 117 | print("Error reading report header: {}".format(err), file=sys.stderr) | ||
| 118 | return "" | ||
| 119 | |||
| 80 | def trace_end(self): | 120 | def trace_end(self): |
| 81 | json_str = json.dumps(self.stack, default=lambda x: x.toJSON()) | 121 | stacks_json = json.dumps(self.stack, default=lambda x: x.to_json()) |
| 82 | 122 | ||
| 83 | if self.args.format == "html": | 123 | if self.args.format == "html": |
| 124 | report_header = self.get_report_header() | ||
| 125 | options = { | ||
| 126 | "colorscheme": self.args.colorscheme, | ||
| 127 | "context": report_header | ||
| 128 | } | ||
| 129 | options_json = json.dumps(options) | ||
| 130 | |||
| 84 | try: | 131 | try: |
| 85 | with io.open(self.args.template, encoding="utf-8") as f: | 132 | with io.open(self.args.template, encoding="utf-8") as template: |
| 86 | output_str = f.read().replace("/** @flamegraph_json **/", | 133 | output_str = ( |
| 87 | json_str) | 134 | template.read() |
| 88 | except IOError as e: | 135 | .replace("/** @options_json **/", options_json) |
| 89 | print("Error reading template file: {}".format(e), file=sys.stderr) | 136 | .replace("/** @flamegraph_json **/", stacks_json) |
| 137 | ) | ||
| 138 | except IOError as err: | ||
| 139 | print("Error reading template file: {}".format(err), file=sys.stderr) | ||
| 90 | sys.exit(1) | 140 | sys.exit(1) |
| 91 | output_fn = self.args.output or "flamegraph.html" | 141 | output_fn = self.args.output or "flamegraph.html" |
| 92 | else: | 142 | else: |
| 93 | output_str = json_str | 143 | output_str = stacks_json |
| 94 | output_fn = self.args.output or "stacks.json" | 144 | output_fn = self.args.output or "stacks.json" |
| 95 | 145 | ||
| 96 | if output_fn == "-": | 146 | if output_fn == "-": |
| @@ -101,8 +151,8 @@ class FlameGraphCLI: · 4 unmodified lines | |||
| 101 | try: | 151 | try: |
| 102 | with io.open(output_fn, "w", encoding="utf-8") as out: | 152 | with io.open(output_fn, "w", encoding="utf-8") as out: |
| 103 | out.write(output_str) | 153 | out.write(output_str) |
| 104 | except IOError as e: | 154 | except IOError as err: |
| 105 | print("Error writing output file: {}".format(e), file=sys.stderr) | 155 | print("Error writing output file: {}".format(err), file=sys.stderr) |
| 106 | sys.exit(1) | 156 | sys.exit(1) |
| 107 | 157 | ||
| 108 | 158 | ||
| @@ -115,12 +165,16 @@ if __name__ == "__main__": · 6 unmodified lines | |||
| 115 | help="output file name") | 165 | help="output file name") |
| 116 | parser.add_argument("--template", | 166 | parser.add_argument("--template", |
| 117 | default="/usr/share/d3-flame-graph/d3-flamegraph-base.html", | 167 | default="/usr/share/d3-flame-graph/d3-flamegraph-base.html", |
| 118 | help="path to flamegraph HTML template") | 168 | help="path to flame graph HTML template") |
| 169 | parser.add_argument("--colorscheme", | ||
| 170 | default="blue-green", | ||
| 171 | help="flame graph color scheme", | ||
| 172 | choices=["blue-green", "orange"]) | ||
| 119 | parser.add_argument("-i", "--input", | 173 | parser.add_argument("-i", "--input", |
| 120 | help=argparse.SUPPRESS) | 174 | help=argparse.SUPPRESS) |
| 121 | 175 | ||
| 122 | args = parser.parse_args() | 176 | cli_args = parser.parse_args() |
| 123 | cli = FlameGraphCLI(args) | 177 | cli = FlameGraphCLI(cli_args) |
| 124 | 178 | ||
| 125 | process_event = cli.process_event | 179 | process_event = cli.process_event |
| 126 | trace_end = cli.trace_end | 180 | trace_end = cli.trace_end |