History

perf flamegraph: flamegraph.py script improvements

* 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

Andreas Gerstmayr committed 5 years ago

Commit c611e4f1 parent bb07d621 file · +81 −27Browse files

Showing the changes to tools/perf/scripts/python (1 of 1 file). Show all

Unified · Split

tools/perf/scripts/python/flamegraph.py+81−27
@@ -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
17from __future__ import print_function21from __future__ import print_function
18import sys22import sys
@@ -20,16 +24,19 @@ import os · 1 unmodified line
20import io24import io
21import argparse25import argparse
22import json26import json
27import subprocess
23 28
24 29# pylint: disable=too-few-public-methods
25class Node:30class Node:
26 def __init__(self, name, libtype=""):31 def __init__(self, name, libtype):
27 self.name = name32 self.name = name
33 # "root" | "kernel" | ""
34 # "" indicates user space
28 self.libtype = libtype35 self.libtype = libtype
29 self.value = 036 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
41class FlameGraphCLI:48class FlameGraphCLI:
42 def __init__(self, args):49 def __init__(self, args):
43 self.args = args50 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 child78 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 child82 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 += 1105 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_str143 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_event179 process_event = cli.process_event
126 trace_end = cli.trace_end180 trace_end = cli.trace_end