From: sashiko-bot@kernel.org
To: "Aaron Tomlin" <atomlin@atomlin.com>
Cc: linux-perf-users@vger.kernel.org
Subject: Re: [PATCH perf-tools-next v8 4/6] perf ftrace: Support display of inlined functions in function graph tracer
Date: Sun, 18 Oct 2026 03:00:14 +0000 [thread overview]
Message-ID: <sashiko-outbox-175899@kernel.org> (raw)
In-Reply-To: <20261018023753.584873-5-atomlin@atomlin.com>
Thank you for your contribution! Sashiko AI review found 5 potential issue(s) to consider:
- [Medium] perf ftrace: find_func_column() misses latency-format column in verbose mode
- [Medium] perf ftrace: select_tracer() overrides function_graph tracer with --inline
- [Medium] perf ftrace: idle tasks across CPUs share a single ftrace_task_state
- [Medium] perf ftrace: perf_ftrace__setup_inlines() ignores vmlinux load failures
- [Medium] perf test ftrace: test_inline_processing() hardcodes build-specific offset
--
commit a1b712dfdc11c697976e8ccf628bd22443d5d421
Author: Aaron Tomlin <atomlin@atomlin.com>
perf ftrace: Support display of inlined functions in function graph tracer
This commit introduces the --inline option to perf ftrace to resolve caller
return addresses via DWARF debug information in vmlinux and synthesize
inlined function frames into the function_graph call stream. It also adds per-
task call stack tracking, the -k/--vmlinux option, and unit tests in
tools/perf/tests/ftrace.c.
> diff --git a/tools/perf/builtin-ftrace.c b/tools/perf/builtin-ftrace.c
> index c715cbebaabe9..cdacf5af14a65 100644
> --- a/tools/perf/builtin-ftrace.c
> +++ b/tools/perf/builtin-ftrace.c
[ ... ]
> @@ -1973,6 +1991,20 @@ int cmd_ftrace(int argc, const char **argv)
>
> switch (subcmd) {
> case PERF_FTRACE_TRACE:
> + if (ftrace.use_inline) {
> + if (ftrace.tracer && strcmp(ftrace.tracer, "function_graph") != 0) {
> + pr_err("Error: --inline option is only supported with function_graph tracer\n");
> + ret = -EINVAL;
> + goto out_delete_filters;
> + }
> + ftrace.tracer = "function_graph";
> + }
[Severity: Medium]
Can select_tracer() override ftrace.tracer back to "function" after this
check runs?
In cmd_ftrace(), ftrace.tracer defaults to DEFAULT_TRACER ("function_graph"),
so running perf ftrace -T <func> --inline (or -N <func> --inline) passes this
validation. However, when __cmd_ftrace() starts, it calls select_tracer():
tools/perf/builtin-ftrace.c:select_tracer() {
bool graph = !list_empty(&ftrace->graph_funcs) ||
!list_empty(&ftrace->nograph_funcs);
bool func = !list_empty(&ftrace->filters) ||
!list_empty(&ftrace->notrace);
/* The function_graph has priority over function tracer. */
if (graph)
ftrace->tracer = "function_graph";
else if (func)
ftrace->tracer = "function";
...
}
Because filters or notrace is non-empty and graph_funcs/nograph_funcs are
empty, select_tracer() overwrites ftrace->tracer to "function", causing
__cmd_ftrace() to configure the function tracer while feeding its output into
ftrace_process_fgraph_line(). Conversely, if ftrace.tracer is set to
"function" in perfconfig and the user runs perf ftrace -G <func> --inline,
cmd_ftrace() rejects it before select_tracer() can select "function_graph".
Should the --inline tracer check or enforcement happen in integration with
select_tracer()?
> diff --git a/tools/perf/tests/ftrace.c b/tools/perf/tests/ftrace.c
> index 596cecee844e3..6e20a61860a9e 100644
> --- a/tools/perf/tests/ftrace.c
> +++ b/tools/perf/tests/ftrace.c
> @@ -62,6 +66,465 @@ static int test_filter_retaddr(void)
> return TEST_OK;
> }
>
> +#define TEST_CHECK(text, cond) \
> +do { \
> + if (!(cond)) { \
> + pr_debug("FAILED %s:%d %s\n", __FILE__, __LINE__, text); \
> + ret = TEST_FAIL; \
> + goto out_cleanup; \
> + } \
> +} while (0)
> +
> +static int test_inline_processing(void)
> +{
> + static const char line_entry[] = " 0) | wake_up_new_task() {";
> + static const char line_enqueue[] =
> + " 0) | enqueue_task() { /* <-wake_up_new_task+0x1d1/0x3e0 */";
[ ... ]
> + vmlinux = vmlinux_path__find();
> +
> + /* If vmlinux is present, test resolving symbols */
> + if (vmlinux) {
> + symbol_conf.vmlinux_name = vmlinux;
> + symbol_conf.ignore_vmlinux_buildid = true;
> + ret = perf_ftrace__setup_inlines(&ftrace);
> + if (ret < 0 || !ftrace.machine) {
> + ret = TEST_SKIP;
> + goto out_cleanup;
> + }
> +
> + out = open_memstream(&out_buf, &out_len);
> + if (!out) {
> + ret = TEST_FAIL;
> + goto out_cleanup;
> + }
> +
> + /* Feed simulated function graph trace lines */
> + ftrace_process_fgraph_line(&ftrace, line_entry, out);
> + ftrace_process_fgraph_line(&ftrace, line_enqueue, out);
> + ftrace_process_fgraph_line(&ftrace, line_exit_enqueue, out);
> + ftrace_process_fgraph_line(&ftrace, line_exit_task, out);
> +
> + fclose(out);
> + ftrace_cpu_states__clear();
> +
> + pr_debug("Processed output:\n%s\n", out_buf);
> +
> + /* Verify activate_task entry with inline hint */
> + TEST_CHECK("contains activate_task entry with inline hint",
> + strstr(out_buf, "activate_task() { /* (inline) */") != NULL);
[Severity: Medium]
Will test_inline_processing() fail on systems where the host vmlinux does not
have an inlined activate_task() call specifically at wake_up_new_task+0x1d1?
When vmlinux_path__find() finds a host vmlinux file, the test feeds
line_enqueue with the hardcoded offset wake_up_new_task+0x1d1/0x3e0 and
requires DWARF resolution to produce "activate_task() { /* (inline) */". Because
the instruction offset +0x1d1 (and whether activate_task is inlined) depends
on the architecture, compiler, optimization flags, and kernel configuration,
as well as DWARF support in both vmlinux and the perf build, this check will
return TEST_FAIL on other kernel builds.
Also, symbol_conf.vmlinux_name and symbol_conf.ignore_vmlinux_buildid are
modified here without being restored in out_cleanup.
> diff --git a/tools/perf/util/ftrace.c b/tools/perf/util/ftrace.c
> index aaf108369c365..020551a4d7e00 100644
> --- a/tools/perf/util/ftrace.c
> +++ b/tools/perf/util/ftrace.c
[ ... ]
> +static bool parse_context_switch(const char *line)
> +{
> + const char *arrow = strstr(line, " => ");
> + const char *p;
> + const char *next;
> + const char *hyphen;
> + char *endptr;
> + long cpu_val;
> + long pid_val;
> +
> + if (!arrow)
> + return false;
> +
> + /* Find CPU number before ')' */
> + p = skip_spaces(line);
> + cpu_val = strtol(p, &endptr, 10);
> + if (endptr == p || *endptr != ')')
> + return false;
> +
> + if (cpu_val < 0 || cpu_val >= FTRACE_MAX_CPUS)
> + return false;
> +
> + /* Parse the target task's PID after the arrow (" => comm-pid") */
> + next = skip_spaces(arrow + 4);
> + hyphen = strrchr(next, '-');
> + if (!hyphen)
> + return false;
> +
> + pid_val = strtol(hyphen + 1, &endptr, 10);
> + if (endptr == hyphen + 1)
> + return false;
> +
> + ftrace_cpu_tasks[cpu_val] = get_task_state((pid_t)pid_val);
> + return true;
> +}
[Severity: Medium]
What happens in system-wide tracing when multiple CPUs switch to the idle
task (<idle>-0)?
Each CPU has its own swapper thread, all with PID 0, which ftrace formats as
"<idle>-0" in context-switch banners and in the verbose comm-pid column. When
parse_context_switch() or find_func_column() sees "<idle>-0", pid_val is 0 and
get_task_state(0) returns the same struct ftrace_task_state instance for all
idle CPUs.
If multiple CPUs are executing in idle simultaneously, won't their interleaved
trace lines share and corrupt the same stack, depth, and inlined_depth? (Note
that get_cpu_state() uses -1 - cpu before a banner is seen; should pid_val == 0
similarly map to a per-CPU state such as -1 - cpu_val?)
[ ... ]
> +static void make_blank_prefix(const struct perf_ftrace *ftrace, const char *line,
> + const char *pipe_char, char *buf, size_t size)
> +{
> + size_t len = pipe_char - line + 1;
> + const char *start_delim = NULL;
> + char *c;
> +
> + if (!size)
> + return;
> +
> + if (len >= size)
> + len = size - 1;
> +
> + memcpy(buf, line, len);
> + buf[len] = '\0';
> +
> + if (*pipe_char == '|') {
> + if (ftrace->graph_verbose) {
> + /* In verbose mode, duration starts after the comm-pid '|' */
> + for (const char *p = pipe_char - 1; p >= line; p--) {
> + if (*p == '|') {
> + start_delim = p;
> + break;
> + }
> + }
> + } else {
> + /* In default mode, duration starts after CPU ')' */
> + start_delim = strchr(line, ')');
> + }
> +
> + if (start_delim && start_delim < pipe_char && start_delim < line + len) {
> + char *end = buf + (pipe_char - line);
> +
> + if (end > buf + len)
> + end = buf + len;
> + for (c = buf + (start_delim - line) + 1; c < end; c++)
> + *c = ' ';
> + }
> + }
> +}
> +
> +/**
> + * find_func_column - Locate the function call column in a trace record
> + * @ftrace: Perf ftrace context
> + * @line: The trace record to parse
> + * @cpu: Pointer to store the CPU number
> + *
> + * Locate the context columns that precede the function call column of a
> + * function_graph trace record. Depending on the funcgraph-* options set
> + * up by perf, a record looks like:
> + *
> + * [ abstime |] CPU) [ comm-pid |] [ duration |] func
> + *
> + * The abstime and comm-pid columns are only present with --graph-opts
> + * verbose.
> + *
> + * Return: A pointer to the last character of the context columns (the final
> + * '|' delimiter). Returns NULL if @line is not a trace record.
> + */
> +static const char *find_func_column(const struct perf_ftrace *ftrace,
> + const char *line, int *cpu)
> +{
> + const char *p = line;
> + char *endptr;
> + long val;
> +
> + if (ftrace->graph_verbose) {
> + p = strchr(p, '|');
> + if (!p)
> + return NULL;
> + p++;
> + }
> +
> + p = skip_spaces(p);
> + val = strtol(p, &endptr, 10);
> + if (endptr == p || *endptr != ')')
> + return NULL;
> + p = endptr;
> +
> + if (ftrace->graph_verbose) {
> + const char *proc_start = p;
> +
> + p = strchr(p, '|');
> + if (!p)
> + return NULL;
> +
> + if (val >= 0 && val < FTRACE_MAX_CPUS) {
> + const char *h = p - 1;
> +
> + while (h >= proc_start && *h != '-')
> + h--;
> + if (h >= proc_start && *h == '-') {
> + long pid_val = strtol(h + 1, NULL, 10);
> + ftrace_cpu_tasks[(int)val] = get_task_state((pid_t)pid_val);
> + }
> + }
> + }
> +
> + p = strchr(p + 1, '|');
> + if (!p)
> + return NULL;
> +
> + *cpu = (int)val;
> + return p;
> +}
[Severity: Medium]
Does find_func_column() account for the latency-format column when
ftrace->graph_verbose is enabled?
In tools/perf/builtin-ftrace.c, set_tracing_funcgraph_verbose() enables three
tracing options:
static int set_tracing_funcgraph_verbose(struct perf_ftrace *ftrace)
{
if (!ftrace->graph_verbose)
return 0;
if (write_tracing_option_file("funcgraph-proc", "1") < 0)
return -1;
if (write_tracing_option_file("funcgraph-abstime", "1") < 0)
return -1;
if (write_tracing_option_file("latency-format", "1") < 0)
return -1;
return 0;
}
When latency-format is enabled alongside funcgraph-abstime and funcgraph-proc,
the kernel's function_graph tracer emits four '|' delimiters before the
function column:
<abstime> | <cpu>) <comm>-<pid> | <lat-fmt> | <duration> | <func>
However, find_func_column() only advances past three '|' characters in
verbose mode, returning a pointer to the '|' at the end of the latency-format
column instead of the '|' at the end of the duration column.
Consequently, in ftrace_process_fgraph_line():
1. On entry lines with a blank duration column, func_start points to the
duration column's trailing "| func() {", so extract_entry_func_name()
extracts "|" as the function name.
2. On exit lines with a duration (e.g. " 1.234 us | }"), func_start
points to the duration value rather than '}', so is_exit is false and
function exits are never detected.
3. make_blank_prefix() blanks out the latency-format column and truncates the
prefix before the duration column.
[ ... ]
> +int perf_ftrace__setup_inlines(struct perf_ftrace *ftrace)
> +{
> + struct map *kmap;
> +
> + if (!ftrace->use_inline)
> + return 0;
> +
> + symbol_conf.try_vmlinux_path = (symbol_conf.vmlinux_name == NULL);
> + symbol_conf.inline_name = true;
> + if (symbol_conf.vmlinux_name)
> + symbol_conf.ignore_vmlinux_buildid = true;
> +
> + if (symbol__init(NULL) < 0) {
> + pr_warning("Failed to initialize symbols for inlined functions\n");
> + return -1;
> + }
> +
> + ftrace->machine = machine__new_host(NULL);
> + if (!ftrace->machine) {
> + pr_warning("Failed to create host machine for symbols\n");
> + return -1;
> + }
> +
> + if (symbol_conf.vmlinux_name) {
> + kmap = machine__kernel_map(ftrace->machine);
> + if (kmap)
> + dso__load_vmlinux(map__dso(kmap), kmap, symbol_conf.vmlinux_name, false);
> + } else {
> + machine__load_vmlinux_path(ftrace->machine);
> + }
> +
> + return 0;
> +}
[Severity: Medium]
Should perf_ftrace__setup_inlines() check the return values of
dso__load_vmlinux() and machine__load_vmlinux_path()?
Both functions return <= 0 when loading vmlinux fails (for example, if the
user passes a non-existent or invalid path via -k/--vmlinux, or if no vmlinux
with symbols is found on the system). Because the return values are ignored
and perf_ftrace__setup_inlines() unconditionally returns 0,perf ftrace
--inline proceeds without a loaded vmlinux DSO (and test_inline_processing()
does not skip when vmlinux cannot be loaded).
If loading fails, should perf_ftrace__setup_inlines() clean up ftrace->machine
and return an error?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261018023753.584873-1-atomlin@atomlin.com?part=4
next prev parent reply other threads:[~2026-10-18 3:00 UTC|newest]
Thread overview: 13+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-10-18 2:37 [PATCH perf-tools-next v8 0/6] perf ftrace: Support inlined functions and display enhancements in function graph tracer Aaron Tomlin
2026-10-18 2:37 ` [PATCH perf-tools-next v8 1/6] perf ftrace: Optimise __cmd_ftrace() stack frame with strbuf streaming Aaron Tomlin
2026-10-18 2:49 ` sashiko-bot
2026-10-18 2:37 ` [PATCH perf-tools-next v8 2/6] perf ftrace: Support filtering return address comments Aaron Tomlin
2026-10-18 2:46 ` sashiko-bot
2026-10-18 2:37 ` [PATCH perf-tools-next v8 3/6] perf symbol: Introduce vmlinux_path__find() helper Aaron Tomlin
2026-10-18 2:45 ` sashiko-bot
2026-10-18 2:37 ` [PATCH perf-tools-next v8 4/6] perf ftrace: Support display of inlined functions in function graph tracer Aaron Tomlin
2026-10-18 3:00 ` sashiko-bot [this message]
2026-10-18 2:37 ` [PATCH perf-tools-next v8 5/6] perf ftrace: Support omitting execution duration " Aaron Tomlin
2026-10-18 2:51 ` sashiko-bot
2026-10-18 2:37 ` [PATCH perf-tools-next v8 6/6] perf ftrace: Filter extraneous functions " Aaron Tomlin
2026-10-18 2:55 ` sashiko-bot
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=sashiko-outbox-175899@kernel.org \
--to=sashiko-bot@kernel.org \
--cc=atomlin@atomlin.com \
--cc=linux-perf-users@vger.kernel.org \
--cc=sashiko-reviews@lists.linux.dev \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox