diff options
Diffstat (limited to 'tools/perf/scripts/python/flamegraph.py')
| -rwxr-xr-x | tools/perf/scripts/python/flamegraph.py | 108 | 
1 files changed, 81 insertions, 27 deletions
| diff --git a/tools/perf/scripts/python/flamegraph.py b/tools/perf/scripts/python/flamegraph.py index 65780013f745..b6af1dd5f816 100755 --- a/tools/perf/scripts/python/flamegraph.py +++ b/tools/perf/scripts/python/flamegraph.py @@ -13,6 +13,10 @@  # Written by Andreas Gerstmayr <agerstmayr@redhat.com>  # Flame Graphs invented by Brendan Gregg <bgregg@netflix.com>  # Works in tandem with d3-flame-graph by Martin Spier <mspier@netflix.com> +# +# pylint: disable=missing-module-docstring +# pylint: disable=missing-class-docstring +# pylint: disable=missing-function-docstring  from __future__ import print_function  import sys @@ -20,16 +24,19 @@ import os  import io  import argparse  import json +import subprocess - +# pylint: disable=too-few-public-methods  class Node: -    def __init__(self, name, libtype=""): +    def __init__(self, name, libtype):          self.name = name +        # "root" | "kernel" | "" +        # "" indicates user space          self.libtype = libtype          self.value = 0          self.children = [] -    def toJSON(self): +    def to_json(self):          return {              "n": self.name,              "l": self.libtype, @@ -41,7 +48,7 @@ class Node:  class FlameGraphCLI:      def __init__(self, args):          self.args = args -        self.stack = Node("root") +        self.stack = Node("all", "root")          if self.args.format == "html" and \                  not os.path.isfile(self.args.template): @@ -53,13 +60,21 @@ class FlameGraphCLI:                    file=sys.stderr)              sys.exit(1) -    def find_or_create_node(self, node, name, dso): -        libtype = "kernel" if dso == "[kernel.kallsyms]" else "" -        if name is None: -            name = "[unknown]" +    @staticmethod +    def get_libtype_from_dso(dso): +        """ +        when kernel-debuginfo is installed, +        dso points to /usr/lib/debug/lib/modules/*/vmlinux +        """ +        if dso and (dso == "[kernel.kallsyms]" or dso.endswith("/vmlinux")): +            return "kernel" +        return "" + +    @staticmethod +    def find_or_create_node(node, name, libtype):          for child in node.children: -            if child.name == name and child.libtype == libtype: +            if child.name == name:                  return child          child = Node(name, libtype) @@ -67,30 +82,65 @@ class FlameGraphCLI:          return child      def process_event(self, event): -        node = self.find_or_create_node(self.stack, event["comm"], None) +        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 +        if pid == 0: +            comm = event["comm"] +            libtype = "kernel" +        else: +            comm = "{} ({})".format(event["comm"], pid) +            libtype = "" +        node = self.find_or_create_node(self.stack, comm, libtype) +          if "callchain" in event: -            for entry in reversed(event['callchain']): -                node = self.find_or_create_node( -                    node, entry.get("sym", {}).get("name"), event.get("dso")) +            for entry in reversed(event["callchain"]): +                name = entry.get("sym", {}).get("name", "[unknown]") +                libtype = self.get_libtype_from_dso(entry.get("dso")) +                node = self.find_or_create_node(node, name, libtype)          else: -            node = self.find_or_create_node( -                node, entry.get("symbol"), event.get("dso")) +            name = event.get("symbol", "[unknown]") +            libtype = self.get_libtype_from_dso(event.get("dso")) +            node = self.find_or_create_node(node, name, libtype)          node.value += 1 +    def get_report_header(self): +        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") +        except Exception as err:  # pylint: disable=broad-except +            print("Error reading report header: {}".format(err), file=sys.stderr) +            return "" +      def trace_end(self): -        json_str = json.dumps(self.stack, default=lambda x: x.toJSON()) +        stacks_json = json.dumps(self.stack, default=lambda x: x.to_json())          if self.args.format == "html": +            report_header = self.get_report_header() +            options = { +                "colorscheme": self.args.colorscheme, +                "context": report_header +            } +            options_json = json.dumps(options) +              try: -                with io.open(self.args.template, encoding="utf-8") as f: -                    output_str = f.read().replace("/** @flamegraph_json **/", -                                                  json_str) -            except IOError as e: -                print("Error reading template file: {}".format(e), file=sys.stderr) +                with io.open(self.args.template, encoding="utf-8") as template: +                    output_str = ( +                        template.read() +                        .replace("/** @options_json **/", options_json) +                        .replace("/** @flamegraph_json **/", stacks_json) +                    ) +            except IOError as err: +                print("Error reading template file: {}".format(err), file=sys.stderr)                  sys.exit(1)              output_fn = self.args.output or "flamegraph.html"          else: -            output_str = json_str +            output_str = stacks_json              output_fn = self.args.output or "stacks.json"          if output_fn == "-": @@ -101,8 +151,8 @@ class FlameGraphCLI:              try:                  with io.open(output_fn, "w", encoding="utf-8") as out:                      out.write(output_str) -            except IOError as e: -                print("Error writing output file: {}".format(e), file=sys.stderr) +            except IOError as err: +                print("Error writing output file: {}".format(err), file=sys.stderr)                  sys.exit(1) @@ -115,12 +165,16 @@ if __name__ == "__main__":                          help="output file name")      parser.add_argument("--template",                          default="/usr/share/d3-flame-graph/d3-flamegraph-base.html", -                        help="path to flamegraph HTML template") +                        help="path to flame graph HTML template") +    parser.add_argument("--colorscheme", +                        default="blue-green", +                        help="flame graph color scheme", +                        choices=["blue-green", "orange"])      parser.add_argument("-i", "--input",                          help=argparse.SUPPRESS) -    args = parser.parse_args() -    cli = FlameGraphCLI(args) +    cli_args = parser.parse_args() +    cli = FlameGraphCLI(cli_args)      process_event = cli.process_event      trace_end = cli.trace_end | 
