aboutsummaryrefslogtreecommitdiffstats
path: root/tools/perf/scripts/python/flamegraph.py
blob: cf7ce8229a6cae61848c735fd0a38cb5748a7dd2 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
# flamegraph.py - create flame graphs from perf samples
# SPDX-License-Identifier: GPL-2.0
#
# Usage:
#
#     perf record -a -g -F 99 sleep 60
#     perf script report flamegraph
#
# Combined:
#
#     perf script flamegraph -a -F 99 sleep 60
#
# 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 argparse
import hashlib
import io
import json
import os
import subprocess
import sys
import urllib.request

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>
  <div id="chart"></div>
  <script type="text/javascript" src="https://d3js.org/d3.v7.js"></script>
  <script type="text/javascript" src="https://cdn.jsdelivr.net/npm/d3-flame-graph@4.1.3/dist/d3-flamegraph.min.js"></script>
  <script type="text/javascript">
  const stacks = [/** @flamegraph_json **/];
  // Note, options is unused.
  const options = [/** @options_json **/];

  var chart = flamegraph();
  d3.select("#chart")
        .datum(stacks[0])
        .call(chart);
  </script>
</body>
"""

# pylint: disable=too-few-public-methods
class Node:
    def __init__(self, name, libtype):
        self.name = name
        # "root" | "kernel" | ""
        # "" indicates user space
        self.libtype = libtype
        self.value = 0
        self.children = []

    def to_json(self):
        return {
            "n": self.name,
            "l": self.libtype,
            "v": self.value,
            "c": self.children
        }


class FlameGraphCLI:
    def __init__(self, args):
        self.args = args
        self.stack = Node("all", "root")

    @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:
                return child

        child = Node(name, libtype)
        node.children.append(child)
        return child

    def process_event(self, event):
        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"]):
                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:
            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):
        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)

            template_md5sum = None
            if self.args.format == "html":
                if os.path.isfile(self.args.template):
                    template = f"file://{self.args.template}"
                else:
                    if not self.args.allow_download:
                        print(f"""Warning: Flame Graph template '{self.args.template}'
