[PATCH v2 09/11] perf cs-etm: Reset per-thread decoder on CPU migration

From: Leo Yan

Date: Fri Oct 16 2026 - 17:12:45 EST


In per-thread mode all AUX fragments for a thread are decoded by a
single decoder, even if the task migrates between CPUs. If independent
fragments come from different CPUs, the trace stream may not be
contiguous for that decoder context.

Retain the CPU from each AUX sample in its queued fragment. Compare the
preceding and fetched buffer CPUs in cs_etm__get_trace() and request a
reset when either CPU is unknown or their CPU IDs differ. Indexed files
can omit PERF_SAMPLE_CPU and hardware-ID records while remaining
decodable through TID matching and the legacy metadata mapping. An
unknown CPU therefore cannot establish trace continuity.

On 32-bit hosts, queuing a large AUX fragment can split it into several
buffers while returning only the final piece. Save the queue tail
before queuing and tag every newly added piece with the AUX sample CPU.
Do this after queuing to preserve CPU filtering based on the original
AUXTRACE record. Per-CPU fragments already match the sample CPU, so use
the same assignment in both modes.

Skip CPU comparisons for internally split continuation buffers, which
belong to the same AUX fragment. Keep AUX flags on the final piece so a
collision applies after the whole fragment.

Check CPU metadata even for empty fragments. Preserve any reset request
until the next nonempty block is decoded.

Signed-off-by: Leo Yan <leo.yan@xxxxxxx>
---
tools/perf/util/cs-etm.c | 30 +++++++++++++++++++++++++++++-
1 file changed, 29 insertions(+), 1 deletion(-)

diff --git a/tools/perf/util/cs-etm.c b/tools/perf/util/cs-etm.c
index 6f95730fe3b94ae3f534eee505132a2efbc4ff96..8f04d7ceddd9d1f7ff22af234b19400efe27ba2b 100644
--- a/tools/perf/util/cs-etm.c
+++ b/tools/perf/util/cs-etm.c
@@ -1486,6 +1486,16 @@ cs_etm__get_trace(struct cs_etm_queue *etmq)
(old_buffer->aux_flags & PERF_AUX_FLAG_COLLISION)))
etmq->reset_decoder = true;

+ /*
+ * Unknown or different CPUs cannot establish per-thread continuity.
+ * Internally split buffers still belong to the same AUX fragment.
+ */
+ if (etmq->etm->per_thread_decoding && old_buffer &&
+ !aux_buffer->consecutive &&
+ (old_buffer->cpu.cpu == -1 || aux_buffer->cpu.cpu == -1 ||
+ old_buffer->cpu.cpu != aux_buffer->cpu.cpu))
+ etmq->reset_decoder = true;
+
return 0;
}

@@ -3251,10 +3261,15 @@ static int cs_etm__queue_aux_fragment(struct perf_session *session, off_t file_o
if (aux_offset >= auxtrace_event->offset &&
aux_offset + aux_size <= auxtrace_event->offset + auxtrace_event->size) {
struct cs_etm_queue *etmq = cs_etm__get_queue(etm, auxtrace_event->cpu);
+ struct auxtrace_buffer *pos;
+ struct list_head *head;

if (!etmq)
return -EINVAL;

+ head = &etm->queues.queue_array[etmq->queue_nr].head;
+ pos = list_last_entry_or_null(head, struct auxtrace_buffer, list);
+
/*
* If this AUX event was inside this buffer somewhere, create a new auxtrace event
* based on the sizes of the aux event, and queue that fragment.
@@ -3272,9 +3287,22 @@ static int cs_etm__queue_aux_fragment(struct perf_session *session, off_t file_o
if (err)
return err;

- if (buffer)
+ if (buffer) {
buffer->aux_flags = aux_event->flags;

+ /*
+ * Preserve queue-time CPU filtering and tag every split
+ * piece added after the previous tail, up to the returned
+ * buffer, for migration detection.
+ */
+ pos = list_prepare_entry(pos, head, list);
+ list_for_each_entry_continue(pos, head, list) {
+ pos->cpu.cpu = sample->cpu;
+ if (pos == buffer)
+ break;
+ }
+ }
+
format = (aux_event->flags & PERF_AUX_FLAG_CORESIGHT_FORMAT_RAW) ?
UNFORMATTED : FORMATTED;
if (etmq->format != UNSET && format != etmq->format) {

--
2.34.1