0001
0002
0003
0004
0005
0006
0007
0008
0009
0010
0011
0012
0013
0014
0015
0016
0017
0018
0019
0020
0021 from __future__ import print_function
0022 import sys
0023 import os
0024 import io
0025 import argparse
0026 import json
0027 import subprocess
0028
0029
0030 class Node:
0031 def __init__(self, name, libtype):
0032 self.name = name
0033
0034
0035 self.libtype = libtype
0036 self.value = 0
0037 self.children = []
0038
0039 def to_json(self):
0040 return {
0041 "n": self.name,
0042 "l": self.libtype,
0043 "v": self.value,
0044 "c": self.children
0045 }
0046
0047
0048 class FlameGraphCLI:
0049 def __init__(self, args):
0050 self.args = args
0051 self.stack = Node("all", "root")
0052
0053 if self.args.format == "html" and \
0054 not os.path.isfile(self.args.template):
0055 print("Flame Graph template {} does not exist. Please install "
0056 "the js-d3-flame-graph (RPM) or libjs-d3-flame-graph (deb) "
0057 "package, specify an existing flame graph template "
0058 "(--template PATH) or another output format "
0059 "(--format FORMAT).".format(self.args.template),
0060 file=sys.stderr)
0061 sys.exit(1)
0062
0063 @staticmethod
0064 def get_libtype_from_dso(dso):
0065 """
0066 when kernel-debuginfo is installed,
0067 dso points to /usr/lib/debug/lib/modules/*/vmlinux
0068 """
0069 if dso and (dso == "[kernel.kallsyms]" or dso.endswith("/vmlinux")):
0070 return "kernel"
0071
0072 return ""
0073
0074 @staticmethod
0075 def find_or_create_node(node, name, libtype):
0076 for child in node.children:
0077 if child.name == name:
0078 return child
0079
0080 child = Node(name, libtype)
0081 node.children.append(child)
0082 return child
0083
0084 def process_event(self, event):
0085 pid = event.get("sample", {}).get("pid", 0)
0086
0087
0088 if pid == 0:
0089 comm = event["comm"]
0090 libtype = "kernel"
0091 else:
0092 comm = "{} ({})".format(event["comm"], pid)
0093 libtype = ""
0094 node = self.find_or_create_node(self.stack, comm, libtype)
0095
0096 if "callchain" in event:
0097 for entry in reversed(event["callchain"]):
0098 name = entry.get("sym", {}).get("name", "[unknown]")
0099 libtype = self.get_libtype_from_dso(entry.get("dso"))
0100 node = self.find_or_create_node(node, name, libtype)
0101 else:
0102 name = event.get("symbol", "[unknown]")
0103 libtype = self.get_libtype_from_dso(event.get("dso"))
0104 node = self.find_or_create_node(node, name, libtype)
0105 node.value += 1
0106
0107 def get_report_header(self):
0108 if self.args.input == "-":
0109
0110
0111 return ""
0112
0113 try:
0114 output = subprocess.check_output(["perf", "report", "--header-only"])
0115 return output.decode("utf-8")
0116 except Exception as err:
0117 print("Error reading report header: {}".format(err), file=sys.stderr)
0118 return ""
0119
0120 def trace_end(self):
0121 stacks_json = json.dumps(self.stack, default=lambda x: x.to_json())
0122
0123 if self.args.format == "html":
0124 report_header = self.get_report_header()
0125 options = {
0126 "colorscheme": self.args.colorscheme,
0127 "context": report_header
0128 }
0129 options_json = json.dumps(options)
0130
0131 try:
0132 with io.open(self.args.template, encoding="utf-8") as template:
0133 output_str = (
0134 template.read()
0135 .replace("/** @options_json **/", options_json)
0136 .replace("/** @flamegraph_json **/", stacks_json)
0137 )
0138 except IOError as err:
0139 print("Error reading template file: {}".format(err), file=sys.stderr)
0140 sys.exit(1)
0141 output_fn = self.args.output or "flamegraph.html"
0142 else:
0143 output_str = stacks_json
0144 output_fn = self.args.output or "stacks.json"
0145
0146 if output_fn == "-":
0147 with io.open(sys.stdout.fileno(), "w", encoding="utf-8", closefd=False) as out:
0148 out.write(output_str)
0149 else:
0150 print("dumping data to {}".format(output_fn))
0151 try:
0152 with io.open(output_fn, "w", encoding="utf-8") as out:
0153 out.write(output_str)
0154 except IOError as err:
0155 print("Error writing output file: {}".format(err), file=sys.stderr)
0156 sys.exit(1)
0157
0158
0159 if __name__ == "__main__":
0160 parser = argparse.ArgumentParser(description="Create flame graphs.")
0161 parser.add_argument("-f", "--format",
0162 default="html", choices=["json", "html"],
0163 help="output file format")
0164 parser.add_argument("-o", "--output",
0165 help="output file name")
0166 parser.add_argument("--template",
0167 default="/usr/share/d3-flame-graph/d3-flamegraph-base.html",
0168 help="path to flame graph HTML template")
0169 parser.add_argument("--colorscheme",
0170 default="blue-green",
0171 help="flame graph color scheme",
0172 choices=["blue-green", "orange"])
0173 parser.add_argument("-i", "--input",
0174 help=argparse.SUPPRESS)
0175
0176 cli_args = parser.parse_args()
0177 cli = FlameGraphCLI(cli_args)
0178
0179 process_event = cli.process_event
0180 trace_end = cli.trace_end