does not exist. To avoid this please install a package such as the
js-d3-flame-graph or libjs-d3-flame-graph, specify an existing flame
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
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,
download the template from:
https://cdn.jsdelivr.net/npm/d3-flame-graph@4.1.3/dist/templates/d3-flamegraph-base.html
and place it at:
/usr/share/d3-flame-graph/d3-flamegraph-base.html""",
                                  file=sys.stderr)
                            quit()
                        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()
                        if s == "n":
                            quit()
                    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:
                    output_str = "".join([
                        l.decode("utf-8") for l in 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
                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":
                        s = input(f"""Unexpected template md5sum.
{download_md5sum} != {template_md5sum}, for:
{output_str}
continue?[yn] """).lower()
                    if s == "n":
                        quit()

            output_str = output_str.replace("/** @options_json **/", options_json)
            output_str = output_str.replace("/** @flamegraph_json **/", stacks_json)

            output_fn = self.args.output or "flamegraph.html"
        else:
            output_str = stacks_json
            output_fn = self.args.output or "stacks.json"

        if output_fn == "-":
            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))
            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)
                sys.exit(1)


if __name__ == "__main__":
    parser = argparse.ArgumentParser(description="Create flame graphs.")
    parser.add_argument("-f", "--format",
                        default="html", choices=["json", "html"],
                        help="output file format")
    parser.add_argument("-o", "--output",
                        help="output file name")
    parser.add_argument("--template",
                        default="/usr/share/d3-flame-graph/d3-flamegraph-base.html",
                        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)
    parser.add_argument("--allow-download",
                        default=False,
                        action="store_true",
                        help="allow unprompted downloading of HTML template")

    cli_args = parser.parse_args()
    cli = FlameGraphCLI(cli_args)

    process_event = cli.process_event
    trace_end = cli.trace_end
Some Infineon devices have a issue where the status register will get stuck with a quick REQUEST_USE / COMMAND_READY sequence. This is not simply a matter of requiring a longer timeout; the work around is to retry the command submission. Add appropriate logic to do this in the send path. This is fixed in later firmware revisions, but those are not always available, and cannot generally be easily updated from outside a firmware environment. Testing has been performed with a simple repeated loop of doing a TPM2_CC_GET_CAPABILITY for TPM_CAP_PROP_MANUFACTURER using the Go code at: https://the.earth.li/~noodles/tpm-stuff/timeout-reproducer-simple.go It can take several hours to reproduce, and several million operations. Signed-off-by: Jonathan McDowell <noodles@meta.com> Reviewed-by: Jarkko Sakkinen <jarkko@kernel.org> Signed-off-by: Jarkko Sakkinen <jarkko@kernel.org> 2025-03-27tpm, tpm_tis: Fix timeout handling when waiting for TPM statusJonathan McDowell1-2/+1 The change to only use interrupts to handle supported status changes introduced an issue when it is necessary to poll for the status. Rather than checking for the status after sleeping the code now sleeps after the check. This means a correct, but slower, status change on the part of the TPM can be missed, resulting in a spurious timeout error, especially on a more loaded system. Switch back to sleeping *then* checking. An up front check of the status has been done at the start of the function, so this does not cause an additional delay when the status is already what we're looking for. Cc: stable@vger.kernel.org # v6.4+ Fixes: e87fcf0dc2b4 ("tpm, tpm_tis: Only handle supported interrupts") Signed-off-by: Jonathan McDowell <noodles@meta.com> Reviewed-by: Michal Suchánek <msuchanek@suse.de> Reviewed-by: Lino Sanfilippo <l.sanfilippo@kunbus.com> Reviewed-by: Jarkko Sakkinen <jarkko@kernel.org> Signed-off-by: Jarkko Sakkinen <jarkko@kernel.org> 2025-03-27tpm: Convert warn to dbg in tpm2_start_auth_session()Jonathan McDowell1-1/+1 TPM2 sessions have been flushed lazily since commit df745e25098dc ("tpm: Lazily flush the auth session"). If /dev/tpm{rm}0 is not accessed in-between two in-kernel calls, it is possible that a TPM2 session is re-started before the previous one has been completed. This causes a spurios warning in a legit run-time condition, which is also correctly addressed with a fast return path: [ 2.944047] tpm tpm0: auth session is active Address the issue by changing dev_warn_once() call to a dev_dbg_once() call. [jarkko: Rewrote the commit message, and instead of dropping converted to a debug message.] Signed-off-by: Jonathan McDowell <noodles@meta.com> Signed-off-by: Jarkko Sakkinen <jarkko@kernel.org> 2025-03-27tpm: Lazily flush auth session when getting random dataJonathan McDowell1-1/+0 Lazy flushing of TPM auth sessions was introduced to speed up IMA measurments into the TPM. Make use of it in tpm2_get_random as well, which has the added benefit of not needlessly cleaning up the session that IMA is using when there are no userspace accesses taking place. Command trace before for every call: hwrng (0x00000161): 14 (52965242 ns) hwrng (0x00000176): 48 (161612432 ns) hwrng (0x00000165): 10 (2410494 ns) hwrng (0x0000017B): 117 (70699883 ns) hwrng (0x0000017B): 117 (70959666 ns) hwrng (0x00000165): 10 (2756827 ns) After, with repeated calls showing no setup: hwrng (0x00000161): 14 (53044582 ns) hwrng (0x00000176): 48 (160491333 ns) hwrng (0x00000165): 10 (2408220 ns) hwrng (0x0000017B): 117 (70695037 ns) hwrng (0x0000017B): 117 (70994984 ns) hwrng (0x0000017B): 117 (70195388 ns) hwrng (0x0000017B): 117 (70973835 ns) Signed-off-by: Jonathan McDowell <noodles@meta.com> Reviewed-by: Jarkko Sakkinen <jarkko@kernel.org> Signed-off-by: Jarkko Sakkinen <jarkko@kernel.org> 2025-03-27tpm: ftpm_tee: remove incorrect of_match_ptr annotationArnd Bergmann1-1/+1 Building with W=1 shows a warning about of_ftpm_tee_ids being unused when CONFIG_OF is disabled: drivers/char/tpm/tpm_ftpm_tee.c:356:34: error: unused variable 'of_ftpm_tee_ids' [-Werror,-Wunused-const-variable] Drop the unnecessary of_match_ptr(). Signed-off-by: Arnd Bergmann <arnd@arndb.de> Reviewed-by: Sumit Garg <sumit.garg@kernel.org> Signed-off-by: Jarkko Sakkinen <jarkko@kernel.org> 2025-03-27tpm: do not start chip while suspendedThadeu Lima de Souza Cascardo2-7/+5 Checking TPM_CHIP_FLAG_SUSPENDED after the call to tpm_find_get_ops() can lead to a spurious tpm_chip_start() call: [35985.503771] i2c i2c-1: Transfer while suspended [35985.503796] WARNING: CPU: 0 PID: 74 at drivers/i2c/i2c-core.h:56 __i2c_transfer+0xbe/0x810 [35985.503802] Modules linked in: [35985.503808] CPU: 0 UID: 0 PID: 74 Comm: hwrng Tainted: G W 6.13.0-next-20250203-00005-gfa0cb5642941 #19 9c3d7f78192f2d38e32010ac9c90fdc71109ef6f [35985.503814] Tainted: [W]=WARN [35985.503817] Hardware name: Google Morphius/Morphius, BIOS Google_Morphius.13434.858.0 10/26/2023 [35985.503819] RIP: 0010:__i2c_transfer+0xbe/0x810 [35985.503825] Code: 30 01 00 00 4c 89 f7 e8 40 fe d8 ff 48 8b 93 80 01 00 00 48 85 d2 75 03 49 8b 16 48 c7 c7 0a fb 7c a7 48 89 c6 e8 32 ad b0 fe <0f> 0b b8 94 ff ff ff e9 33 04 00 00 be 02 00 00 00 83 fd 02 0f 5 [35985.503828] RSP: 0018:ffffa106c0333d30 EFLAGS: 00010246 [35985.503833] RAX: 074ba64aa20f7000 RBX: ffff8aa4c1167120 RCX: 0000000000000000 [35985.503836] RDX: 0000000000000000 RSI: ffffffffa77ab0e4 RDI: 0000000000000001 [35985.503838] RBP: 0000000000000001 R08: 0000000000000001 R09: 0000000000000000 [35985.503841] R10: 0000000000000004 R11: 00000001000313d5 R12: ffff8aa4c10f1820 [35985.503843] R13: ffff8aa4c0e243c0 R14: ffff8aa4c1167250 R15: ffff8aa4c1167120 [35985.503846] FS: 0000000000000000(0000) GS:ffff8aa4eae00000(0000) knlGS:0000000000000000 [35985.503849] CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033 [35985.503852] CR2: 00007fab0aaf1000 CR3: 0000000105328000 CR4: 00000000003506f0 [35985.503855] Call Trace: [35985.503859] <TASK> [35985.503863] ? __warn+0xd4/0x260 [35985.503868] ? __i2c_transfer+0xbe/0x810 [35985.503874] ? report_bug+0xf3/0x210 [35985.503882] ? handle_bug+0x63/0xb0 [35985.503887] ? exc_invalid_op+0x16/0x50 [35985.503892] ? asm_exc_invalid_op+0x16/0x20 [35985.503904] ? __i2c_transfer+0xbe/0x810 [35985.503913] tpm_cr50_i2c_transfer_message+0x24/0xf0 [35985.503920] tpm_cr50_i2c_read+0x8e/0x120 [35985.503928] tpm_cr50_request_locality+0x75/0x170 [35985.503935] tpm_chip_start+0x116/0x160 [35985.503942] tpm_try_get_ops+0x57/0x90 [35985.503948] tpm_find_get_ops+0x26/0xd0 [35985.503955] tpm_get_random+0x2d/0x80 Don't move forward with tpm_chip_start() inside tpm_try_get_ops(), unless TPM_CHIP_FLAG_SUSPENDED is not set. tpm_find_get_ops() will return NULL in such a failure case. Fixes: 9265fed6db60 ("tpm: Lock TPM chip in tpm_pm_suspend() first") Signed-off-by: Thadeu Lima de Souza Cascardo <cascardo@igalia.com> Cc: stable@vger.kernel.org Cc: Jerry Snitselaar <jsnitsel@redhat.com> Cc: Mike Seo <mikeseohyungjin@gmail.com> Cc: Jarkko Sakkinen <jarkko@kernel.org> Reviewed-by: Jerry Snitselaar <jsnitsel@redhat.com> Reviewed-by: Jarkko Sakkinen <jarkko@kernel.org> Signed-off-by: Jarkko Sakkinen <jarkko@kernel.org> 2025-03-27ktest: Fix Test Failures Due to Missing LOG_FILE DirectoriesAyush Jain1-0/+8 Handle missing parent directories for LOG_FILE path to prevent test failures. If the parent directories don't exist, create them to ensure the tests proceed successfully. Cc: <warthog9@eaglescrag.net> Link: https://lore.kernel.org/20250307043854.2518539-1-Ayush.jain3@amd.com Signed-off-by: Ayush Jain <Ayush.jain3@amd.com> Signed-off-by: Steven Rostedt <rostedt@goodmis.org> 2025-03-27tracing: probe-events: Add comments about entry data storing codeMasami Hiramatsu (Google)1-0/+28 Add comments about entry data storing code to __store_entry_arg() and traceprobe_get_entry_data_size(). These are a bit complicated because of building the entry data storing code and scanning it. This just add comments, no behavior change. Link: https://lore.kernel.org/all/174061715004.501424.333819546601401102.stgit@devnote2/ Reported-by: Steven Rostedt <rostedt@goodmis.org> Closes: https://lore.kernel.org/all/20250226102223.586d7119@gandalf.local.home/ Reviewed-by: Steven Rostedt (Google) <rostedt@goodmis.org> Signed-off-by: Masami Hiramatsu (Google) <mhiramat@kernel.org>