From: Serhei Makarov <serhei@serhei.io>
To: acme@kernel.org, irogers@google.com, namhyung@kernel.org,
james.clark@linaro.org
Cc: jolsa@kernel.org, adrian.hunter@intel.com, peterz@infradead.org,
mingo@kernel.org, linux-kernel@vger.kernel.org,
linux-perf-users@vger.kernel.org, serhei@serhei.io
Subject: [PATCH v2 1/2] perf inject: Support piped data with --convert-callchain
Date: Thu, 12 Nov 2026 15:18:32 -0500 [thread overview]
Message-ID: <20261112201833.78630-1-serhei@serhei.io> (raw)
When profiling applications without framepointers or non-framepointer
distros, it's helpful to pipe stack sample data directly to perf
inject --convert-callchain, e.g.:
perf record -F 999 --call-graph dwarf -o - -- myapp \
| perf inject --convert-callchain -i - -o perf.data
This piped setup doesn't store stack samples on disk, removing a major
performance bottleneck for DWARF stack unwinding as noted in [1].
To support piped output, write a modified copy of the event attributes
in perf_event__repipe_attr. This gets around the inability to modify
event attribute records in-place after they are written to the pipe.
[1]: https://rwmj.wordpress.com/2023/02/14/frame-pointers-vs-dwarf-my-verdict/
> The first most obvious thing is that even with the smallest stack
> data collection, DWARF’s perf.data is over 10 times larger, and it
> balloons even larger once you start to collect more reasonable stack
> sizes. For a single minute of data collection, collecting 10s of
> gigabytes of data is not very practical even on high end machines, and
> continuous performance analysis would be impossible at these data
> rates.
Signed-off-by: Serhei Makarov <serhei@serhei.io>
---
v2 changes)
* Rebased onto perf-tools-next/perf-tools-next which includes the
existing size-calculation fix (cf Ian Rogers, commit daac18e7c42c).
---
tools/perf/builtin-inject.c | 68 +++++++++++++++++++++++++------------
1 file changed, 46 insertions(+), 22 deletions(-)
diff --git a/tools/perf/builtin-inject.c b/tools/perf/builtin-inject.c
index ce7562fc788c..9fd02a6f5849 100644
--- a/tools/perf/builtin-inject.c
+++ b/tools/perf/builtin-inject.c
@@ -229,6 +229,7 @@ static int perf_event__repipe_attr(const struct perf_tool *tool,
union perf_event *event,
struct evlist **pevlist)
{
+ union perf_event *event0;
struct perf_inject *inject = container_of(tool, struct perf_inject,
tool);
struct perf_event_attr attr;
@@ -258,6 +259,25 @@ static int perf_event__repipe_attr(const struct perf_tool *tool,
goto out;
}
+ if (inject->convert_callchain) {
+ /* Copy event to repipe with corrected final attributes,
+ without confusing downstream users of pevlist: */
+ event0 = event;
+ event = (void *)inject->event_copy;
+ if (event == NULL) {
+ inject->event_copy = malloc(PERF_SAMPLE_MAX_SIZE);
+ if (!inject->event_copy)
+ return -ENOMEM;
+ event = (void *)inject->event_copy;
+ }
+ memcpy(event, event0, event0->header.size);
+
+ event->attr.attr.sample_type &= ~(PERF_SAMPLE_REGS_USER | PERF_SAMPLE_STACK_USER);
+ event->attr.attr.sample_regs_user = 0;
+ event->attr.attr.sample_stack_user = 0;
+ event->attr.attr.exclude_callchain_user = 0;
+ }
+
if (!inject->itrace_synth_opts.set) {
ret = perf_event__repipe_synth(tool, event);
goto out;
@@ -519,6 +539,18 @@ static int perf_event__repipe_sample(const struct perf_tool *tool,
return perf_event__repipe_synth(tool, event);
}
+static bool evsel__has_dwarf_callchain(struct evsel *evsel)
+{
+ struct perf_event_attr *attr = &evsel->core.attr;
+ const u64 dwarf_callchain_flags =
+ PERF_SAMPLE_STACK_USER | PERF_SAMPLE_REGS_USER | PERF_SAMPLE_CALLCHAIN;
+
+ if (!attr->exclude_callchain_user)
+ return false;
+
+ return (attr->sample_type & dwarf_callchain_flags) == dwarf_callchain_flags;
+}
+
static int perf_event__convert_sample_callchain(const struct perf_tool *tool,
union perf_event *event,
struct perf_sample *sample,
@@ -526,7 +558,7 @@ static int perf_event__convert_sample_callchain(const struct perf_tool *tool,
{
struct perf_inject *inject = container_of(tool, struct perf_inject, tool);
struct evsel *evsel = sample->evsel;
- struct callchain_cursor *cursor = get_tls_callchain_cursor();
+ struct callchain_cursor *cursor;
union perf_event *event_copy = (void *)inject->event_copy;
struct callchain_cursor_node *node;
struct thread *thread;
@@ -535,6 +567,10 @@ static int perf_event__convert_sample_callchain(const struct perf_tool *tool,
u64 i, k;
int ret;
+ if (!evsel__has_dwarf_callchain(evsel))
+ return perf_event__repipe(tool, event, sample, machine);
+ cursor = get_tls_callchain_cursor();
+
if (event_copy == NULL) {
inject->event_copy = malloc(PERF_SAMPLE_MAX_SIZE);
if (!inject->event_copy)
@@ -2650,18 +2686,6 @@ static int __cmd_inject(struct perf_inject *inject)
return ret;
}
-static bool evsel__has_dwarf_callchain(struct evsel *evsel)
-{
- struct perf_event_attr *attr = &evsel->core.attr;
- const u64 dwarf_callchain_flags =
- PERF_SAMPLE_STACK_USER | PERF_SAMPLE_REGS_USER | PERF_SAMPLE_CALLCHAIN;
-
- if (!attr->exclude_callchain_user)
- return false;
-
- return (attr->sample_type & dwarf_callchain_flags) == dwarf_callchain_flags;
-}
-
int cmd_inject(int argc, const char **argv)
{
struct perf_inject inject = {
@@ -2956,15 +2980,15 @@ int cmd_inject(int argc, const char **argv)
if (inject.convert_callchain) {
struct evsel *evsel;
- if (inject.output.is_pipe || inject.session->data->is_pipe) {
- pr_err("--convert-callchain cannot work with pipe\n");
- goto out_delete;
- }
-
- evlist__for_each_entry(inject.session->evlist, evsel) {
- if (!evsel__has_dwarf_callchain(evsel) && !evsel__is_dummy_event(evsel)) {
- pr_err("--convert-callchain requires DWARF call graph.\n");
- goto out_delete;
+ /* For on-disk data, check evlist up-front.
+ For piped data, evlist is not available yet;
+ check in perf_event__convert_sample_callchain. */
+ if (!inject.session->data->is_pipe) {
+ evlist__for_each_entry(inject.session->evlist, evsel) {
+ if (!evsel__has_dwarf_callchain(evsel) && !evsel__is_dummy_event(evsel)) {
+ pr_err("--convert-callchain requires DWARF call graph.\n");
+ goto out_delete;
+ }
}
}
--
2.56.0
next reply other threads:[~2026-11-12 20:19 UTC|newest]
Thread overview: 4+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-11-12 20:18 Serhei Makarov [this message]
2026-11-12 20:18 ` [PATCH v2 2/2] perf test: Test piped data in inject-callchain.sh Serhei Makarov
2026-11-12 20:28 ` sashiko-bot
2026-11-12 20:44 ` [PATCH v2 1/2] perf inject: Support piped data with --convert-callchain 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=20261112201833.78630-1-serhei@serhei.io \
--to=serhei@serhei.io \
--cc=acme@kernel.org \
--cc=adrian.hunter@intel.com \
--cc=irogers@google.com \
--cc=james.clark@linaro.org \
--cc=jolsa@kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-perf-users@vger.kernel.org \
--cc=mingo@kernel.org \
--cc=namhyung@kernel.org \
--cc=peterz@infradead.org \
/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