2020-03-20 15:17:00

by Andreas Gerstmayr

[permalink] [raw]
Subject: [PATCH] perf script: add flamegraph.py script

This script works in tandem with d3-flame-graph to generate flame graphs
from perf. It supports two output formats: JSON and HTML (the default).
The HTML format will look for a standalone d3-flame-graph template file in
/usr/share/d3-flame-graph/d3-flamegraph-base.html and fill in the collected
stacks.

Usage:

perf record -a -g -F 99 sleep 60
perf script report flamegraph

Combined:

perf script flamegraph -a -F 99 sleep 60

Signed-off-by: Andreas Gerstmayr <[email protected]>
---
Package for d3-flame-graph is currently under review, in the meantime you can
download the template from:
https://cdn.jsdelivr.net/npm/[email protected]/dist/templates/d3-flamegraph-base.html
and move it to /usr/share/d3-flame-graph/d3-flamegraph-base.html

.../perf/scripts/python/bin/flamegraph-record | 2 +
.../perf/scripts/python/bin/flamegraph-report | 3 +
tools/perf/scripts/python/flamegraph.py | 126 ++++++++++++++++++
3 files changed, 131 insertions(+)
create mode 100755 tools/perf/scripts/python/bin/flamegraph-record
create mode 100755 tools/perf/scripts/python/bin/flamegraph-report
create mode 100755 tools/perf/scripts/python/flamegraph.py

diff --git a/tools/perf/scripts/python/bin/flamegraph-record b/tools/perf/scripts/python/bin/flamegraph-record
new file mode 100755
index 000000000000..725d66e71570
--- /dev/null
+++ b/tools/perf/scripts/python/bin/flamegraph-record
@@ -0,0 +1,2 @@
+#!/usr/bin/sh
+perf record -g "$@"
diff --git a/tools/perf/scripts/python/bin/flamegraph-report b/tools/perf/scripts/python/bin/flamegraph-report
new file mode 100755
index 000000000000..b1a79afd903b
--- /dev/null
+++ b/tools/perf/scripts/python/bin/flamegraph-report
@@ -0,0 +1,3 @@
+#!/usr/bin/sh
+# description: create flame graphs
+perf script -s "$PERF_EXEC_PATH"/scripts/python/flamegraph.py -- "$@"
diff --git a/tools/perf/scripts/python/flamegraph.py b/tools/perf/scripts/python/flamegraph.py
new file mode 100755
index 000000000000..5835d190ca42
--- /dev/null
+++ b/tools/perf/scripts/python/flamegraph.py
@@ -0,0 +1,126 @@
+# 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 <[email protected]>
+# Flame Graphs invented by Brendan Gregg <[email protected]>
+# Works in tandem with d3-flame-graph by Martin Spier <[email protected]>
+
+import sys
+import os
+import argparse
+import json
+
+
+class Node:
+ def __init__(self, name, libtype=""):
+ self.name = name
+ self.libtype = libtype
+ self.value = 0
+ self.children = []
+
+ def toJSON(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("root")
+
+ if self.args.format == "html" and \
+ not os.path.isfile(self.args.template):
+ print(f"Flame Graph template {self.args.template} does not " +
+ f"exist. Please install the js-d3-flame-graph (RPM) or " +
+ f"libjs-d3-flame-graph (deb) package, specify an " +
+ f"existing flame graph template (--template PATH) or " +
+ f"another output format (--format FORMAT).",
+ 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]"
+
+ for child in node.children:
+ if child.name == name and child.libtype == libtype:
+ return child
+
+ child = Node(name, libtype)
+ node.children.append(child)
+ return child
+
+ def process_event(self, event):
+ node = self.find_or_create_node(self.stack, event["comm"], None)
+ 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"))
+ else:
+ node = self.find_or_create_node(
+ node, entry.get("symbol"), event.get("dso"))
+ node.value += 1
+
+ def trace_end(self):
+ json_str = json.dumps(self.stack, default=lambda x: x.toJSON(),
+ indent=self.args.indent)
+
+ if self.args.format == "html":
+ try:
+ with open(self.args.template) as f:
+ output_str = f.read().replace("/** @flamegraph_json **/",
+ json_str)
+ except IOError as e:
+ print(f"Error reading template file: {e}", file=sys.stderr)
+ sys.exit(1)
+ output_fn = self.args.output or "flamegraph.html"
+ else:
+ output_str = json_str
+ output_fn = self.args.output or "stacks.json"
+
+ if output_fn == "-":
+ sys.stdout.write(output_str)
+ else:
+ print(f"dumping data to {output_fn}")
+ try:
+ with open(output_fn, "w") as out:
+ out.write(output_str)
+ except IOError as e:
+ print(f"Error writing output file: {e}", 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("--indent",
+ type=int, help="JSON indentation")
+ parser.add_argument("--template",
+ default="/usr/share/d3-flame-graph/d3-flamegraph-base.html",
+ help="path to flamegraph HTML template")
+ parser.add_argument("-i", "--input",
+ help=argparse.SUPPRESS)
+
+ args = parser.parse_args()
+ cli = FlameGraphCLI(args)
+
+ process_event = cli.process_event
+ trace_end = cli.trace_end
--
2.25.1


2020-03-24 16:18:59

by Kim Phillips

[permalink] [raw]
Subject: Re: [PATCH] perf script: add flamegraph.py script

On 3/20/20 10:13 AM, Andreas Gerstmayr wrote:
> This script works in tandem with d3-flame-graph to generate flame graphs
> from perf. It supports two output formats: JSON and HTML (the default).
> The HTML format will look for a standalone d3-flame-graph template file in
> /usr/share/d3-flame-graph/d3-flamegraph-base.html and fill in the collected
> stacks.
>
> Usage:
>
> perf record -a -g -F 99 sleep 60
> perf script report flamegraph

On Ubuntu 19.10, where python 2.7 is still the default, I get:

$ perf script report flamegraph
File "/usr/libexec/perf-core/scripts/python/flamegraph.py", line 46
print(f"Flame Graph template {self.args.template} does not " +
^
SyntaxError: invalid syntax
Error running python script /usr/libexec/perf-core/scripts/python/flamegraph.py

Installing libpython3-dev doesn't help.

$ perf script -s lang

Scripting language extensions (used in perf script -s [spec:]script.[spec]):

Perl [Perl]
pl [Perl]
Python [Python]
py [Python]

Should there be a python3 in that list?

Thanks,

Kim

2020-03-24 19:07:58

by Andreas Gerstmayr

[permalink] [raw]
Subject: Re: [PATCH] perf script: add flamegraph.py script

On 24.03.20 17:16, Kim Phillips wrote:
> On Ubuntu 19.10, where python 2.7 is still the default, I get:
>
> $ perf script report flamegraph
> File "/usr/libexec/perf-core/scripts/python/flamegraph.py", line 46
> print(f"Flame Graph template {self.args.template} does not " +
> ^
> SyntaxError: invalid syntax
> Error running python script /usr/libexec/perf-core/scripts/python/flamegraph.py
>
> Installing libpython3-dev doesn't help.

Hmm, I was hoping that I can drop support for Python 2 in 2020 ;) (it's
officially EOL since Jan 1, 2020)

The Ubuntu 18.04 release notes mention that "Python 2 is no longer
installed by default. Python 3 has been updated to 3.6. This is the last
LTS release to include Python 2 in main."
(https://wiki.ubuntu.com/BionicBeaver/ReleaseNotes) - so imho it should
be fine to drop Python 2 support.

I tested it with a Ubuntu VM, and by default the Python bindings aren't
enabled in perf (see
https://bugs.launchpad.net/ubuntu/+source/linux/+bug/1707875).

But you can compile perf and select Python 3:

$ make -j2 PYTHON=python3

in the perf source directory (libpython3-dev must be installed).


Does this work for you?


Cheers,
Andreas

2020-03-24 21:28:34

by Arnaldo Carvalho de Melo

[permalink] [raw]
Subject: Re: [PATCH] perf script: add flamegraph.py script



On March 24, 2020 4:05:15 PM GMT-03:00, Andreas Gerstmayr <[email protected]> wrote:
>On 24.03.20 17:16, Kim Phillips wrote:
>> On Ubuntu 19.10, where python 2.7 is still the default, I get:
>>
>> $ perf script report flamegraph
>> File "/usr/libexec/perf-core/scripts/python/flamegraph.py", line
>46
>> print(f"Flame Graph template {self.args.template} does not " +
>> ^
>> SyntaxError: invalid syntax
>> Error running python script
>/usr/libexec/perf-core/scripts/python/flamegraph.py
>>
>> Installing libpython3-dev doesn't help.
>
>Hmm, I was hoping that I can drop support for Python 2 in 2020 ;) (it's
>
>officially EOL since Jan 1, 2020)
>
>The Ubuntu 18.04 release notes mention that "Python 2 is no longer
>installed by default. Python 3 has been updated to 3.6. This is the
>last
>LTS release to include Python 2 in main."
>(https://wiki.ubuntu.com/BionicBeaver/ReleaseNotes) - so imho it should
>
>be fine to drop Python 2 support.
>
>I tested it with a Ubuntu VM, and by default the Python bindings aren't
>
>enabled in perf (see
>https://bugs.launchpad.net/ubuntu/+source/linux/+bug/1707875).
>
>But you can compile perf and select Python 3:
>
>$ make -j2 PYTHON=python3
>

I plan to make this the default soon.

- Arnaldo
>in the perf source directory (libpython3-dev must be installed).
>
>
>Does this work for you?
>
>
>Cheers,
>Andreas

--
Sent from my Android device with K-9 Mail. Please excuse my brevity.

2020-03-26 19:05:38

by Kim Phillips

[permalink] [raw]
Subject: Re: [PATCH] perf script: add flamegraph.py script

On 3/24/20 2:05 PM, Andreas Gerstmayr wrote:
> On 24.03.20 17:16, Kim Phillips wrote:
>> On Ubuntu 19.10, where python 2.7 is still the default, I get:
>>
>> $ perf script report flamegraph
>>    File "/usr/libexec/perf-core/scripts/python/flamegraph.py", line 46
>>      print(f"Flame Graph template {self.args.template} does not " +
>>                                                                 ^
>> SyntaxError: invalid syntax
>> Error running python script /usr/libexec/perf-core/scripts/python/flamegraph.py
>>
>> Installing libpython3-dev doesn't help.
>
> Hmm, I was hoping that I can drop support for Python 2 in 2020 ;) (it's officially EOL since Jan 1, 2020)
>
> The Ubuntu 18.04 release notes mention that "Python 2 is no longer installed by default. Python 3 has been updated to 3.6. This is the last LTS release to include Python 2 in main." (https://wiki.ubuntu.com/BionicBeaver/ReleaseNotes) - so imho it should be fine to drop Python 2 support.
>
> I tested it with a Ubuntu VM, and by default the Python bindings aren't enabled in perf (see https://bugs.launchpad.net/ubuntu/+source/linux/+bug/1707875).
>
> But you can compile perf and select Python 3:
>
> $ make -j2 PYTHON=python3
>
> in the perf source directory (libpython3-dev must be installed).
>
>
> Does this work for you?

Not on Ubuntu 18.04.4 LTS, but it does on 19.10.

On 19.10 however, when specifying dwarf on the record, e.g.:

sudo perf record -a -g -C2,4 --call-graph=dwarf -- sleep 10

I now get a SIGSEGV when executing perf script report flamegraph.

Here's a trace:

#0 0x000055555590a9b2 in regs_map (regs=0x7fffffffbfc8, mask=16715775,
bf=0x7fffffffba60 "", size=512) at util/scripting-engines/trace-event-python.c:696
#1 0x000055555590ab03 in set_regs_in_dict (dict=0x7ffff61dd500, sample=0x7fffffffbf20,
evsel=0x555555d7a700) at util/scripting-engines/trace-event-python.c:718
#2 0x000055555590af1f in get_perf_sample_dict (sample=0x7fffffffbf20,
evsel=0x555555d7a700, al=0x7fffffffbdd0, callchain=0x7ffff625b780)
at util/scripting-engines/trace-event-python.c:787
#3 0x000055555590ce3e in python_process_general_event (sample=0x7fffffffbf20,
evsel=0x555555d7a700, al=0x7fffffffbdd0)
at util/scripting-engines/trace-event-python.c:1301
#4 0x000055555590cf94 in python_process_event (event=0x7ffff60b0a48,
sample=0x7fffffffbf20, evsel=0x555555d7a700, al=0x7fffffffbdd0)
at util/scripting-engines/trace-event-python.c:1328
#5 0x000055555577375c in process_sample_event (tool=0x7fffffffcf30,
event=0x7ffff60b0a48, sample=0x7fffffffbf20, evsel=0x555555d7a700,
machine=0x555555d73168) at builtin-script.c:2072
#6 0x000055555585f3d9 in perf_evlist__deliver_sample (evlist=0x555555d79c60,
tool=0x7fffffffcf30, event=0x7ffff60b0a48, sample=0x7fffffffbf20,
evsel=0x555555d7a700, machine=0x555555d73168) at util/session.c:1389
#7 0x000055555585f588 in machines__deliver_event (machines=0x555555d73168,
evlist=0x555555d79c60, event=0x7ffff60b0a48, sample=0x7fffffffbf20,
tool=0x7fffffffcf30, file_offset=3037768) at util/session.c:1426
#8 0x000055555585fa32 in perf_session__deliver_event (session=0x555555d72fe0,
event=0x7ffff60b0a48, tool=0x7fffffffcf30, file_offset=3037768)
at util/session.c:1499
#9 0x000055555585bf5e in ordered_events__deliver_event (oe=0x555555d79b20,
event=0x555556446588) at util/session.c:183
#10 0x0000555555864010 in do_flush (oe=0x555555d79b20, show_progress=false)
at util/ordered-events.c:244
#11 0x000055555586435f in __ordered_events__flush (oe=0x555555d79b20,
how=OE_FLUSH__ROUND, timestamp=0) at util/ordered-events.c:323
#12 0x0000555555864447 in ordered_events__flush (oe=0x555555d79b20, how=OE_FLUSH__ROUND)
at util/ordered-events.c:341
#13 0x000055555585e2b1 in process_finished_round (tool=0x7fffffffcf30,
event=0x7ffff60ec040, oe=0x555555d79b20) at util/session.c:997
#14 0x000055555585fcea in perf_session__process_user_event (session=0x555555d72fe0,
event=0x7ffff60ec040, file_offset=3280960) at util/session.c:1546
#15 0x000055555586055d in perf_session__process_event (session=0x555555d72fe0,
event=0x7ffff60ec040, file_offset=3280960) at util/session.c:1706
#16 0x0000555555861973 in process_simple (session=0x555555d72fe0, event=0x7ffff60ec040,
file_offset=3280960) at util/session.c:2202
#17 0x0000555555861792 in reader__process_events (rd=0x7fffffffcd70,
session=0x555555d72fe0, prog=0x7fffffffcd90) at util/session.c:2168
#18 0x0000555555861a68 in __perf_session__process_events (session=0x555555d72fe0)
at util/session.c:2225
#19 0x0000555555861b9d in perf_session__process_events (session=0x555555d72fe0)
at util/session.c:2258
#20 0x0000555555774d02 in __cmd_script (script=0x7fffffffcf30) at builtin-script.c:2557
#21 0x0000555555779988 in cmd_script (argc=0, argv=0x7fffffffebd0)
at builtin-script.c:3926
#22 0x00005555557f2a93 in run_builtin (p=0x555555bb44d8 <commands+408>, argc=4,
argv=0x7fffffffebd0) at perf.c:312
#23 0x00005555557f2d18 in handle_internal_command (argc=4, argv=0x7fffffffebd0)
at perf.c:364
#24 0x00005555557f2e6b in run_argv (argcp=0x7fffffffea2c, argv=0x7fffffffea20)
at perf.c:408
#25 0x00005555557f326e in main (argc=4, argv=0x7fffffffebd0) at perf.c:538

This is on today's acme's perf/urgent branch.

Thanks,

Kim

2020-04-02 12:46:07

by Andreas Gerstmayr

[permalink] [raw]
Subject: [PATCH] perf script: fix invalid read

closedir(lang_dir) frees the memory of script_dirent->d_name, which
gets accessed in the next line in a call to scnprintf().

Valgrind report:

Invalid read of size 1
==413557== at 0x483CBE6: strlen (vg_replace_strmem.c:461)
==413557== by 0x4DD45FD: __vfprintf_internal (vfprintf-internal.c:1688)
==413557== by 0x4DE6679: __vsnprintf_internal (vsnprintf.c:114)
==413557== by 0x53A037: vsnprintf (stdio2.h:80)
==413557== by 0x53A037: scnprintf (vsprintf.c:21)
==413557== by 0x435202: get_script_path (builtin-script.c:3223)
==413557== Address 0x52e7313 is 1,139 bytes inside a block of size 32,816 free'd
==413557== at 0x483AA0C: free (vg_replace_malloc.c:540)
==413557== by 0x4E303C0: closedir (closedir.c:50)
==413557== by 0x4351DC: get_script_path (builtin-script.c:3222)

Signed-off-by: Andreas Gerstmayr <[email protected]>
---
tools/perf/builtin-script.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/tools/perf/builtin-script.c b/tools/perf/builtin-script.c
index 656b347f6dd8..170af13b4d53 100644
--- a/tools/perf/builtin-script.c
+++ b/tools/perf/builtin-script.c
@@ -3218,10 +3218,10 @@ static char *get_script_path(const char *script_root, const char *suffix)
__script_root = get_script_root(script_dirent, suffix);
if (__script_root && !strcmp(script_root, __script_root)) {
free(__script_root);
- closedir(lang_dir);
closedir(scripts_dir);
scnprintf(script_path, MAXPATHLEN, "%s/%s",
lang_path, script_dirent->d_name);
+ closedir(lang_dir);
return strdup(script_path);
}
free(__script_root);
--
2.25.1

2020-04-02 13:07:01

by Andreas Gerstmayr

[permalink] [raw]
Subject: [PATCH] perf script report: fix segfault when using DWARF mode

When running perf script report with a Python script and a callgraph in
DWARF mode, intr_regs->regs can be 0 and therefore crashing the regs_map
function.

Added a check for this condition (same check as in builtin-script.c:595).

Signed-off-by: Andreas Gerstmayr <[email protected]>
---
tools/perf/util/scripting-engines/trace-event-python.c | 3 +++
1 file changed, 3 insertions(+)

diff --git a/tools/perf/util/scripting-engines/trace-event-python.c b/tools/perf/util/scripting-engines/trace-event-python.c
index 8c1b27cd8b99..2c372cf5495e 100644
--- a/tools/perf/util/scripting-engines/trace-event-python.c
+++ b/tools/perf/util/scripting-engines/trace-event-python.c
@@ -694,6 +694,9 @@ static int regs_map(struct regs_dump *regs, uint64_t mask, char *bf, int size)

bf[0] = 0;

+ if (!regs || !regs->regs)
+ return 0;
+
for_each_set_bit(r, (unsigned long *) &mask, sizeof(mask) * 8) {
u64 val = regs->regs[i++];

--
2.25.1

2020-04-02 13:46:46

by Andreas Gerstmayr

[permalink] [raw]
Subject: Re: [PATCH] perf script: add flamegraph.py script

On 26.03.20 20:04, Kim Phillips wrote:
> I now get a SIGSEGV when executing perf script report flamegraph.
>
> Here's a trace:
>
> #0 0x000055555590a9b2 in regs_map (regs=0x7fffffffbfc8, mask=16715775,
> bf=0x7fffffffba60 "", size=512) at util/scripting-engines/trace-event-python.c:696
> #1 0x000055555590ab03 in set_regs_in_dict (dict=0x7ffff61dd500, sample=0x7fffffffbf20,
> evsel=0x555555d7a700) at util/scripting-engines/trace-event-python.c:718

This error seems unrelated to flamegraph.py (occurs also with the
stackcollapse.py script).

Looks like the intr_regs->regs can be 0 when running in DWARF mode (this
error doesn't occur in the default mode). I've added a check and sent a
patch. While at it, valgrind reported an invalid read in a different
code path, which is fixed by the other patch I just sent a few minutes ago.

Can you please test again and report if there are any other issues?


Cheers,
Andreas

2020-04-02 15:17:13

by Arnaldo Carvalho de Melo

[permalink] [raw]
Subject: Re: [PATCH] perf script: fix invalid read

Em Thu, Apr 02, 2020 at 02:43:38PM +0200, Andreas Gerstmayr escreveu:
> closedir(lang_dir) frees the memory of script_dirent->d_name, which
> gets accessed in the next line in a call to scnprintf().
>
> Valgrind report:
>
> Invalid read of size 1
> ==413557== at 0x483CBE6: strlen (vg_replace_strmem.c:461)
> ==413557== by 0x4DD45FD: __vfprintf_internal (vfprintf-internal.c:1688)
> ==413557== by 0x4DE6679: __vsnprintf_internal (vsnprintf.c:114)
> ==413557== by 0x53A037: vsnprintf (stdio2.h:80)
> ==413557== by 0x53A037: scnprintf (vsprintf.c:21)
> ==413557== by 0x435202: get_script_path (builtin-script.c:3223)
> ==413557== Address 0x52e7313 is 1,139 bytes inside a block of size 32,816 free'd
> ==413557== at 0x483AA0C: free (vg_replace_malloc.c:540)
> ==413557== by 0x4E303C0: closedir (closedir.c:50)
> ==413557== by 0x4351DC: get_script_path (builtin-script.c:3222)

Thanks, applied to perf/urgent.

- Arnaldo

> Signed-off-by: Andreas Gerstmayr <[email protected]>
> ---
> tools/perf/builtin-script.c | 2 +-
> 1 file changed, 1 insertion(+), 1 deletion(-)
>
> diff --git a/tools/perf/builtin-script.c b/tools/perf/builtin-script.c
> index 656b347f6dd8..170af13b4d53 100644
> --- a/tools/perf/builtin-script.c
> +++ b/tools/perf/builtin-script.c
> @@ -3218,10 +3218,10 @@ static char *get_script_path(const char *script_root, const char *suffix)
> __script_root = get_script_root(script_dirent, suffix);
> if (__script_root && !strcmp(script_root, __script_root)) {
> free(__script_root);
> - closedir(lang_dir);
> closedir(scripts_dir);
> scnprintf(script_path, MAXPATHLEN, "%s/%s",
> lang_path, script_dirent->d_name);
> + closedir(lang_dir);
> return strdup(script_path);
> }
> free(__script_root);
> --
> 2.25.1
>

--

- Arnaldo

2020-04-02 15:17:39

by Arnaldo Carvalho de Melo

[permalink] [raw]
Subject: Re: [PATCH] perf script report: fix segfault when using DWARF mode

Em Thu, Apr 02, 2020 at 02:54:16PM +0200, Andreas Gerstmayr escreveu:
> When running perf script report with a Python script and a callgraph in
> DWARF mode, intr_regs->regs can be 0 and therefore crashing the regs_map
> function.
>
> Added a check for this condition (same check as in builtin-script.c:595).

Thanks, applied to perf/urgent.

- Arnaldo

> Signed-off-by: Andreas Gerstmayr <[email protected]>
> ---
> tools/perf/util/scripting-engines/trace-event-python.c | 3 +++
> 1 file changed, 3 insertions(+)
>
> diff --git a/tools/perf/util/scripting-engines/trace-event-python.c b/tools/perf/util/scripting-engines/trace-event-python.c
> index 8c1b27cd8b99..2c372cf5495e 100644
> --- a/tools/perf/util/scripting-engines/trace-event-python.c
> +++ b/tools/perf/util/scripting-engines/trace-event-python.c
> @@ -694,6 +694,9 @@ static int regs_map(struct regs_dump *regs, uint64_t mask, char *bf, int size)
>
> bf[0] = 0;
>
> + if (!regs || !regs->regs)
> + return 0;
> +
> for_each_set_bit(r, (unsigned long *) &mask, sizeof(mask) * 8) {
> u64 val = regs->regs[i++];
>
> --
> 2.25.1
>

--

- Arnaldo

2020-04-02 19:47:11

by Kim Phillips

[permalink] [raw]
Subject: Re: [PATCH] perf script report: fix segfault when using DWARF mode

On 4/2/20 7:54 AM, Andreas Gerstmayr wrote:
> When running perf script report with a Python script and a callgraph in
> DWARF mode, intr_regs->regs can be 0 and therefore crashing the regs_map
> function.
>
> Added a check for this condition (same check as in builtin-script.c:595).
>
> Signed-off-by: Andreas Gerstmayr <[email protected]>
> ---

Tested-by: Kim Phillips <[email protected]>

Thanks,

Kim

2020-04-03 13:01:58

by Arnaldo Carvalho de Melo

[permalink] [raw]
Subject: Re: [PATCH] perf script report: fix segfault when using DWARF mode

Em Thu, Apr 02, 2020 at 02:07:51PM -0500, Kim Phillips escreveu:
> On 4/2/20 7:54 AM, Andreas Gerstmayr wrote:
> > When running perf script report with a Python script and a callgraph in
> > DWARF mode, intr_regs->regs can be 0 and therefore crashing the regs_map
> > function.
> >
> > Added a check for this condition (same check as in builtin-script.c:595).
> >
> > Signed-off-by: Andreas Gerstmayr <[email protected]>
> > ---
>
> Tested-by: Kim Phillips <[email protected]>

Thanks, added this to that patch.

- Arnaldo

2020-04-03 13:18:29

by Andreas Gerstmayr

[permalink] [raw]
Subject: Re: [PATCH] perf script report: fix segfault when using DWARF mode

On 03.04.20 14:40, Arnaldo Carvalho de Melo wrote:
> Em Thu, Apr 02, 2020 at 02:07:51PM -0500, Kim Phillips escreveu:
>> On 4/2/20 7:54 AM, Andreas Gerstmayr wrote:
>>> When running perf script report with a Python script and a callgraph in
>>> DWARF mode, intr_regs->regs can be 0 and therefore crashing the regs_map
>>> function.
>>>
>>> Added a check for this condition (same check as in builtin-script.c:595).
>>>
>>> Signed-off-by: Andreas Gerstmayr <[email protected]>
>>> ---
>>
>> Tested-by: Kim Phillips <[email protected]>
>
> Thanks, added this to that patch.
>

Great, thanks!


Cheers,
Andreas

Subject: [tip: perf/urgent] perf script: Fix invalid read of directory entry after closedir()

The following commit has been merged into the perf/urgent branch of tip:

Commit-ID: 27486a85cb65bd258a9a213b3e95bdf8c24fd781
Gitweb: https://git.kernel.org/tip/27486a85cb65bd258a9a213b3e95bdf8c24fd781
Author: Andreas Gerstmayr <[email protected]>
AuthorDate: Thu, 02 Apr 2020 14:43:38 +02:00
Committer: Arnaldo Carvalho de Melo <[email protected]>
CommitterDate: Fri, 03 Apr 2020 10:03:18 -03:00

perf script: Fix invalid read of directory entry after closedir()

closedir(lang_dir) frees the memory of script_dirent->d_name, which
gets accessed in the next line in a call to scnprintf().

Valgrind report:

Invalid read of size 1
==413557== at 0x483CBE6: strlen (vg_replace_strmem.c:461)
==413557== by 0x4DD45FD: __vfprintf_internal (vfprintf-internal.c:1688)
==413557== by 0x4DE6679: __vsnprintf_internal (vsnprintf.c:114)
==413557== by 0x53A037: vsnprintf (stdio2.h:80)
==413557== by 0x53A037: scnprintf (vsprintf.c:21)
==413557== by 0x435202: get_script_path (builtin-script.c:3223)
==413557== Address 0x52e7313 is 1,139 bytes inside a block of size 32,816 free'd
==413557== at 0x483AA0C: free (vg_replace_malloc.c:540)
==413557== by 0x4E303C0: closedir (closedir.c:50)
==413557== by 0x4351DC: get_script_path (builtin-script.c:3222)

Signed-off-by: Andreas Gerstmayr <[email protected]>
Cc: Alexander Shishkin <[email protected]>
Cc: Jiri Olsa <[email protected]>
Cc: Mark Rutland <[email protected]>
Cc: Namhyung Kim <[email protected]>
Cc: Peter Zijlstra <[email protected]>
Link: http://lore.kernel.org/lkml/[email protected]
Signed-off-by: Arnaldo Carvalho de Melo <[email protected]>
---
tools/perf/builtin-script.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/tools/perf/builtin-script.c b/tools/perf/builtin-script.c
index 186ebf8..1f57a7e 100644
--- a/tools/perf/builtin-script.c
+++ b/tools/perf/builtin-script.c
@@ -3265,10 +3265,10 @@ static char *get_script_path(const char *script_root, const char *suffix)
__script_root = get_script_root(script_dirent, suffix);
if (__script_root && !strcmp(script_root, __script_root)) {
free(__script_root);
- closedir(lang_dir);
closedir(scripts_dir);
scnprintf(script_path, MAXPATHLEN, "%s/%s",
lang_path, script_dirent->d_name);
+ closedir(lang_dir);
return strdup(script_path);
}
free(__script_root);

Subject: [tip: perf/urgent] perf script report: Fix SEGFAULT when using DWARF mode

The following commit has been merged into the perf/urgent branch of tip:

Commit-ID: 1a4025f06059eeaecb2ef24363350ea3431568df
Gitweb: https://git.kernel.org/tip/1a4025f06059eeaecb2ef24363350ea3431568df
Author: Andreas Gerstmayr <[email protected]>
AuthorDate: Thu, 02 Apr 2020 14:54:16 +02:00
Committer: Arnaldo Carvalho de Melo <[email protected]>
CommitterDate: Fri, 03 Apr 2020 09:39:53 -03:00

perf script report: Fix SEGFAULT when using DWARF mode

When running perf script report with a Python script and a callgraph in
DWARF mode, intr_regs->regs can be 0 and therefore crashing the regs_map
function.

Added a check for this condition (same check as in builtin-script.c:595).

Signed-off-by: Andreas Gerstmayr <[email protected]>
Tested-by: Kim Phillips <[email protected]>
Cc: Adrian Hunter <[email protected]>
Cc: Alexander Shishkin <[email protected]>
Cc: Jiri Olsa <[email protected]>
Cc: Kan Liang <[email protected]>
Cc: Mark Rutland <[email protected]>
Cc: Namhyung Kim <[email protected]>
Cc: Peter Zijlstra <[email protected]>
Cc: Steven Rostedt (VMware) <[email protected]>
Link: http://lore.kernel.org/lkml/[email protected]
Signed-off-by: Arnaldo Carvalho de Melo <[email protected]>
---
tools/perf/util/scripting-engines/trace-event-python.c | 3 +++
1 file changed, 3 insertions(+)

diff --git a/tools/perf/util/scripting-engines/trace-event-python.c b/tools/perf/util/scripting-engines/trace-event-python.c
index 8c1b27c..2c372cf 100644
--- a/tools/perf/util/scripting-engines/trace-event-python.c
+++ b/tools/perf/util/scripting-engines/trace-event-python.c
@@ -694,6 +694,9 @@ static int regs_map(struct regs_dump *regs, uint64_t mask, char *bf, int size)

bf[0] = 0;

+ if (!regs || !regs->regs)
+ return 0;
+
for_each_set_bit(r, (unsigned long *) &mask, sizeof(mask) * 8) {
u64 val = regs->regs[i++];

2020-04-06 09:31:00

by Andreas Gerstmayr

[permalink] [raw]
Subject: Re: [PATCH] perf script report: fix segfault when using DWARF mode

On 03.04.20 15:16, Andreas Gerstmayr wrote:
> On 03.04.20 14:40, Arnaldo Carvalho de Melo wrote:
>> Em Thu, Apr 02, 2020 at 02:07:51PM -0500, Kim Phillips escreveu:
>>> On 4/2/20 7:54 AM, Andreas Gerstmayr wrote:
>>>> When running perf script report with a Python script and a callgraph in
>>>> DWARF mode, intr_regs->regs can be 0 and therefore crashing the
>>>> regs_map
>>>> function.
>>>>
>>>> Added a check for this condition (same check as in
>>>> builtin-script.c:595).
>>>>
>>>> Signed-off-by: Andreas Gerstmayr <[email protected]>
>>>> ---
>>>
>>> Tested-by: Kim Phillips <[email protected]>
>>
>> Thanks, added this to that patch.
>>
>
> Great, thanks!

Ah, I thought you were referring to my initial flamegraph.py perf script.
Is there anything I can do to get it merged?


Cheers,
Andreas

2020-04-06 12:59:56

by Arnaldo Carvalho de Melo

[permalink] [raw]
Subject: Re: [PATCH] perf script report: fix segfault when using DWARF mode

Em Mon, Apr 06, 2020 at 11:30:23AM +0200, Andreas Gerstmayr escreveu:
> On 03.04.20 15:16, Andreas Gerstmayr wrote:
> > On 03.04.20 14:40, Arnaldo Carvalho de Melo wrote:
> > > Em Thu, Apr 02, 2020 at 02:07:51PM -0500, Kim Phillips escreveu:
> > > > On 4/2/20 7:54 AM, Andreas Gerstmayr wrote:
> > > > > When running perf script report with a Python script and a callgraph in
> > > > > DWARF mode, intr_regs->regs can be 0 and therefore crashing
> > > > > the regs_map
> > > > > function.

> > > > > Added a check for this condition (same check as in
> > > > > builtin-script.c:595).

> > > > > Signed-off-by: Andreas Gerstmayr <[email protected]>

> > > > Tested-by: Kim Phillips <[email protected]>

> > > Thanks, added this to that patch.

> > Great, thanks!

> Ah, I thought you were referring to my initial flamegraph.py perf script.
> Is there anything I can do to get it merged?

I'll test it today, were there any Tested-by: or Reviewed-by: to that
flamegraph.py?

That is not yet a strict requirement for having patches accepted, but
help me a lot in dedicating time to test things that passed thru some
testing by other people,

Thanks,

- Arnaldo

2020-04-06 14:28:32

by Kim Phillips

[permalink] [raw]
Subject: Re: [PATCH] perf script: add flamegraph.py script

On 4/2/20 8:04 AM, Andreas Gerstmayr wrote:
> On 26.03.20 20:04, Kim Phillips wrote:
>> I now get a SIGSEGV when executing perf script report flamegraph.
>>
>> Here's a trace:
>>
>> #0  0x000055555590a9b2 in regs_map (regs=0x7fffffffbfc8, mask=16715775,
>>      bf=0x7fffffffba60 "", size=512) at util/scripting-engines/trace-event-python.c:696
>> #1  0x000055555590ab03 in set_regs_in_dict (dict=0x7ffff61dd500, sample=0x7fffffffbf20,
>>      evsel=0x555555d7a700) at util/scripting-engines/trace-event-python.c:718
>
> This error seems unrelated to flamegraph.py (occurs also with the stackcollapse.py script).
>
> Looks like the intr_regs->regs can be 0 when running in DWARF mode (this error doesn't occur in the default mode). I've added a check and sent a patch. While at it, valgrind reported an invalid read in a different code path, which is fixed by the other patch I just sent a few minutes ago.
>
> Can you please test again and report if there are any other issues?

This flamegraph patch works for me now:

Tested-by: Kim Phillips <[email protected]>

Thanks,

Kim

2020-04-06 14:29:03

by Kim Phillips

[permalink] [raw]
Subject: Re: [PATCH] perf script report: fix segfault when using DWARF mode

On 4/6/20 7:59 AM, Arnaldo Carvalho de Melo wrote:
> Em Mon, Apr 06, 2020 at 11:30:23AM +0200, Andreas Gerstmayr escreveu:
>> On 03.04.20 15:16, Andreas Gerstmayr wrote:
>>> On 03.04.20 14:40, Arnaldo Carvalho de Melo wrote:
>>>> Em Thu, Apr 02, 2020 at 02:07:51PM -0500, Kim Phillips escreveu:
>>>>> On 4/2/20 7:54 AM, Andreas Gerstmayr wrote:
>>>>>> When running perf script report with a Python script and a callgraph in
>>>>>> DWARF mode, intr_regs->regs can be 0 and therefore crashing
>>>>>> the regs_map
>>>>>> function.
>>>>>> Added a check for this condition (same check as in
>>>>>> builtin-script.c:595).
>>>>>> Signed-off-by: Andreas Gerstmayr <[email protected]>
>>>>> Tested-by: Kim Phillips <[email protected]>
>>>> Thanks, added this to that patch.
>>> Great, thanks!
>> Ah, I thought you were referring to my initial flamegraph.py perf script.
>> Is there anything I can do to get it merged?
>
> I'll test it today, were there any Tested-by: or Reviewed-by: to that
> flamegraph.py?

I just added mine.

Kim

2020-04-06 15:12:02

by Arnaldo Carvalho de Melo

[permalink] [raw]
Subject: Re: [PATCH] perf script: add flamegraph.py script

Em Fri, Mar 20, 2020 at 04:13:48PM +0100, Andreas Gerstmayr escreveu:
> This script works in tandem with d3-flame-graph to generate flame graphs
> from perf. It supports two output formats: JSON and HTML (the default).
> The HTML format will look for a standalone d3-flame-graph template file in
> /usr/share/d3-flame-graph/d3-flamegraph-base.html and fill in the collected
> stacks.
>
> Usage:
>
> perf record -a -g -F 99 sleep 60
> perf script report flamegraph
>
> Combined:
>
> perf script flamegraph -a -F 99 sleep 60
>
> Signed-off-by: Andreas Gerstmayr <[email protected]>

Trying it now with:

[root@five ~]# perf record -a -g -F 99 sleep 1
[ perf record: Woken up 1 times to write data ]
[ perf record: Captured and wrote 1.120 MB perf.data (59 samples) ]
[root@five ~]# perf script report flamegraph
File "/home/acme/libexec/perf-core/scripts/python/flamegraph.py", line 46
print(f"Flame Graph template {self.args.template} does not " +
^
SyntaxError: invalid syntax
Error running python script /home/acme/libexec/perf-core/scripts/python/flamegraph.py
[root@five ~]#



Ok, gets better when I build with 'make -C tools/perf PYTHON=python3'

[root@five ~]# perf script report flamegraph
Flame Graph template /usr/share/d3-flame-graph/d3-flamegraph-base.html does not exist. Please install the js-d3-flame-graph (RPM) or libjs-d3-flame-graph (deb) package, specify an existing flame graph template (--template PATH) or another output format (--format FORMAT).
[root@five ~]#

It works then, so I'll add a note to that effect. But please consider
fixing it so that it works with both python2 and python3, if possible,
or to fail gracefully and state that only python3 is supported and that
perf should be built with "PYTHON=python3" on the make command line.

Some RFEs for you to consider:

1. make:

perf flamegraph -a -F 99 sleep 1

be equivalent, should be easy.

2. make the command somehow create a new tab on an existing browser with
the generated file, and switch the focus to that tab.

3. get whats in:

[root@five ~]# perf report --header-only
# ========
# captured on : Mon Apr 6 12:02:52 2020
# header version : 1
# data offset : 336
# data size : 1173584
# feat offset : 1173920
# hostname : five
# os release : 5.5.10-200.fc31.x86_64
# perf version : 5.6.gb6b7dc6ec0c8
# arch : x86_64
# nrcpus online : 12
# nrcpus avail : 12
# cpudesc : AMD Ryzen 5 3600X 6-Core Processor
# cpuid : AuthenticAMD,23,113,0
# total memory : 32890748 kB
# cmdline : /home/acme/bin/perf record -a -g -F 99 sleep 1
# event : name = cycles, , id = { 2918625, 2918626, 2918627, 2918628, 2918629, 2918630, 2918631, 2918632, 2918633, 2918634, 2918635, 2918636 }, size = 120, { sample_period, sample_freq } = 99, sample_type = IP|TID|TIME|CALLCHAIN|CPU|PERIOD, read_format = ID, disabled = 1,>
# CPU_TOPOLOGY info available, use -I to display
# NUMA_TOPOLOGY info available, use -I to display
# pmu mappings: amd_df = 8, software = 1, ibs_op = 11, ibs_fetch = 10, uprobe = 7, cpu = 4, amd_iommu_0 = 12, breakpoint = 5, amd_l3 = 9, tracepoint = 2, kprobe = 6, msr = 13
# CACHE info available, use -I to display
# time of first sample : 87600.831767
# time of last sample : 87601.096829
# sample duration : 265.062 ms
# MEM_TOPOLOGY info available, use -I to display
# bpf_prog_info 40: bpf_prog_6deef7357e7b4530 addr 0xffffffffc030650c size 66
# bpf_prog_info 41: bpf_prog_6deef7357e7b4530 addr 0xffffffffc03080e8 size 66
# bpf_prog_info 42: bpf_prog_6deef7357e7b4530 addr 0xffffffffc030a218 size 66
# bpf_prog_info 43: bpf_prog_6deef7357e7b4530 addr 0xffffffffc036c698 size 66
# bpf_prog_info 44: bpf_prog_5a2b06eab81b8f51 addr 0xffffffffc036e2c0 size 1132
# bpf_prog_info 45: bpf_prog_6deef7357e7b4530 addr 0xffffffffc06e7118 size 66
# bpf_prog_info 46: bpf_prog_6deef7357e7b4530 addr 0xffffffffc06e9cd8 size 66
# bpf_prog_info 47: bpf_prog_f3b9e166f6c1aaaa addr 0xffffffffc089732c size 1786
# bpf_prog_info 48: bpf_prog_6deef7357e7b4530 addr 0xffffffffc0dcb64c size 66
# bpf_prog_info 49: bpf_prog_6deef7357e7b4530 addr 0xffffffffc0dcd0fc size 66
# bpf_prog_info 50: bpf_prog_6deef7357e7b4530 addr 0xffffffffc0f4d8dc size 66
# bpf_prog_info 51: bpf_prog_6deef7357e7b4530 addr 0xffffffffc0f4fc7c size 66
# bpf_prog_info 52: bpf_prog_84efc2eecc454ca6 addr 0xffffffffc0f6e584 size 373
# missing features: TRACING_DATA BRANCH_STACK GROUP_DESC AUXTRACE STAT CLOCKID DIR_FORMAT COMPRESSED
# ========
#
[root@five ~]#

And make it available in some UI element.

Thanks,

- Arnaldo

2020-04-09 17:19:37

by Andreas Gerstmayr

[permalink] [raw]
Subject: Re: [PATCH] perf script: add flamegraph.py script

Hi Arnaldo,

thanks for merging!

On 06.04.20 17:11, Arnaldo Carvalho de Melo wrote:
> It works then, so I'll add a note to that effect. But please consider
> fixing it so that it works with both python2 and python3, if possible,
> or to fail gracefully and state that only python3 is supported and that
> perf should be built with "PYTHON=python3" on the make command line.

Ok, I just sent a patch (based on your perf/core branch) to add Python 2
support. I hope soon Python 2 support won't be required anymore ;)

> Some RFEs for you to consider:
>
> 1. make:
>
> perf flamegraph -a -F 99 sleep 1
>
> be equivalent, should be easy.

`perf flamegraph -a -F 99 sleep 1` should be equivalent to `perf script
flamegraph -a -F 99 sleep 1`?
Or in other words, flamegraph should be a top-level command of perf, and
run the flamegraph.py script?

> 2. make the command somehow create a new tab on an existing browser with
> the generated file, and switch the focus to that tab.

As a personal preference I don't like programs interfering with my
browser session. For example I have a flame graph open in a tab, then I
generate another one (overwriting the current file), I would not want
another tab to open with the new flame graph, but instead I want to
manually refresh the currently open tab.

> 3. get whats in:
>
> [root@five ~]# perf report --header-only
> # ========
> # captured on : Mon Apr 6 12:02:52 2020
> # header version : 1
> # data offset : 336
> # data size : 1173584
> # feat offset : 1173920
> # hostname : five
> # os release : 5.5.10-200.fc31.x86_64
> # perf version : 5.6.gb6b7dc6ec0c8
> # arch : x86_64
> # nrcpus online : 12
> # nrcpus avail : 12
> # cpudesc : AMD Ryzen 5 3600X 6-Core Processor
> # cpuid : AuthenticAMD,23,113,0
> # total memory : 32890748 kB
> # cmdline : /home/acme/bin/perf record -a -g -F 99 sleep 1
> # event : name = cycles, , id = { 2918625, 2918626, 2918627, 2918628, 2918629, 2918630, 2918631, 2918632, 2918633, 2918634, 2918635, 2918636 }, size = 120, { sample_period, sample_freq } = 99, sample_type = IP|TID|TIME|CALLCHAIN|CPU|PERIOD, read_format = ID, disabled = 1,>
> # CPU_TOPOLOGY info available, use -I to display
> # NUMA_TOPOLOGY info available, use -I to display
> # pmu mappings: amd_df = 8, software = 1, ibs_op = 11, ibs_fetch = 10, uprobe = 7, cpu = 4, amd_iommu_0 = 12, breakpoint = 5, amd_l3 = 9, tracepoint = 2, kprobe = 6, msr = 13
> # CACHE info available, use -I to display
> # time of first sample : 87600.831767
> # time of last sample : 87601.096829
> # sample duration : 265.062 ms
> # MEM_TOPOLOGY info available, use -I to display
> # bpf_prog_info 40: bpf_prog_6deef7357e7b4530 addr 0xffffffffc030650c size 66
> # bpf_prog_info 41: bpf_prog_6deef7357e7b4530 addr 0xffffffffc03080e8 size 66
> # bpf_prog_info 42: bpf_prog_6deef7357e7b4530 addr 0xffffffffc030a218 size 66
> # bpf_prog_info 43: bpf_prog_6deef7357e7b4530 addr 0xffffffffc036c698 size 66
> # bpf_prog_info 44: bpf_prog_5a2b06eab81b8f51 addr 0xffffffffc036e2c0 size 1132
> # bpf_prog_info 45: bpf_prog_6deef7357e7b4530 addr 0xffffffffc06e7118 size 66
> # bpf_prog_info 46: bpf_prog_6deef7357e7b4530 addr 0xffffffffc06e9cd8 size 66
> # bpf_prog_info 47: bpf_prog_f3b9e166f6c1aaaa addr 0xffffffffc089732c size 1786
> # bpf_prog_info 48: bpf_prog_6deef7357e7b4530 addr 0xffffffffc0dcb64c size 66
> # bpf_prog_info 49: bpf_prog_6deef7357e7b4530 addr 0xffffffffc0dcd0fc size 66
> # bpf_prog_info 50: bpf_prog_6deef7357e7b4530 addr 0xffffffffc0f4d8dc size 66
> # bpf_prog_info 51: bpf_prog_6deef7357e7b4530 addr 0xffffffffc0f4fc7c size 66
> # bpf_prog_info 52: bpf_prog_84efc2eecc454ca6 addr 0xffffffffc0f6e584 size 373
> # missing features: TRACING_DATA BRANCH_STACK GROUP_DESC AUXTRACE STAT CLOCKID DIR_FORMAT COMPRESSED
> # ========
> #
> [root@five ~]#
>
> And make it available in some UI element.

Good idea!
I'll implement this with the next set of flame graph template changes.


Thanks,
Andreas

2020-04-09 17:29:25

by Andreas Gerstmayr

[permalink] [raw]
Subject: [PATCH] perf script flamegraph: python2 support, update cli args

* removed --indent argument
* renamed -F to -f argument to be consistent with other arguments

Signed-off-by: Andreas Gerstmayr <[email protected]>
---
tools/perf/scripts/python/flamegraph.py | 24 +++++++++++-------------
1 file changed, 11 insertions(+), 13 deletions(-)

diff --git a/tools/perf/scripts/python/flamegraph.py b/tools/perf/scripts/python/flamegraph.py
index 5835d190ca42..61f3be9add6b 100755
--- a/tools/perf/scripts/python/flamegraph.py
+++ b/tools/perf/scripts/python/flamegraph.py
@@ -14,6 +14,7 @@
# Flame Graphs invented by Brendan Gregg <[email protected]>
# Works in tandem with d3-flame-graph by Martin Spier <[email protected]>

+from __future__ import print_function
import sys
import os
import argparse
@@ -43,11 +44,11 @@ class FlameGraphCLI:

if self.args.format == "html" and \
not os.path.isfile(self.args.template):
- print(f"Flame Graph template {self.args.template} does not " +
- f"exist. Please install the js-d3-flame-graph (RPM) or " +
- f"libjs-d3-flame-graph (deb) package, specify an " +
- f"existing flame graph template (--template PATH) or " +
- f"another output format (--format FORMAT).",
+ print("Flame Graph template {} does not exist. Please install "
+ "the js-d3-flame-graph (RPM) or libjs-d3-flame-graph (deb) "
+ "package, specify an existing flame graph template "
+ "(--template PATH) or another output format "
+ "(--format FORMAT).".format(self.args.template),
file=sys.stderr)
sys.exit(1)

@@ -76,8 +77,7 @@ class FlameGraphCLI:
node.value += 1

def trace_end(self):
- json_str = json.dumps(self.stack, default=lambda x: x.toJSON(),
- indent=self.args.indent)
+ json_str = json.dumps(self.stack, default=lambda x: x.toJSON())

if self.args.format == "html":
try:
@@ -85,7 +85,7 @@ class FlameGraphCLI:
output_str = f.read().replace("/** @flamegraph_json **/",
json_str)
except IOError as e:
- print(f"Error reading template file: {e}", file=sys.stderr)
+ print("Error reading template file: {}".format(e), file=sys.stderr)
sys.exit(1)
output_fn = self.args.output or "flamegraph.html"
else:
@@ -95,24 +95,22 @@ class FlameGraphCLI:
if output_fn == "-":
sys.stdout.write(output_str)
else:
- print(f"dumping data to {output_fn}")
+ print("dumping data to {}".format(output_fn))
try:
with open(output_fn, "w") as out:
out.write(output_str)
except IOError as e:
- print(f"Error writing output file: {e}", file=sys.stderr)
+ print("Error writing output file: {}".format(e), file=sys.stderr)
sys.exit(1)


if __name__ == "__main__":
parser = argparse.ArgumentParser(description="Create flame graphs.")
- parser.add_argument("-F", "--format",
+ 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("--indent",
- type=int, help="JSON indentation")
parser.add_argument("--template",
default="/usr/share/d3-flame-graph/d3-flamegraph-base.html",
help="path to flamegraph HTML template")
--
2.25.2

Subject: [tip: perf/core] perf script: Add flamegraph.py script

The following commit has been merged into the perf/core branch of tip:

Commit-ID: 5287f926920688e1151741d49da37a533ccf1960
Gitweb: https://git.kernel.org/tip/5287f926920688e1151741d49da37a533ccf1960
Author: Andreas Gerstmayr <[email protected]>
AuthorDate: Fri, 20 Mar 2020 16:13:48 +01:00
Committer: Arnaldo Carvalho de Melo <[email protected]>
CommitterDate: Thu, 16 Apr 2020 12:19:14 -03:00

perf script: Add flamegraph.py script

This script works in tandem with d3-flame-graph to generate flame graphs
from perf. It supports two output formats: JSON and HTML (the default).
The HTML format will look for a standalone d3-flame-graph template file
in /usr/share/d3-flame-graph/d3-flamegraph-base.html and fill in the
collected stacks.

Usage:

perf record -a -g -F 99 sleep 60
perf script report flamegraph

Combined:

perf script flamegraph -a -F 99 sleep 60

Committer testing:

Tested both with "PYTHON=python3" and with the default, that uses
python2-devel:

Complete set of instructions:

$ mkdir /tmp/build/perf
$ make PYTHON=python3 -C tools/perf O=/tmp/build/perf install-bin
$ export PATH=~/bin:$PATH
$ perf record -a -g -F 99 sleep 60
$ perf script report flamegraph

Now go and open the generated flamegraph.html file in a browser.

At first this required building with PYTHON=python3, but after I
reported this Andreas was kind enough to send a patch making it work
with both python and python3.

Signed-off-by: Andreas Gerstmayr <[email protected]>
Tested-by: Arnaldo Carvalho de Melo <[email protected]>
Cc: Alexander Shishkin <[email protected]>
Cc: Jiri Olsa <[email protected]>
Cc: Mark Rutland <[email protected]>
Cc: Namhyung Kim <[email protected]>
Cc: Peter Zijlstra <[email protected]>
Cc: Brendan Gregg <[email protected]>
Cc: Martin Spier <[email protected]>
Link: http://lore.kernel.org/lkml/[email protected]
Signed-off-by: Arnaldo Carvalho de Melo <[email protected]>
---
tools/perf/scripts/python/bin/flamegraph-record | 2 +-
tools/perf/scripts/python/bin/flamegraph-report | 3 +-
tools/perf/scripts/python/flamegraph.py | 124 +++++++++++++++-
3 files changed, 129 insertions(+)
create mode 100755 tools/perf/scripts/python/bin/flamegraph-record
create mode 100755 tools/perf/scripts/python/bin/flamegraph-report
create mode 100755 tools/perf/scripts/python/flamegraph.py

diff --git a/tools/perf/scripts/python/bin/flamegraph-record b/tools/perf/scripts/python/bin/flamegraph-record
new file mode 100755
index 0000000..725d66e
--- /dev/null
+++ b/tools/perf/scripts/python/bin/flamegraph-record
@@ -0,0 +1,2 @@
+#!/usr/bin/sh
+perf record -g "$@"
diff --git a/tools/perf/scripts/python/bin/flamegraph-report b/tools/perf/scripts/python/bin/flamegraph-report
new file mode 100755
index 0000000..b1a79af
--- /dev/null
+++ b/tools/perf/scripts/python/bin/flamegraph-report
@@ -0,0 +1,3 @@
+#!/usr/bin/sh
+# description: create flame graphs
+perf script -s "$PERF_EXEC_PATH"/scripts/python/flamegraph.py -- "$@"
diff --git a/tools/perf/scripts/python/flamegraph.py b/tools/perf/scripts/python/flamegraph.py
new file mode 100755
index 0000000..61f3be9
--- /dev/null
+++ b/tools/perf/scripts/python/flamegraph.py
@@ -0,0 +1,124 @@
+# 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 <[email protected]>
+# Flame Graphs invented by Brendan Gregg <[email protected]>
+# Works in tandem with d3-flame-graph by Martin Spier <[email protected]>
+
+from __future__ import print_function
+import sys
+import os
+import argparse
+import json
+
+
+class Node:
+ def __init__(self, name, libtype=""):
+ self.name = name
+ self.libtype = libtype
+ self.value = 0
+ self.children = []
+
+ def toJSON(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("root")
+
+ if self.args.format == "html" and \
+ not os.path.isfile(self.args.template):
+ print("Flame Graph template {} does not exist. Please install "
+ "the js-d3-flame-graph (RPM) or libjs-d3-flame-graph (deb) "
+ "package, specify an existing flame graph template "
+ "(--template PATH) or another output format "
+ "(--format FORMAT).".format(self.args.template),
+ 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]"
+
+ for child in node.children:
+ if child.name == name and child.libtype == libtype:
+ return child
+
+ child = Node(name, libtype)
+ node.children.append(child)
+ return child
+
+ def process_event(self, event):
+ node = self.find_or_create_node(self.stack, event["comm"], None)
+ 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"))
+ else:
+ node = self.find_or_create_node(
+ node, entry.get("symbol"), event.get("dso"))
+ node.value += 1
+
+ def trace_end(self):
+ json_str = json.dumps(self.stack, default=lambda x: x.toJSON())
+
+ if self.args.format == "html":
+ try:
+ with open(self.args.template) 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)
+ sys.exit(1)
+ output_fn = self.args.output or "flamegraph.html"
+ else:
+ output_str = json_str
+ output_fn = self.args.output or "stacks.json"
+
+ if output_fn == "-":
+ sys.stdout.write(output_str)
+ else:
+ print("dumping data to {}".format(output_fn))
+ try:
+ with open(output_fn, "w") as out:
+ out.write(output_str)
+ except IOError as e:
+ print("Error writing output file: {}".format(e), 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 flamegraph HTML template")
+ parser.add_argument("-i", "--input",
+ help=argparse.SUPPRESS)
+
+ args = parser.parse_args()
+ cli = FlameGraphCLI(args)
+
+ process_event = cli.process_event
+ trace_end = cli.trace_end