On 03/08/2026 10:01, Amir Ayupov wrote:
This series implements --itrace=L for Arm CoreSight ETM: decoded branch
Hi Amir,
How did you send this? The cover letter seems to be on a different thread to the patches.
history is attached to the PMU samples already present in the recording, rather than to synthesised instruction samples.
The motivating use case is context-sensitive PGO, which wants a callchain and a branch stack describing the same point in time. Recording a cycles event with call-graph=fp and aux-action=pause supplies the callchain, and the ETM trace leading up to that sample supplies the branch stack, without having to trace a long-running process continuously.
Intel PT has had this since commit f0a0251cee80 ("perf intel-pt: Add support for synthesizing branch stacks for regular events"), so this deliberately follows intel-pt: the same --itrace=L option and the same thread_stack__br_sample_late() call.
Patches 1 to 4 are independent fixes and infrastructure the feature needs:
1 makes an inconsistent HEADER_GROUP_DESC non-fatal. AUX recordings using aux-action pause/resume produce a group descriptor the strict reader rejects, which makes an otherwise readable perf.data unreadable, so without this the recipe in patch 9 cannot be decoded at all. Useful on its own. 2 reports hw_idx as -1 rather than 0 in reconstructed branch stacks, since they have no hardware index. 3 bounds a wrapped memcpy in thread_stack__br_sample(). Latent today, reachable once a caller keeps a ring larger than the requested output depth. 4 adds a dlfilter that drops samples with an empty branch stack.
Patch 5 is a no-functional-change refactor splitting cs_etm__process_timestamped_queues() into its three parts. Heap seeding moves to cs_etm__update_queues(), gated on queues.new_data and mirroring intel_pt_update_queues(); the end-of-session flush moves to cs_etm__flush_timestamped_queues(); and the decode loop is left on its own so patch 6 can drive it once per sample. Neither seeding nor flushing can be repeated, which is why they have to come out first. The moved code is unchanged, so both loops appear as context in the diff.
Patch 6 is the feature and patch 7 adds a shell test.
Patch 8 is where review attention is most useful. --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; they 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. On a 12 s capture with pause period 100003 and resume period 8350251, of 335291 samples that received branch history only 3371 were backed by trace decoded for that sample.
A trace window belongs to exactly one sample, and with AUX pause and resume the sample is what stops the trace, so the pairing is one to one by
What happens when aux-pause isn't used and there isn't a 1:1 pairing? There is a lot of description of that which implies that it doesn't work if there isn't. But as far as I can tell it works just as well by using the timestamps?
construction. Patch 8 therefore takes the branch history when attaching it instead of copying it, and a later sample with nothing newly decoded finds an empty branch stack, which the dlfilter removes. thread_stack__br_sample() is unchanged, so lowercase --itrace=l keeps the overlapping branch stacks it produces today.
Patch 9 documents the workflow.
Because the sample is what stops the trace, the history attached to it lines up well with the callchain: on a brstack capture the leaf of the callchain matched the function containing the newest branch stack entry's target for 93.5% of attached samples. The residual comes from the decode loop stopping on interpolated timestamps, so a few branches that ran just after the sample can still be included. Trimming those with the sample ip raises it to 96.8%, but that matters far more for free-running ETM strobing than for pause and resume, so I have left it out of this series and will send it separately.
Patch 8 could be squashed into patch 6, since patch 6 on its own produces mostly stale history. I kept them apart so the decode mechanism and the attachment policy can be reviewed separately, but I am happy to fold them.
Testing
Built with:
make -C tools/perf NO_LIBELF=1 NO_LIBTRACEEVENT=1 CORESIGHT=1
Every patch builds individually. checkpatch reports only "does MAINTAINERS need updating?" for the two new files and "quoted string split across lines" for the dlfilter description string, which matches how dlfilter-show-cycles.c already writes it.
Tested on Arm Neoverse V2 with CoreSight ETM:
- perf test "CoreSight branch history on existing samples": Ok, 3 for 3
- captures from 5 MiB to 2.5 GiB decoded with --itrace=L64, no decode errors
- the other CoreSight tests are unchanged by this series; four of them fail identically at the base commit on this machine
Can you report or investigate these failures please. None of the tests should be failing on TRBE hardware, at least on the latest perf-tools-next branch. You can try applying "[PATCH 00/14] perf cs-etm: Per-thread mode fixes and snapshot wrap support" to be sure, but I don't think that fixes any current failures.
Amir Ayupov (9): perf header: Tolerate inconsistent HEADER_GROUP_DESC perf thread-stack: Report branch stack hw_idx as not available perf thread-stack: Bound wrapped branch stack copy perf dlfilter: Add non-empty branch stack filter perf cs-etm: Split up cs_etm__process_timestamped_queues() perf cs-etm: Add branch history to existing samples perf test cs-etm: Test branch history on existing samples perf cs-etm: Consume branch history when attaching it to a sample Documentation: coresight: Document context-sensitive PGO workflow
.../trace/coresight/coresight-perf.rst | 62 +++++ tools/perf/Makefile.perf | 1 + .../dlfilters/dlfilter-nonempty-brstack.c | 26 ++ .../tests/shell/coresight/add_last_branch.sh | 175 +++++++++++++ tools/perf/util/cs-etm.c | 242 ++++++++++++++++-- tools/perf/util/header.c | 42 ++- tools/perf/util/thread-stack.c | 21 +- tools/perf/util/thread-stack.h | 1 + 8 files changed, 544 insertions(+), 26 deletions(-) create mode 100644 tools/perf/dlfilters/dlfilter-nonempty-brstack.c create mode 100755 tools/perf/tests/shell/coresight/add_last_branch.sh
base-commit: da85966dfd23a3b03e00ee3bce6ad301f0a2b229