[PATCH 8/9] perf cs-etm: Consume branch history when attaching it to a sample
Amir Ayupov <[email protected]> Mon, 3 Aug 2026 02:06:39 -0700
| Newsgroups | org.kernel.vger.linux-perf-users,org.infradead.lists.linux-arm-kernel,org.kernel.vger.linux-doc |
|---|---|
| Message-ID | <[email protected]> |
--itrace=L attaches whatever the thread stack holds when a sample is processed. With a duty cycled trace most samples fire while the trace is off. Those samples have nothing newly decoded, but the thread stack still holds the previous window, so they were being given branches that ran an arbitrary amount of time earlier as if they immediately preceded the sample. The reconstruction is discarded on a trace discontinuity, but that does not help here: the discontinuity that ends a gap is only decoded once trace resumes, which is after the samples in the gap have been processed. A trace window belongs to exactly one sample. With AUX pause and resume the sample is what stops the trace, so the pairing is one to one by construction. Take the branch history when attaching it instead of copying it, and a later sample with nothing newly decoded then finds an empty branch stack, which dlfilter-nonempty-brstack.so removes. This is not a small correction. On a 12 s single-threaded capture with pause period 100003 and resume period 8350251, of 335291 samples that previously received branch history only 3371 were backed by trace decoded for that sample; the other 331920 repeated an earlier window. The proportion follows the ETM duty cycle, so it holds for any low duty cycle configuration. Note that thread_stack__br_sample(), used by lowercase --itrace=l, still copies, so synthesised instruction samples keep the overlapping branch stacks they have today. Signed-off-by: Amir Ayupov <[email protected]> --- tools/perf/util/cs-etm.c | 17 ++++++++--------- tools/perf/util/thread-stack.c | 17 +++++++++++++++++ tools/perf/util/thread-stack.h | 1 + 3 files changed, 26 insertions(+), 9 deletions(-) diff --git a/tools/perf/util/cs-etm.c b/tools/perf/util/cs-etm.c index 048ff97caa936..a3498a0a96a05 100644 --- a/tools/perf/util/cs-etm.c +++ b/tools/perf/util/cs-etm.c @@ -3123,19 +3123,18 @@ static int cs_etm__process_sample(struct cs_etm_auxtrace *etm, return -ENOMEM; /* - * The thread stack is emptied when the decoder reports a - * discontinuity and when a queue runs out of trace, so branches from - * before either of those are never reported. - * - * Note that a sample landing in a gap between two trace windows is - * not covered by that: the discontinuity that ends the gap has not - * been decoded at this point, so the preceding window is still in - * the thread stack and gets attached. Filtering those out needs a - * per-window end time that the decoder does not currently expose. + * Take the branch history rather than copying it. The trace window + * belongs to the sample that ends it, so once it has been attached a + * later sample with nothing newly decoded finds an empty stack rather + * than being given an earlier window's branches. That is the common + * case whenever the trace is duty cycled, by AUX pause/resume or by + * ETM strobing. */ thread_stack__br_sample_late(thread, sample->cpu, etm->br_stack, etm->br_stack_sz, sample->ip, machine__kernel_start(machine)); + thread_stack__br_stack_consume(thread, sample->cpu); + if (etm->br_stack->nr) sample->branch_stack = etm->br_stack; diff --git a/tools/perf/util/thread-stack.c b/tools/perf/util/thread-stack.c index 51eaedb47bb1d..374a291aa48fe 100644 --- a/tools/perf/util/thread-stack.c +++ b/tools/perf/util/thread-stack.c @@ -614,6 +614,23 @@ void thread_stack__sample_late(struct thread *thread, int cpu, } } +/* + * Branch history belongs to the sample that ends the trace window, so a + * decoder that attaches it to an existing sample should take it rather than + * copy it. A later sample with no newly decoded trace then finds an empty + * branch stack instead of the previous window's branches. + */ +void thread_stack__br_stack_consume(struct thread *thread, int cpu) +{ + struct thread_stack *ts = thread__stack(thread, cpu); + + if (!ts || !ts->br_stack_rb) + return; + + ts->br_stack_pos = 0; + ts->br_stack_rb->nr = 0; +} + void thread_stack__br_sample(struct thread *thread, int cpu, struct branch_stack *dst, unsigned int sz) { diff --git a/tools/perf/util/thread-stack.h b/tools/perf/util/thread-stack.h index b3cd09beb62f0..2aec292bd1bcb 100644 --- a/tools/perf/util/thread-stack.h +++ b/tools/perf/util/thread-stack.h @@ -88,6 +88,7 @@ void thread_stack__sample(struct thread *thread, int cpu, struct ip_callchain *c void thread_stack__sample_late(struct thread *thread, int cpu, struct ip_callchain *chain, size_t sz, u64 ip, u64 kernel_start); +void thread_stack__br_stack_consume(struct thread *thread, int cpu); void thread_stack__br_sample(struct thread *thread, int cpu, struct branch_stack *dst, unsigned int sz); void thread_stack__br_sample_late(struct thread *thread, int cpu, -- 2.52.0