* [PATCH v3 00/18] perf cs-etm: Per-thread mode fixes and snapshot wrap support
@ 2026-11-02 12:15 James Clark
2026-11-02 12:15 ` [PATCH v3 01/18] perf cs-etm: Fix nVHE per-thread decoding James Clark
` (17 more replies)
0 siblings, 18 replies; 37+ messages in thread
From: James Clark @ 2026-11-02 12:15 UTC (permalink / raw)
To: Leo Yan, Adrian Hunter
Cc: Suzuki K Poulose, Mike Leach, Leo Yan, Will Deacon,
Peter Zijlstra, Ingo Molnar, Arnaldo Carvalho de Melo,
Namhyung Kim, Mark Rutland, Alexander Shishkin, Jiri Olsa,
Ian Rogers, Mathieu Poirier, Jonathan Corbet, Shuah Khan,
Suyash Mahar, Amir Ayupov, linux-arm-kernel, coresight,
linux-perf-users, linux-kernel, Arnaldo Carvalho de Melo,
linux-doc, James Clark, John Garry
Fix a few issues with per-thread mode:
- On nVHE, everything looks like guest trace instead of host
- Tracing multiple threads didn't really work
After adding a tests for those, it turns out there were some snapshot
bugs not picked up by the other snapshot tests. That showed that the
different snapshot searches for Intel BTS and Arm SPE were inconsistent
and I think all tracers can benefit from using the same pointer fixup
logic, so that gets refactored into the common auxtrace.c. Then SPE can
just share the Intel BTS search as it was obviously copied from it
originally. IntelPT keeps the more advanced duplicate data search, but
in the future that could probably be made the common one and all tracers
would benefit from using it (except maybe BTS if it always has a very
small buffer, but I doubt the overhead of the duplicate search would be
an issue). For now this is more of a refactor rather than behavioral
change so don't do that yet.
Signed-off-by: James Clark <james.clark@linaro.org>
---
Changes in v3:
- Keep the find_snapshot() callback instead of adding
snapshot_has_wrapped() and monotonic_snapshot_head boolean (Adrian)
- Add auxtrace_record__default_find_snapshot() for the simple search for
BTS and SPE (Adrian)
- Add auxtrace_record__snapshot_fixup() for any snapshot search
implementation to use to update the pointers (Adrian)
- Reword UINT64_MAX timestamp comment (Leo)
- Fix disassembly test after rebasing onto exception sample fixes
- Only take 'perf_guest' into account if there is no PIDFMT_CTXTID2
- Improve decoding of old files by hard coding queue 0 for per-thread
- Link to v2: https://patch.msgid.link/20260821-james-cs-unformatted-per-thread-fix-v2-0-00c4fd0701b4@linaro.org
Changes in v2:
- Remove stale "may be reset to -1 in per-thread mode" comment
- Set CPU when queueing auxtrace fragments otherwise
auxtrace_queues__add_event() overwrites the queue's CPU
- Link to v1: https://patch.msgid.link/20260811-james-cs-unformatted-per-thread-fix-v1-0-90565318f5f8@linaro.org
---
James Clark (18):
perf cs-etm: Fix nVHE per-thread decoding
perf cs-etm: Warn for invalid timestamp option
perf cs-etm: Turn on context packet timestamps in per-thread mode
perf cs-etm: Use per-CPU queues for per-thread mode
perf test: cs-etm: Increase window size in disassembly test
perf cs-etm: Don't drop previous target for kernel samples
perf python: Expose sample instruction length
perf cs-etm: Use instruction length in disassembly script
perf cs-etm: Increase default timestamp generation period
perf auxtrace: Turn Intel BTS snapshot search into a generic one
perf arm-spe: Use generic snapshot search
perf auxtrace: intel-pt: Use common snapshot pointer fixup
perf cs-etm: Queue partial AUX records
perf cs-etm: Don't print missing buffers in snapshot mode
perf auxtrace: cs-etm: Capture wrapped snapshots
perf test: Allow infinite named_thread loops
perf test: cs-etm: Add test for per-thread mode
perf test: cs-etm: Test multiple per-thread threads
tools/perf/Documentation/perf-test.txt | 2 +-
tools/perf/arch/arm/util/cs-etm.c | 34 ++++-
tools/perf/arch/arm64/util/arm-spe.c | 147 +--------------------
tools/perf/arch/x86/util/intel-bts.c | 115 +---------------
tools/perf/arch/x86/util/intel-pt.c | 21 +--
tools/perf/python/arm-cs-trace-disasm.py | 22 +--
tools/perf/python/perf.pyi | 1 +
.../perf/tests/shell/coresight/per-thread-multi.sh | 76 +++++++++++
tools/perf/tests/shell/coresight/per-thread.sh | 48 +++++++
.../perf/tests/shell/coresight/raw_dump_stress.sh | 5 -
.../shell/coresight/test_arm_coresight_disasm.sh | 4 +-
tools/perf/tests/workloads/named_threads.c | 7 +-
tools/perf/util/auxtrace.c | 145 +++++++++++++++++++-
tools/perf/util/auxtrace.h | 18 +++
tools/perf/util/cs-etm.c | 113 +++++++++++-----
tools/perf/util/python.c | 1 +
16 files changed, 418 insertions(+), 341 deletions(-)
---
base-commit: 790454475c15508d68ccfc4b023642305100f18f
change-id: 20260605-james-cs-unformatted-per-thread-fix-50e723aa7f0e
prerequisite-message-id: <20261030-perf_cs_etm_fix_non_taken-v3-0-7629b52ab047@arm.com>
prerequisite-patch-id: 4ef889ad751e344ae47c1d75a2a2e6cb2eadf6e1
prerequisite-patch-id: 636b39730334cbc5a76642c070e704ffe178e7be
prerequisite-patch-id: fb58f6daebdf5a194f8aa56ccc4022f932a5a6e5
prerequisite-patch-id: d240c10c6824f8ae6c3f10a1da01226a957f05fb
prerequisite-patch-id: 17b291db261e32dcb0d6f1ab2ed0adb7e0411813
prerequisite-patch-id: 45ae1d523c9e5c8c573309f1dab107a598f3d4f8
prerequisite-patch-id: 39a3eb25b3a45f4481760c25fa270773bcfd94f5
prerequisite-patch-id: 5723a15636c2a28ec40eb6b5c9836d162e971786
prerequisite-patch-id: d18054b586fc38bc832ead185e8c1fbb0ce46f39
prerequisite-patch-id: 6735b192189d7945613458abce3da871cb79b35d
prerequisite-patch-id: 387cb2c6181bef8f45a8fb167e8ef86e4a54ff69
prerequisite-patch-id: 50243a8d1739141aa24aa371a8452d4d2d078a99
prerequisite-patch-id: 2ae6f1e7707ba0c71a0678331b02d738fb25cb3c
prerequisite-patch-id: 1fa0a90cfbc904c4429c1427409125ac44b27aed
Best regards,
--
James Clark <james.clark@linaro.org>
^ permalink raw reply [flat|nested] 37+ messages in thread
* [PATCH v3 01/18] perf cs-etm: Fix nVHE per-thread decoding
2026-11-02 12:15 [PATCH v3 00/18] perf cs-etm: Per-thread mode fixes and snapshot wrap support James Clark
@ 2026-11-02 12:15 ` James Clark
2026-11-02 12:36 ` sashiko-bot
2026-11-02 12:15 ` [PATCH v3 02/18] perf cs-etm: Warn for invalid timestamp option James Clark
` (16 subsequent siblings)
17 siblings, 1 reply; 37+ messages in thread
From: James Clark @ 2026-11-02 12:15 UTC (permalink / raw)
To: Leo Yan, Adrian Hunter
Cc: Suzuki K Poulose, Mike Leach, Leo Yan, Will Deacon,
Peter Zijlstra, Ingo Molnar, Arnaldo Carvalho de Melo,
Namhyung Kim, Mark Rutland, Alexander Shishkin, Jiri Olsa,
Ian Rogers, Mathieu Poirier, Jonathan Corbet, Shuah Khan,
Suyash Mahar, Amir Ayupov, linux-arm-kernel, coresight,
linux-perf-users, linux-kernel, Arnaldo Carvalho de Melo,
linux-doc, James Clark, John Garry
pid_fmt is unset in per-thread mode because we don't need PIDs, so we
can't use it as a hint to determine which EL the host is running at.
That results in falling though to the next part where EL1 is treated as
guest, which is wrong for nVHE.
Fix it by only returning a guest machine when run from perf kvm where a
guest image would be provided, which matches existing behavior in other
parts of Perf. The exception is when pid_fmt shows that the host is
running at EL2 (VHE). Then EL1 is always a guest, so keep using the guest
machine rather than decoding guest trace with the host kernel maps.
Reviewed-by: Leo Yan <leo.yan@arm.com>
Signed-off-by: James Clark <james.clark@linaro.org>
---
tools/perf/util/cs-etm.c | 14 ++++++++++++++
1 file changed, 14 insertions(+)
diff --git a/tools/perf/util/cs-etm.c b/tools/perf/util/cs-etm.c
index c8a6a6e4c7d3..509ec014b276 100644
--- a/tools/perf/util/cs-etm.c
+++ b/tools/perf/util/cs-etm.c
@@ -1102,6 +1102,20 @@ static struct machine *cs_etm__get_machine(struct cs_etm_queue *etmq,
if (pid_fmt == CS_ETM_PIDFMT_CTXTID)
return &etmq->etm->session->machines.host;
+ /*
+ * If guest processing hasn't been enabled, also assume everything is
+ * the host, unless the host is known to be running at EL2 (VHE).
+ *
+ * This matches Perf's guest handling in machines__find_for_cpumode()
+ * etc where guest machines are only created and used when perf_guest is
+ * set. This also guards against accidentally using guest machines when
+ * pid_fmt can't be used as a hint (per-thread mode). With VHE, EL1 is
+ * always a guest so keep using the guest machine to avoid decoding guest
+ * trace with the host kernel maps.
+ */
+ if (!perf_guest && pid_fmt != CS_ETM_PIDFMT_CTXTID2)
+ return &etmq->etm->session->machines.host;
+
/*
* Not perfect, but otherwise assume anything in EL1 is the default
* guest, and everything else is the host. Distinguishing between guest
--
2.43.0
^ permalink raw reply related [flat|nested] 37+ messages in thread
* [PATCH v3 02/18] perf cs-etm: Warn for invalid timestamp option
2026-11-02 12:15 [PATCH v3 00/18] perf cs-etm: Per-thread mode fixes and snapshot wrap support James Clark
2026-11-02 12:15 ` [PATCH v3 01/18] perf cs-etm: Fix nVHE per-thread decoding James Clark
@ 2026-11-02 12:15 ` James Clark
2026-11-02 12:24 ` sashiko-bot
2026-11-02 12:15 ` [PATCH v3 03/18] perf cs-etm: Turn on context packet timestamps in per-thread mode James Clark
` (15 subsequent siblings)
17 siblings, 1 reply; 37+ messages in thread
From: James Clark @ 2026-11-02 12:15 UTC (permalink / raw)
To: Leo Yan, Adrian Hunter
Cc: Suzuki K Poulose, Mike Leach, Leo Yan, Will Deacon,
Peter Zijlstra, Ingo Molnar, Arnaldo Carvalho de Melo,
Namhyung Kim, Mark Rutland, Alexander Shishkin, Jiri Olsa,
Ian Rogers, Mathieu Poirier, Jonathan Corbet, Shuah Khan,
Suyash Mahar, Amir Ayupov, linux-arm-kernel, coresight,
linux-perf-users, linux-kernel, Arnaldo Carvalho de Melo,
linux-doc, James Clark, John Garry
Instead of silently returning an error, print a useful warning.
Reviewed-by: Leo Yan <leo.yan@arm.com>
Signed-off-by: James Clark <james.clark@linaro.org>
---
tools/perf/arch/arm/util/cs-etm.c | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/tools/perf/arch/arm/util/cs-etm.c b/tools/perf/arch/arm/util/cs-etm.c
index d2861d66a661..acfdb3f53821 100644
--- a/tools/perf/arch/arm/util/cs-etm.c
+++ b/tools/perf/arch/arm/util/cs-etm.c
@@ -176,8 +176,11 @@ static int cs_etm_validate_timestamp(struct perf_pmu *cs_etm_pmu, struct evsel *
* 0b01000 Implementation supports a maximum timestamp of 64bits.
*/
trcidr0 &= GENMASK(28, 24);
- if (!trcidr0)
+ if (!trcidr0) {
+ pr_err("%s: timestamp not supported by HW, disable with %s/timestamp=0/\n",
+ CORESIGHT_ETM_PMU_NAME, CORESIGHT_ETM_PMU_NAME);
return -EINVAL;
+ }
return 0;
}
--
2.43.0
^ permalink raw reply related [flat|nested] 37+ messages in thread
* [PATCH v3 03/18] perf cs-etm: Turn on context packet timestamps in per-thread mode
2026-11-02 12:15 [PATCH v3 00/18] perf cs-etm: Per-thread mode fixes and snapshot wrap support James Clark
2026-11-02 12:15 ` [PATCH v3 01/18] perf cs-etm: Fix nVHE per-thread decoding James Clark
2026-11-02 12:15 ` [PATCH v3 02/18] perf cs-etm: Warn for invalid timestamp option James Clark
@ 2026-11-02 12:15 ` James Clark
2026-11-02 12:32 ` sashiko-bot
2026-11-02 12:15 ` [PATCH v3 04/18] perf cs-etm: Use per-CPU queues for " James Clark
` (14 subsequent siblings)
17 siblings, 1 reply; 37+ messages in thread
From: James Clark @ 2026-11-02 12:15 UTC (permalink / raw)
To: Leo Yan, Adrian Hunter
Cc: Suzuki K Poulose, Mike Leach, Leo Yan, Will Deacon,
Peter Zijlstra, Ingo Molnar, Arnaldo Carvalho de Melo,
Namhyung Kim, Mark Rutland, Alexander Shishkin, Jiri Olsa,
Ian Rogers, Mathieu Poirier, Jonathan Corbet, Shuah Khan,
Suyash Mahar, Amir Ayupov, linux-arm-kernel, coresight,
linux-perf-users, linux-kernel, Arnaldo Carvalho de Melo,
linux-doc, James Clark, John Garry
Practically this costs nothing and preempts future changes that will be
made to support decoding trace in order with Perf MMAP and JIT events.
It also helps keep samples in order when we add support for multiple
per-thread mode threads in the next commit.
Signed-off-by: James Clark <james.clark@linaro.org>
---
tools/perf/arch/arm/util/cs-etm.c | 21 +++++++++++++++++----
1 file changed, 17 insertions(+), 4 deletions(-)
diff --git a/tools/perf/arch/arm/util/cs-etm.c b/tools/perf/arch/arm/util/cs-etm.c
index acfdb3f53821..47b4e386cc53 100644
--- a/tools/perf/arch/arm/util/cs-etm.c
+++ b/tools/perf/arch/arm/util/cs-etm.c
@@ -453,13 +453,26 @@ static int cs_etm_recording_options(struct auxtrace_record *itr,
*/
evsel__set_sample_bit(cs_etm_evsel, CPU);
- /*
- * Also the case of per-cpu mmaps, need the contextID in order to be notified
- * when a context switch happened.
- */
if (!perf_cpu_map__is_any_cpu_or_is_empty(cpus)) {
+ /*
+ * Timestamps are required to interleave samples from different
+ * CPUs.
+ */
evsel__set_config_if_unset(cs_etm_evsel, "timestamp", 1);
+ /* Context IDs are required to associate trace to a process */
evsel__set_config_if_unset(cs_etm_evsel, "contextid", 1);
+ } else {
+ /*
+ * Disable periodic timestamps but generate timestamps for trace
+ * synchronization. Even in per-thread mode with a single
+ * process we still need to correlate trace to a specific mmap
+ * around execs, which can be done with only timestamps of trace
+ * synchronization.
+ *
+ * Use MAX to support the kernel widening the field in the
+ * future.
+ */
+ evsel__set_config_if_unset(cs_etm_evsel, "timestamp", UINT64_MAX);
}
/*
--
2.43.0
^ permalink raw reply related [flat|nested] 37+ messages in thread
* [PATCH v3 04/18] perf cs-etm: Use per-CPU queues for per-thread mode
2026-11-02 12:15 [PATCH v3 00/18] perf cs-etm: Per-thread mode fixes and snapshot wrap support James Clark
` (2 preceding siblings ...)
2026-11-02 12:15 ` [PATCH v3 03/18] perf cs-etm: Turn on context packet timestamps in per-thread mode James Clark
@ 2026-11-02 12:15 ` James Clark
2026-11-02 12:34 ` sashiko-bot
2026-11-02 12:15 ` [PATCH v3 05/18] perf test: cs-etm: Increase window size in disassembly test James Clark
` (13 subsequent siblings)
17 siblings, 1 reply; 37+ messages in thread
From: James Clark @ 2026-11-02 12:15 UTC (permalink / raw)
To: Leo Yan, Adrian Hunter
Cc: Suzuki K Poulose, Mike Leach, Leo Yan, Will Deacon,
Peter Zijlstra, Ingo Molnar, Arnaldo Carvalho de Melo,
Namhyung Kim, Mark Rutland, Alexander Shishkin, Jiri Olsa,
Ian Rogers, Mathieu Poirier, Jonathan Corbet, Shuah Khan,
Suyash Mahar, Amir Ayupov, linux-arm-kernel, coresight,
linux-perf-users, linux-kernel, Arnaldo Carvalho de Melo,
linux-doc, James Clark, John Garry
This makes it easier to support multiple threads without creating a
decode tree for every CPU for every thread. It also fixes the CPU field
of synthesised samples being incorrect when the CPU can't be retrieved
using the trace ID in unformatted mode.
It also simplifies by removing a special case in the decode path for
per-thread mode. Per-thread mode is now decoded in exactly the same way
as per-CPU (apart from pre-initialising the context).
Signed-off-by: James Clark <james.clark@linaro.org>
---
tools/perf/util/cs-etm.c | 40 ++++++++++++++++++++++++++++++----------
1 file changed, 30 insertions(+), 10 deletions(-)
diff --git a/tools/perf/util/cs-etm.c b/tools/perf/util/cs-etm.c
index 509ec014b276..f19aa4f787fa 100644
--- a/tools/perf/util/cs-etm.c
+++ b/tools/perf/util/cs-etm.c
@@ -296,9 +296,6 @@ static int cs_etm__map_trace_id(struct cs_etm_queue *etmq, u8 trace_chan_id,
static struct cs_etm_queue *cs_etm__get_queue(struct cs_etm_auxtrace *etm, int cpu)
{
- if (etm->per_thread_decoding)
- return etm->queues.queue_array[0].priv;
-
if (cpu < 0 || cpu >= (int)etm->queues.nr_queues)
return NULL;
@@ -436,11 +433,7 @@ static int cs_etm__map_unformatted_trace_ids(struct cs_etm_auxtrace *etm)
return -EINVAL;
}
- if (etm->per_thread_decoding)
- cpu_data = etm->metadata[0];
- else
- cpu_data = get_cpu_data(etm, queue->cpu);
-
+ cpu_data = get_cpu_data(etm, queue->cpu);
if (!cpu_data) {
pr_err("CS_ETM: Unformatted queue missing CPU metadata\n");
return -EINVAL;
@@ -1301,7 +1294,7 @@ static int cs_etm__setup_queue(struct cs_etm_auxtrace *etm,
queue->priv = etmq;
etmq->etm = etm;
etmq->queue_nr = queue_nr;
- queue->cpu = queue_nr; /* Placeholder, may be reset to -1 in per-thread mode */
+ queue->cpu = queue_nr;
etmq->offset = 0;
etmq->sink_id = SINK_UNSET;
@@ -2187,6 +2180,7 @@ static void cs_etm__flush_all_stack(struct cs_etm_queue *etmq)
*/
static int cs_etm__get_data_block(struct cs_etm_queue *etmq)
{
+ struct cs_etm_auxtrace *etm = etmq->etm;
int ret;
/* The current block is not finished */
@@ -2215,6 +2209,29 @@ static int cs_etm__get_data_block(struct cs_etm_queue *etmq)
*/
cs_etm__flush_all_stack(etmq);
+ /*
+ * Per-thread mode still uses a queue for each CPU, but that CPU can run
+ * different threads. When the TID from the AUX record on a CPU changes,
+ * re-initialize the thread using the AUX record/buffer fragment TID so
+ * we can start decoding even if the context ID packet was cropped or
+ * they're disabled.
+ */
+ if (etm->per_thread_decoding) {
+ struct cs_etm_traceid_queue *tidq = cs_etm__etmq_get_traceid_queue(etmq,
+ CS_ETM_PER_THREAD_TRACEID);
+
+ if (thread__tid(tidq->decode_thread) != etmq->buffer->tid) {
+ thread__zput(tidq->frontend_thread);
+ thread__zput(tidq->decode_thread);
+ tidq->frontend_thread = machine__findnew_thread(
+ &etm->session->machines.host, -1,
+ etmq->buffer->tid);
+ tidq->decode_thread = machine__findnew_thread(
+ &etm->session->machines.host, -1,
+ etmq->buffer->tid);
+ }
+ }
+
return 1;
}
@@ -3288,7 +3305,9 @@ 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);
+ /* Old per-thread files have no CPU (before v6.4) */
+ int queue_idx = sample->cpu == (u32)-1 ? 0 : sample->cpu;
+ struct cs_etm_queue *etmq = cs_etm__get_queue(etm, queue_idx);
if (!etmq)
return -EINVAL;
@@ -3301,6 +3320,7 @@ static int cs_etm__queue_aux_fragment(struct perf_session *session, off_t file_o
auxtrace_fragment.auxtrace.size = aux_size;
auxtrace_fragment.auxtrace.offset = aux_offset;
auxtrace_fragment.auxtrace.idx = etmq->queue_nr;
+ auxtrace_fragment.auxtrace.cpu = etmq->queue_nr;
file_offset += aux_offset - auxtrace_event->offset + auxtrace_event->header.size;
pr_debug3("CS ETM: Queue buffer size: %#"PRI_lx64" offset: %#"PRI_lx64
--
2.43.0
^ permalink raw reply related [flat|nested] 37+ messages in thread
* [PATCH v3 05/18] perf test: cs-etm: Increase window size in disassembly test
2026-11-02 12:15 [PATCH v3 00/18] perf cs-etm: Per-thread mode fixes and snapshot wrap support James Clark
` (3 preceding siblings ...)
2026-11-02 12:15 ` [PATCH v3 04/18] perf cs-etm: Use per-CPU queues for " James Clark
@ 2026-11-02 12:15 ` James Clark
2026-11-02 12:24 ` sashiko-bot
2026-11-02 12:15 ` [PATCH v3 06/18] perf cs-etm: Don't drop previous target for kernel samples James Clark
` (12 subsequent siblings)
17 siblings, 1 reply; 37+ messages in thread
From: James Clark @ 2026-11-02 12:15 UTC (permalink / raw)
To: Leo Yan, Adrian Hunter
Cc: Suzuki K Poulose, Mike Leach, Leo Yan, Will Deacon,
Peter Zijlstra, Ingo Molnar, Arnaldo Carvalho de Melo,
Namhyung Kim, Mark Rutland, Alexander Shishkin, Jiri Olsa,
Ian Rogers, Mathieu Poirier, Jonathan Corbet, Shuah Khan,
Suyash Mahar, Amir Ayupov, linux-arm-kernel, coresight,
linux-perf-users, linux-kernel, Arnaldo Carvalho de Melo,
linux-doc, James Clark, John Garry
When we increase the default timestamp interval in a later commit, less
trace will be recorded. At the moment the amount of trace overflows the
64K buffer, so the exit snapshot trace is what happened at the end of
the process rather than the beginning. With the new default interval,
the entire execution will be contained in one buffer and the first few
samples are always various instruction and data faults. These go to the
kernel so don't result in ranges that can be disassembled.
Increase the window to 6 samples to ensure something with a branch can
be disassembled even with the fault samples.
Signed-off-by: James Clark <james.clark@linaro.org>
---
tools/perf/tests/shell/coresight/test_arm_coresight_disasm.sh | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/tools/perf/tests/shell/coresight/test_arm_coresight_disasm.sh b/tools/perf/tests/shell/coresight/test_arm_coresight_disasm.sh
index d298b18154b2..236c4160e480 100755
--- a/tools/perf/tests/shell/coresight/test_arm_coresight_disasm.sh
+++ b/tools/perf/tests/shell/coresight/test_arm_coresight_disasm.sh
@@ -50,7 +50,7 @@ if [ "$(id -u)" == 0 ] && [ -e /proc/kcore ]; then
echo "Testing kernel disassembly"
perf record -o ${perfdata} -e cs_etm//k --kcore -Se -m,64K -- touch $file > /dev/null 2>&1
perf script -i ${perfdata} arm-cs-trace-disasm -- \
- -d --stop-sample=2 -k ${perfdata}/kcore_dir/kcore 2> /dev/null > ${file}
+ -d --stop-sample=6 -k ${perfdata}/kcore_dir/kcore 2> /dev/null > ${file}
grep -q -E ${branch_search} ${file}
echo "Found kernel branches"
else
@@ -62,7 +62,7 @@ fi
echo "Testing userspace disassembly"
perf record -o ${perfdata} -e cs_etm//u -Se -m,64K -- touch $file > /dev/null 2>&1
perf script -i ${perfdata} arm-cs-trace-disasm -- \
- -d --stop-sample=2 2> /dev/null > ${file}
+ -d --stop-sample=6 2> /dev/null > ${file}
grep -q -E ${branch_search} ${file}
echo "Found userspace branches"
--
2.43.0
^ permalink raw reply related [flat|nested] 37+ messages in thread
* [PATCH v3 06/18] perf cs-etm: Don't drop previous target for kernel samples
2026-11-02 12:15 [PATCH v3 00/18] perf cs-etm: Per-thread mode fixes and snapshot wrap support James Clark
` (4 preceding siblings ...)
2026-11-02 12:15 ` [PATCH v3 05/18] perf test: cs-etm: Increase window size in disassembly test James Clark
@ 2026-11-02 12:15 ` James Clark
2026-11-02 12:29 ` sashiko-bot
2026-11-02 12:15 ` [PATCH v3 07/18] perf python: Expose sample instruction length James Clark
` (11 subsequent siblings)
17 siblings, 1 reply; 37+ messages in thread
From: James Clark @ 2026-11-02 12:15 UTC (permalink / raw)
To: Leo Yan, Adrian Hunter
Cc: Suzuki K Poulose, Mike Leach, Leo Yan, Will Deacon,
Peter Zijlstra, Ingo Molnar, Arnaldo Carvalho de Melo,
Namhyung Kim, Mark Rutland, Alexander Shishkin, Jiri Olsa,
Ian Rogers, Mathieu Poirier, Jonathan Corbet, Shuah Khan,
Suyash Mahar, Amir Ayupov, linux-arm-kernel, coresight,
linux-perf-users, linux-kernel, Arnaldo Carvalho de Melo,
linux-doc, James Clark, John Garry
The branch target of the previous sample is used as the start of the
range to disassemble, but if that sample's DSO is "[unknown]" then we'll
drop it even though it's useful information. This happens when recording
userspace only. Save the address earlier for "Trace On" samples, before
we get to the early exit condition for unknown DSOs.
In a later commit to change the default timestamp interval, trace will
be collected that triggers this issue, so fix it now.
Signed-off-by: James Clark <james.clark@linaro.org>
---
tools/perf/python/arm-cs-trace-disasm.py | 13 +++++++------
1 file changed, 7 insertions(+), 6 deletions(-)
diff --git a/tools/perf/python/arm-cs-trace-disasm.py b/tools/perf/python/arm-cs-trace-disasm.py
index 1812ccaf4b20..5a7daf927bea 100755
--- a/tools/perf/python/arm-cs-trace-disasm.py
+++ b/tools/perf/python/arm-cs-trace-disasm.py
@@ -52,6 +52,7 @@ DISASM_CACHE: Dict[str, List[str]] = {}
DISASM_RE = re.compile(r"^\s*([0-9a-fA-F]+):")
DISASM_FUNC_RE = re.compile(r"^\s*([0-9a-fA-F]+)\s.*:")
CACHE_SIZE = 1024
+PERF_IP_FLAG_TRACE_BEGIN = 1 << 8
class _State:
sample_idx: int = -1
source_file_name: Optional[str] = None
@@ -252,12 +253,12 @@ class TraceDisasm:
print(f"Event type: {ev_name}")
print_sample(sample)
- # Initialize CPU data if it's empty, and directly return back
- # if this is the first tracing event for this CPU. This must happen
- # before the dso == '[unknown]' check because the initial
- # CS_ETM_TRACE_ON sample has ip == 0 (dso == '[unknown]') and
- # carries the starting target address in sample.sample_addr.
- if self.cpu_data.get(f"{sample.sample_cpu}addr") is None:
+ # Initialize CPU data on the first event and whenever tracing resumes.
+ # Trace-start samples may have no source DSO map, but carry the next
+ # range's starting address in sample.sample_addr. Instruction samples
+ # in the first range also have TRACE_BEGIN set, so only check branches.
+ if (self.cpu_data.get(f"{sample.sample_cpu}addr") is None or
+ ("branches" in ev_name and sample.flags & PERF_IP_FLAG_TRACE_BEGIN)):
self.cpu_data[f"{sample.sample_cpu}addr"] = sample.sample_addr
return
--
2.43.0
^ permalink raw reply related [flat|nested] 37+ messages in thread
* [PATCH v3 07/18] perf python: Expose sample instruction length
2026-11-02 12:15 [PATCH v3 00/18] perf cs-etm: Per-thread mode fixes and snapshot wrap support James Clark
` (5 preceding siblings ...)
2026-11-02 12:15 ` [PATCH v3 06/18] perf cs-etm: Don't drop previous target for kernel samples James Clark
@ 2026-11-02 12:15 ` James Clark
2026-11-02 12:23 ` sashiko-bot
2026-11-02 12:15 ` [PATCH v3 08/18] perf cs-etm: Use instruction length in disassembly script James Clark
` (10 subsequent siblings)
17 siblings, 1 reply; 37+ messages in thread
From: James Clark @ 2026-11-02 12:15 UTC (permalink / raw)
To: Leo Yan, Adrian Hunter
Cc: Suzuki K Poulose, Mike Leach, Leo Yan, Will Deacon,
Peter Zijlstra, Ingo Molnar, Arnaldo Carvalho de Melo,
Namhyung Kim, Mark Rutland, Alexander Shishkin, Jiri Olsa,
Ian Rogers, Mathieu Poirier, Jonathan Corbet, Shuah Khan,
Suyash Mahar, Amir Ayupov, linux-arm-kernel, coresight,
linux-perf-users, linux-kernel, Arnaldo Carvalho de Melo,
linux-doc, James Clark, John Garry
Expose perf_sample::insn_len as a read-only insn_len attribute.
Trace decoders supply instruction lengths on synthesized samples to
indicate whether an instruction was executed or not during a call or
exception. Let scripts read this value directly.
Signed-off-by: James Clark <james.clark@linaro.org>
---
tools/perf/python/perf.pyi | 1 +
tools/perf/util/python.c | 1 +
2 files changed, 2 insertions(+)
diff --git a/tools/perf/python/perf.pyi b/tools/perf/python/perf.pyi
index 087da00728fd..525d34ed08dc 100644
--- a/tools/perf/python/perf.pyi
+++ b/tools/perf/python/perf.pyi
@@ -215,6 +215,7 @@ class sample_event(_sample_members):
sample_data_src: int
sample_insn_count: int
sample_cyc_count: int
+ insn_len: int
type: int
raw_buf: bytes
dso: Optional[str]
diff --git a/tools/perf/util/python.c b/tools/perf/util/python.c
index 60dbe2dad5eb..d2aff058eb89 100644
--- a/tools/perf/util/python.c
+++ b/tools/perf/util/python.c
@@ -1380,6 +1380,7 @@ static PyGetSetDef pyrf_sample_event__getset[] = {
sample_member_def(sample_data_src, data_src, T_ULONGLONG, "event data source"),
sample_member_def(sample_insn_count, insn_cnt, T_ULONGLONG, "event instruction count"),
sample_member_def(sample_cyc_count, cyc_cnt, T_ULONGLONG, "event cycle count"),
+ sample_member_def(insn_len, insn_len, T_USHORT, "event instruction length"),
sample_member_def(flags, flags, T_UINT, "event flags"),
member_def(perf_event_header, type, T_UINT, "event type"),
{
--
2.43.0
^ permalink raw reply related [flat|nested] 37+ messages in thread
* [PATCH v3 08/18] perf cs-etm: Use instruction length in disassembly script
2026-11-02 12:15 [PATCH v3 00/18] perf cs-etm: Per-thread mode fixes and snapshot wrap support James Clark
` (6 preceding siblings ...)
2026-11-02 12:15 ` [PATCH v3 07/18] perf python: Expose sample instruction length James Clark
@ 2026-11-02 12:15 ` James Clark
2026-11-02 12:24 ` sashiko-bot
2026-11-02 12:15 ` [PATCH v3 09/18] perf cs-etm: Increase default timestamp generation period James Clark
` (9 subsequent siblings)
17 siblings, 1 reply; 37+ messages in thread
From: James Clark @ 2026-11-02 12:15 UTC (permalink / raw)
To: Leo Yan, Adrian Hunter
Cc: Suzuki K Poulose, Mike Leach, Leo Yan, Will Deacon,
Peter Zijlstra, Ingo Molnar, Arnaldo Carvalho de Melo,
Namhyung Kim, Mark Rutland, Alexander Shishkin, Jiri Olsa,
Ian Rogers, Mathieu Poirier, Jonathan Corbet, Shuah Khan,
Suyash Mahar, Amir Ayupov, linux-arm-kernel, coresight,
linux-perf-users, linux-kernel, Arnaldo Carvalho de Melo,
linux-doc, James Clark, John Garry
The disassembly script adds four bytes to each branch sample's IP to
include the final instruction in the range. For an exception sample
whose IP identifies a faulting or interrupted instruction, this
incorrectly includes an instruction that did not execute.
Use the decoder-supplied insn_len to calculate the exclusive end
address. This preserves zero-length exception entries, includes executed
syscall instructions and also uses the decoded size for Thumb
instructions.
Signed-off-by: James Clark <james.clark@linaro.org>
---
tools/perf/python/arm-cs-trace-disasm.py | 9 +++++----
1 file changed, 5 insertions(+), 4 deletions(-)
diff --git a/tools/perf/python/arm-cs-trace-disasm.py b/tools/perf/python/arm-cs-trace-disasm.py
index 5a7daf927bea..7cf0af5d2780 100755
--- a/tools/perf/python/arm-cs-trace-disasm.py
+++ b/tools/perf/python/arm-cs-trace-disasm.py
@@ -315,11 +315,12 @@ class TraceDisasm:
# [ sample_prev::addr .. sample_next::ip ]
#
# sample_prev::addr is stored into cpu_data and read back as
- # 'start_addr' when the new packet arrives, and sample_next::ip + 4 is
- # used for 'stop_addr' so objdump includes the branch instruction at
- # sample_next::ip.
+ # 'start_addr' when the new packet arrives. Add sample_next::insn_len
+ # to sample_next::ip so objdump includes the last executed instruction.
+ # The decoder supplies a zero length for an unexecuted instruction at
+ # an exception entry, excluding it from the range.
start_addr = self.cpu_data[str(cpu) + 'addr']
- stop_addr = ip + 4
+ stop_addr = ip + sample.insn_len
# Record for previous sample packet
self.cpu_data[str(cpu) + 'addr'] = addr
--
2.43.0
^ permalink raw reply related [flat|nested] 37+ messages in thread
* [PATCH v3 09/18] perf cs-etm: Increase default timestamp generation period
2026-11-02 12:15 [PATCH v3 00/18] perf cs-etm: Per-thread mode fixes and snapshot wrap support James Clark
` (7 preceding siblings ...)
2026-11-02 12:15 ` [PATCH v3 08/18] perf cs-etm: Use instruction length in disassembly script James Clark
@ 2026-11-02 12:15 ` James Clark
2026-11-02 12:24 ` sashiko-bot
2026-11-02 12:15 ` [PATCH v3 10/18] perf auxtrace: Turn Intel BTS snapshot search into a generic one James Clark
` (8 subsequent siblings)
17 siblings, 1 reply; 37+ messages in thread
From: James Clark @ 2026-11-02 12:15 UTC (permalink / raw)
To: Leo Yan, Adrian Hunter
Cc: Suzuki K Poulose, Mike Leach, Leo Yan, Will Deacon,
Peter Zijlstra, Ingo Molnar, Arnaldo Carvalho de Melo,
Namhyung Kim, Mark Rutland, Alexander Shishkin, Jiri Olsa,
Ian Rogers, Mathieu Poirier, Jonathan Corbet, Shuah Khan,
Suyash Mahar, Amir Ayupov, linux-arm-kernel, coresight,
linux-perf-users, linux-kernel, Arnaldo Carvalho de Melo,
linux-doc, James Clark, John Garry
The original purpose of timestamps in per-CPU mode was to roughly emit
samples in the order they occurred across multiple CPUs, rather than
emit all the trace from CPU 1 followed by all from CPU 2 etc.
Previously there was no way to change the interval from the minimum of 1
cycle, but custom intervals are now supported by the driver.
Drastically reduce the number of timestamps emitted by increasing the
interval, which is more than enough to interleave sample generation.
This reduces the size of the trace generated.
Reviewed-by: Leo Yan <leo.yan@arm.com>
Signed-off-by: James Clark <james.clark@linaro.org>
---
tools/perf/arch/arm/util/cs-etm.c | 7 +++++--
1 file changed, 5 insertions(+), 2 deletions(-)
diff --git a/tools/perf/arch/arm/util/cs-etm.c b/tools/perf/arch/arm/util/cs-etm.c
index 47b4e386cc53..5e7d09b7170c 100644
--- a/tools/perf/arch/arm/util/cs-etm.c
+++ b/tools/perf/arch/arm/util/cs-etm.c
@@ -70,6 +70,9 @@ static const char * const metadata_ete_ro[] = {
enum cs_etm_version { CS_NOT_PRESENT, CS_ETMV3, CS_ETMV4, CS_ETE };
+/* Default timestamp period of 256 cycles (2 ^ (9 - 1) = 256). */
+#define DEFAULT_TS 9
+
static bool cs_etm_is_ete(struct perf_pmu *cs_etm_pmu, struct perf_cpu cpu);
static int cs_etm_get_ro(struct perf_pmu *pmu, struct perf_cpu cpu, const char *path, __u64 *val);
static bool cs_etm_pmu_path_exists(struct perf_pmu *pmu, struct perf_cpu cpu, const char *path);
@@ -458,7 +461,7 @@ static int cs_etm_recording_options(struct auxtrace_record *itr,
* Timestamps are required to interleave samples from different
* CPUs.
*/
- evsel__set_config_if_unset(cs_etm_evsel, "timestamp", 1);
+ evsel__set_config_if_unset(cs_etm_evsel, "timestamp", DEFAULT_TS);
/* Context IDs are required to associate trace to a process */
evsel__set_config_if_unset(cs_etm_evsel, "contextid", 1);
} else {
@@ -481,7 +484,7 @@ static int cs_etm_recording_options(struct auxtrace_record *itr,
* timestamp tracing.
*/
if (opts->sample_time_set)
- evsel__set_config_if_unset(cs_etm_evsel, "timestamp", 1);
+ evsel__set_config_if_unset(cs_etm_evsel, "timestamp", DEFAULT_TS);
/* Add dummy event to keep tracking */
err = parse_event(evlist, "dummy:u");
--
2.43.0
^ permalink raw reply related [flat|nested] 37+ messages in thread
* [PATCH v3 10/18] perf auxtrace: Turn Intel BTS snapshot search into a generic one
2026-11-02 12:15 [PATCH v3 00/18] perf cs-etm: Per-thread mode fixes and snapshot wrap support James Clark
` (8 preceding siblings ...)
2026-11-02 12:15 ` [PATCH v3 09/18] perf cs-etm: Increase default timestamp generation period James Clark
@ 2026-11-02 12:15 ` James Clark
2026-11-02 12:21 ` sashiko-bot
2026-11-02 12:15 ` [PATCH v3 11/18] perf arm-spe: Use generic snapshot search James Clark
` (7 subsequent siblings)
17 siblings, 1 reply; 37+ messages in thread
From: James Clark @ 2026-11-02 12:15 UTC (permalink / raw)
To: Leo Yan, Adrian Hunter
Cc: Suzuki K Poulose, Mike Leach, Leo Yan, Will Deacon,
Peter Zijlstra, Ingo Molnar, Arnaldo Carvalho de Melo,
Namhyung Kim, Mark Rutland, Alexander Shishkin, Jiri Olsa,
Ian Rogers, Mathieu Poirier, Jonathan Corbet, Shuah Khan,
Suyash Mahar, Amir Ayupov, linux-arm-kernel, coresight,
linux-perf-users, linux-kernel, Arnaldo Carvalho de Melo,
linux-doc, James Clark, John Garry
Currently each auxtrace user has to implement both the snapshot search
and the logic to fixup the 'head' and 'old' values after finding a wrap.
IntelPT and BTS have the same fixup logic but different wrap searches.
BTS and Arm SPE have the same search but SPE modifies head differently
(although it probably shouldn’t), and Arm Coresight doesn't have a
search at all, but later it will want the same search and fixup logic.
Add auxtrace_record__default_find_snapshot() as a generic
find_snapshot() implementation that tracers can use without
reimplementing it. It's based on intel_bts_find_snapshot() and the
search includes the caching from it. Split the pointer fixup into
auxtrace_record__snapshot_fixup() so that tracers with their own search,
like Intel PT, can still share it.
No functional changes intended apart from changing the pr_debug3()s
to print hex to match __auxtrace_mmap__read() and no longer leaking
snapshot_refs when expanding the array length.
Signed-off-by: James Clark <james.clark@linaro.org>
---
tools/perf/arch/x86/util/intel-bts.c | 115 +--------------------------------
tools/perf/util/auxtrace.c | 122 ++++++++++++++++++++++++++++++++++-
tools/perf/util/auxtrace.h | 14 ++++
3 files changed, 137 insertions(+), 114 deletions(-)
diff --git a/tools/perf/arch/x86/util/intel-bts.c b/tools/perf/arch/x86/util/intel-bts.c
index d44d568a6d21..2f810ff62a03 100644
--- a/tools/perf/arch/x86/util/intel-bts.c
+++ b/tools/perf/arch/x86/util/intel-bts.c
@@ -34,20 +34,12 @@
#define KiB_MASK(x) (KiB(x) - 1)
#define MiB_MASK(x) (MiB(x) - 1)
-struct intel_bts_snapshot_ref {
- void *ref_buf;
- size_t ref_offset;
- bool wrapped;
-};
-
struct intel_bts_recording {
struct auxtrace_record itr;
struct perf_pmu *intel_bts_pmu;
struct evlist *evlist;
bool snapshot_mode;
size_t snapshot_size;
- int snapshot_ref_cnt;
- struct intel_bts_snapshot_ref *snapshot_refs;
};
struct branch {
@@ -280,46 +272,11 @@ static u64 intel_bts_reference(struct auxtrace_record *itr __maybe_unused)
return rdtsc();
}
-static int intel_bts_alloc_snapshot_refs(struct intel_bts_recording *btsr,
- int idx)
-{
- const size_t sz = sizeof(struct intel_bts_snapshot_ref);
- int cnt = btsr->snapshot_ref_cnt, new_cnt = cnt * 2;
- struct intel_bts_snapshot_ref *refs;
-
- if (!new_cnt)
- new_cnt = 16;
-
- while (new_cnt <= idx)
- new_cnt *= 2;
-
- refs = calloc(new_cnt, sz);
- if (!refs)
- return -ENOMEM;
-
- memcpy(refs, btsr->snapshot_refs, cnt * sz);
-
- btsr->snapshot_refs = refs;
- btsr->snapshot_ref_cnt = new_cnt;
-
- return 0;
-}
-
-static void intel_bts_free_snapshot_refs(struct intel_bts_recording *btsr)
-{
- int i;
-
- for (i = 0; i < btsr->snapshot_ref_cnt; i++)
- zfree(&btsr->snapshot_refs[i].ref_buf);
- zfree(&btsr->snapshot_refs);
-}
-
static void intel_bts_recording_free(struct auxtrace_record *itr)
{
struct intel_bts_recording *btsr =
container_of(itr, struct intel_bts_recording, itr);
- intel_bts_free_snapshot_refs(btsr);
free(btsr);
}
@@ -349,75 +306,6 @@ static int intel_bts_snapshot_finish(struct auxtrace_record *itr)
return -EINVAL;
}
-static bool intel_bts_first_wrap(u64 *data, size_t buf_size)
-{
- int i, a, b;
-
- b = buf_size >> 3;
- a = b - 512;
- if (a < 0)
- a = 0;
-
- for (i = a; i < b; i++) {
- if (data[i])
- return true;
- }
-
- return false;
-}
-
-static int intel_bts_find_snapshot(struct auxtrace_record *itr, int idx,
- struct auxtrace_mmap *mm, unsigned char *data,
- u64 *head, u64 *old)
-{
- struct intel_bts_recording *btsr =
- container_of(itr, struct intel_bts_recording, itr);
- bool wrapped;
- int err;
-
- pr_debug3("%s: mmap index %d old head %zu new head %zu\n",
- __func__, idx, (size_t)*old, (size_t)*head);
-
- if (idx >= btsr->snapshot_ref_cnt) {
- err = intel_bts_alloc_snapshot_refs(btsr, idx);
- if (err)
- goto out_err;
- }
-
- wrapped = btsr->snapshot_refs[idx].wrapped;
- if (!wrapped && intel_bts_first_wrap((u64 *)data, mm->len)) {
- btsr->snapshot_refs[idx].wrapped = true;
- wrapped = true;
- }
-
- /*
- * In full trace mode 'head' continually increases. However in snapshot
- * mode 'head' is an offset within the buffer. Here 'old' and 'head'
- * are adjusted to match the full trace case which expects that 'old' is
- * always less than 'head'.
- */
- if (wrapped) {
- *old = *head;
- *head += mm->len;
- } else {
- if (mm->mask)
- *old &= mm->mask;
- else
- *old %= mm->len;
- if (*old > *head)
- *head += mm->len;
- }
-
- pr_debug3("%s: wrap-around %sdetected, adjusted old head %zu adjusted new head %zu\n",
- __func__, wrapped ? "" : "not ", (size_t)*old, (size_t)*head);
-
- return 0;
-
-out_err:
- pr_err("%s: failed, error %d\n", __func__, err);
- return err;
-}
-
struct auxtrace_record *intel_bts_recording_init(int *err)
{
struct perf_pmu *intel_bts_pmu = perf_pmus__find(INTEL_BTS_PMU_NAME);
@@ -444,7 +332,8 @@ struct auxtrace_record *intel_bts_recording_init(int *err)
btsr->itr.free = intel_bts_recording_free;
btsr->itr.snapshot_start = intel_bts_snapshot_start;
btsr->itr.snapshot_finish = intel_bts_snapshot_finish;
- btsr->itr.find_snapshot = intel_bts_find_snapshot;
+ btsr->itr.find_snapshot = auxtrace_record__default_find_snapshot;
+ btsr->itr.snapshot_search_bytes = 4096;
btsr->itr.parse_snapshot_options = intel_bts_parse_snapshot_options;
btsr->itr.reference = intel_bts_reference;
btsr->itr.read_finish = auxtrace_record__read_finish;
diff --git a/tools/perf/util/auxtrace.c b/tools/perf/util/auxtrace.c
index 8bb491d389a2..c613efd83223 100644
--- a/tools/perf/util/auxtrace.c
+++ b/tools/perf/util/auxtrace.c
@@ -15,6 +15,7 @@
#include <linux/kernel.h>
#include <linux/perf_event.h>
#include <linux/types.h>
+#include <linux/bitmap.h>
#include <linux/bitops.h>
#include <linux/log2.h>
#include <linux/string.h>
@@ -603,8 +604,10 @@ int auxtrace_record__info_fill(struct auxtrace_record *itr,
void auxtrace_record__free(struct auxtrace_record *itr)
{
- if (itr)
+ if (itr) {
+ zfree(&itr->snapshot_wrapped);
itr->free(itr);
+ }
}
int auxtrace_record__snapshot_start(struct auxtrace_record *itr)
@@ -630,6 +633,123 @@ int auxtrace_record__find_snapshot(struct auxtrace_record *itr, int idx,
return 0;
}
+static int auxtrace_alloc_wrap_bitmap(struct auxtrace_record *itr, int idx)
+{
+ int len = itr->snapshot_wrapped_len;
+ unsigned long *new_bitmap;
+ int new_len = len * 2;
+
+ if (!new_len)
+ new_len = BITS_PER_LONG;
+
+ while (new_len <= idx)
+ new_len *= 2;
+
+ new_bitmap = realloc(itr->snapshot_wrapped, bitmap_size(new_len));
+ if (!new_bitmap)
+ return -ENOMEM;
+
+ itr->snapshot_wrapped = new_bitmap;
+ bitmap_clear(itr->snapshot_wrapped, len, new_len - len);
+ itr->snapshot_wrapped_len = new_len;
+
+ return 0;
+}
+
+/*
+ * Returns 1 if non zero data exists within
+ * auxtrace_record::snapshot_search_bytes of the end of the buffer. The result
+ * is cached for each buffer idx so the search is not repeated.
+ *
+ * Writes at the end mean a high chance that trace would have continued past
+ * this search area and wrapped to the beginning. It's not a perfect heuristic,
+ * but it's only to avoid saving mostly empty buffers into the file. A false
+ * positive results in saving up to snapshot_search_bytes zeros before the
+ * actual data, which a decoder should be able to skip over.
+ */
+static int auxtrace_snapshot_has_wrapped(struct auxtrace_record *itr, int idx,
+ unsigned char *data, size_t buf_size)
+{
+ u64 *wide_data = (u64 *)data;
+ s64 i, a, b;
+
+ if (idx >= itr->snapshot_wrapped_len) {
+ int err = auxtrace_alloc_wrap_bitmap(itr, idx);
+
+ if (err)
+ return err;
+ }
+
+ if (test_bit(idx, itr->snapshot_wrapped))
+ return 1;
+
+ b = buf_size / sizeof(u64);
+ a = b - (itr->snapshot_search_bytes / sizeof(u64));
+ if (a < 0)
+ a = 0;
+
+ for (i = a; i < b; i++) {
+ if (wide_data[i]) {
+ __set_bit(idx, itr->snapshot_wrapped);
+ return 1;
+ }
+ }
+
+ return 0;
+}
+
+/*
+ * In full trace mode 'head' continually increases. However in snapshot mode
+ * 'head' is an offset within the buffer. Here 'old' and 'head' are adjusted to
+ * match the full trace case which expects that 'old' is always less than
+ * 'head'. 'wrapped' is the result of the auxtrace_record::find_snapshot()
+ * implementation's search.
+ */
+void auxtrace_record__snapshot_fixup(struct auxtrace_mmap *mm, bool wrapped,
+ u64 *head, u64 *old)
+{
+ if (wrapped) {
+ *old = *head;
+ *head += mm->len;
+ } else {
+ if (mm->mask)
+ *old &= mm->mask;
+ else
+ *old %= mm->len;
+ if (*old > *head)
+ *head += mm->len;
+ }
+
+ pr_debug3("%s: wrap-around %sdetected, adjusted old head 0x%"PRIx64" adjusted new head 0x%"PRIx64"\n",
+ __func__, wrapped ? "" : "not ", *old, *head);
+}
+
+/*
+ * Generic auxtrace_record::find_snapshot() implementation. Searches the last
+ * auxtrace_record::snapshot_search_bytes of the buffer for data to determine
+ * whether it has wrapped, then fixes up 'head' and 'old' accordingly.
+ */
+int auxtrace_record__default_find_snapshot(struct auxtrace_record *itr, int idx,
+ struct auxtrace_mmap *mm,
+ unsigned char *data, u64 *head,
+ u64 *old)
+{
+ int wrapped;
+
+ pr_debug3("%s: mmap index %d old head 0x%"PRIx64" new head 0x%"PRIx64"\n",
+ __func__, idx, *old, *head);
+
+ wrapped = auxtrace_snapshot_has_wrapped(itr, idx, data, mm->len);
+ if (wrapped < 0) {
+ pr_err("%s: failed, error %d\n", __func__, wrapped);
+ return wrapped;
+ }
+
+ auxtrace_record__snapshot_fixup(mm, wrapped, head, old);
+
+ return 0;
+}
+
int auxtrace_record__options(struct auxtrace_record *itr,
struct evlist *evlist,
struct record_opts *opts)
diff --git a/tools/perf/util/auxtrace.h b/tools/perf/util/auxtrace.h
index c29a8444f08e..9d86f8366ad7 100644
--- a/tools/perf/util/auxtrace.h
+++ b/tools/perf/util/auxtrace.h
@@ -388,6 +388,11 @@ struct auxtrace_mmap_params {
* @default_aux_sample_size: default sample size for --aux sample option
* @pmu: associated pmu
* @evlist: selected events list
+ * @snapshot_wrapped_len: number of bits in @snapshot_wrapped
+ * @snapshot_wrapped: bitmap indicating if each aux buffer has wrapped
+ * @snapshot_search_bytes: number of bytes at the end of the buffer that
+ * auxtrace_record__default_find_snapshot() searches
+ * for data to determine if a wrap has occurred
*/
struct auxtrace_record {
int (*recording_options)(struct auxtrace_record *itr,
@@ -413,6 +418,9 @@ struct auxtrace_record {
unsigned int alignment;
unsigned int default_aux_sample_size;
struct evlist *evlist;
+ int snapshot_wrapped_len;
+ unsigned long *snapshot_wrapped;
+ int snapshot_search_bytes;
};
/**
@@ -595,6 +603,12 @@ int auxtrace_record__snapshot_finish(struct auxtrace_record *itr, bool on_exit);
int auxtrace_record__find_snapshot(struct auxtrace_record *itr, int idx,
struct auxtrace_mmap *mm,
unsigned char *data, u64 *head, u64 *old);
+int auxtrace_record__default_find_snapshot(struct auxtrace_record *itr, int idx,
+ struct auxtrace_mmap *mm,
+ unsigned char *data, u64 *head,
+ u64 *old);
+void auxtrace_record__snapshot_fixup(struct auxtrace_mmap *mm, bool wrapped,
+ u64 *head, u64 *old);
u64 auxtrace_record__reference(struct auxtrace_record *itr);
int auxtrace_record__read_finish(struct auxtrace_record *itr, int idx);
--
2.43.0
^ permalink raw reply related [flat|nested] 37+ messages in thread
* [PATCH v3 11/18] perf arm-spe: Use generic snapshot search
2026-11-02 12:15 [PATCH v3 00/18] perf cs-etm: Per-thread mode fixes and snapshot wrap support James Clark
` (9 preceding siblings ...)
2026-11-02 12:15 ` [PATCH v3 10/18] perf auxtrace: Turn Intel BTS snapshot search into a generic one James Clark
@ 2026-11-02 12:15 ` James Clark
2026-11-02 12:23 ` sashiko-bot
2026-11-02 12:15 ` [PATCH v3 12/18] perf auxtrace: intel-pt: Use common snapshot pointer fixup James Clark
` (6 subsequent siblings)
17 siblings, 1 reply; 37+ messages in thread
From: James Clark @ 2026-11-02 12:15 UTC (permalink / raw)
To: Leo Yan, Adrian Hunter
Cc: Suzuki K Poulose, Mike Leach, Leo Yan, Will Deacon,
Peter Zijlstra, Ingo Molnar, Arnaldo Carvalho de Melo,
Namhyung Kim, Mark Rutland, Alexander Shishkin, Jiri Olsa,
Ian Rogers, Mathieu Poirier, Jonathan Corbet, Shuah Khan,
Suyash Mahar, Amir Ayupov, linux-arm-kernel, coresight,
linux-perf-users, linux-kernel, Arnaldo Carvalho de Melo,
linux-doc, James Clark, John Garry
The new generic snapshot search gives a similar result as the
reimplemented version of it here. One slight difference is in the
pointer fixup. SPE has:
*head += mm->len;
*old = *head - mm->len;
Which gives exactly the same result but written in reverse order to the
generic one:
*old = *head;
*head += mm->len;
SPE also had a special fixup cases (if (head >= buffer_size)):
* head > isn't needed because the SPE driver always wraps them, and
__auxtrace_mmap__read() handles that anyway.
* head == buffer_size isn't wrapped by the driver, but it just
indicates 'full' which doesn't mean wrapped any more or less than any
other index in the buffer.
Both of these cases are covered by the 512 byte search.
It also didn't have the special case for old > head for when the wrap
heuristic fails but the pointers showed a wrap had happened.
The other feature lost is that this search only looked from head to the
end of the buffer, rather than always at the last 512 bytes. This was
flawed because once head is close to the end, it's likely it could
contain zero padding from actual SPE data and a wrap would be missed.
It's better to err on the side of caution and mark as a wrap, rather
than trying to optimize by limiting the search from head onwards. This
can mistake old data as evidence of a wrap, but for false positives
[head..mm->len] contains zero data. This should be fine, as zero data
are treated as PAD packets and discarded during decoding.
Reviewed-by: Leo Yan <leo.yan@arm.com>
Signed-off-by: James Clark <james.clark@linaro.org>
---
tools/perf/arch/arm64/util/arm-spe.c | 147 +----------------------------------
1 file changed, 2 insertions(+), 145 deletions(-)
diff --git a/tools/perf/arch/arm64/util/arm-spe.c b/tools/perf/arch/arm64/util/arm-spe.c
index 1ba803a8d9b4..8d6a2bde2892 100644
--- a/tools/perf/arch/arm64/util/arm-spe.c
+++ b/tools/perf/arch/arm64/util/arm-spe.c
@@ -26,7 +26,6 @@
#include "../../../util/record.h"
#include "../../../util/header.h"
#include "../../../util/arm-spe.h"
-#include <tools/libc_compat.h> // reallocarray
#define ARM_SPE_CPU_MAGIC 0x1010101010101010ULL
@@ -37,8 +36,6 @@ struct arm_spe_recording {
struct auxtrace_record itr;
struct perf_pmu *arm_spe_pmu;
struct evlist *evlist;
- int wrapped_cnt;
- bool *wrapped;
};
/* Iterate config list to detect if the "freq" parameter is set */
@@ -497,146 +494,6 @@ static int arm_spe_snapshot_finish(struct auxtrace_record *itr)
return ret;
}
-static int arm_spe_alloc_wrapped_array(struct arm_spe_recording *ptr, int idx)
-{
- bool *wrapped;
- int cnt = ptr->wrapped_cnt, new_cnt, i;
-
- /*
- * No need to allocate, so return early.
- */
- if (idx < cnt)
- return 0;
-
- /*
- * Make ptr->wrapped as big as idx.
- */
- new_cnt = idx + 1;
-
- /*
- * Free'ed in arm_spe_recording_free().
- */
- wrapped = reallocarray(ptr->wrapped, new_cnt, sizeof(bool));
- if (!wrapped)
- return -ENOMEM;
-
- /*
- * init new allocated values.
- */
- for (i = cnt; i < new_cnt; i++)
- wrapped[i] = false;
-
- ptr->wrapped_cnt = new_cnt;
- ptr->wrapped = wrapped;
-
- return 0;
-}
-
-static bool arm_spe_buffer_has_wrapped(unsigned char *buffer,
- size_t buffer_size, u64 head)
-{
- u64 i, watermark;
- u64 *buf = (u64 *)buffer;
- size_t buf_size = buffer_size;
-
- /*
- * Defensively handle the case where head might be continually increasing - if its value is
- * equal or greater than the size of the ring buffer, then we can safely determine it has
- * wrapped around. Otherwise, continue to detect if head might have wrapped.
- */
- if (head >= buffer_size)
- return true;
-
- /*
- * We want to look the very last 512 byte (chosen arbitrarily) in the ring buffer.
- */
- watermark = buf_size - 512;
-
- /*
- * The value of head is somewhere within the size of the ring buffer. This can be that there
- * hasn't been enough data to fill the ring buffer yet or the trace time was so long that
- * head has numerically wrapped around. To find we need to check if we have data at the
- * very end of the ring buffer. We can reliably do this because mmap'ed pages are zeroed
- * out and there is a fresh mapping with every new session.
- */
-
- /*
- * head is less than 512 byte from the end of the ring buffer.
- */
- if (head > watermark)
- watermark = head;
-
- /*
- * Speed things up by using 64 bit transactions (see "u64 *buf" above)
- */
- watermark /= sizeof(u64);
- buf_size /= sizeof(u64);
-
- /*
- * If we find trace data at the end of the ring buffer, head has been there and has
- * numerically wrapped around at least once.
- */
- for (i = watermark; i < buf_size; i++)
- if (buf[i])
- return true;
-
- return false;
-}
-
-static int arm_spe_find_snapshot(struct auxtrace_record *itr, int idx,
- struct auxtrace_mmap *mm, unsigned char *data,
- u64 *head, u64 *old)
-{
- int err;
- bool wrapped;
- struct arm_spe_recording *ptr =
- container_of(itr, struct arm_spe_recording, itr);
-
- /*
- * Allocate memory to keep track of wrapping if this is the first
- * time we deal with this *mm.
- */
- if (idx >= ptr->wrapped_cnt) {
- err = arm_spe_alloc_wrapped_array(ptr, idx);
- if (err)
- return err;
- }
-
- /*
- * Check to see if *head has wrapped around. If it hasn't only the
- * amount of data between *head and *old is snapshot'ed to avoid
- * bloating the perf.data file with zeros. But as soon as *head has
- * wrapped around the entire size of the AUX ring buffer it taken.
- */
- wrapped = ptr->wrapped[idx];
- if (!wrapped && arm_spe_buffer_has_wrapped(data, mm->len, *head)) {
- wrapped = true;
- ptr->wrapped[idx] = true;
- }
-
- pr_debug3("%s: mmap index %d old head %zu new head %zu size %zu\n",
- __func__, idx, (size_t)*old, (size_t)*head, mm->len);
-
- /*
- * No wrap has occurred, we can just use *head and *old.
- */
- if (!wrapped)
- return 0;
-
- /*
- * *head has wrapped around - adjust *head and *old to pickup the
- * entire content of the AUX buffer.
- */
- if (*head >= mm->len) {
- *old = *head - mm->len;
- } else {
- *head += mm->len;
- *old = *head - mm->len;
- }
-
- return 0;
-}
-
static u64 arm_spe_reference(struct auxtrace_record *itr __maybe_unused)
{
struct timespec ts;
@@ -651,7 +508,6 @@ static void arm_spe_recording_free(struct auxtrace_record *itr)
struct arm_spe_recording *sper =
container_of(itr, struct arm_spe_recording, itr);
- zfree(&sper->wrapped);
free(sper);
}
@@ -674,7 +530,8 @@ struct auxtrace_record *arm_spe_recording_init(int *err,
sper->arm_spe_pmu = arm_spe_pmu;
sper->itr.snapshot_start = arm_spe_snapshot_start;
sper->itr.snapshot_finish = arm_spe_snapshot_finish;
- sper->itr.find_snapshot = arm_spe_find_snapshot;
+ sper->itr.find_snapshot = auxtrace_record__default_find_snapshot;
+ sper->itr.snapshot_search_bytes = 512;
sper->itr.parse_snapshot_options = arm_spe_parse_snapshot_options;
sper->itr.recording_options = arm_spe_recording_options;
sper->itr.info_priv_size = arm_spe_info_priv_size;
--
2.43.0
^ permalink raw reply related [flat|nested] 37+ messages in thread
* [PATCH v3 12/18] perf auxtrace: intel-pt: Use common snapshot pointer fixup
2026-11-02 12:15 [PATCH v3 00/18] perf cs-etm: Per-thread mode fixes and snapshot wrap support James Clark
` (10 preceding siblings ...)
2026-11-02 12:15 ` [PATCH v3 11/18] perf arm-spe: Use generic snapshot search James Clark
@ 2026-11-02 12:15 ` James Clark
2026-11-02 12:25 ` sashiko-bot
2026-11-02 12:15 ` [PATCH v3 13/18] perf cs-etm: Queue partial AUX records James Clark
` (5 subsequent siblings)
17 siblings, 1 reply; 37+ messages in thread
From: James Clark @ 2026-11-02 12:15 UTC (permalink / raw)
To: Leo Yan, Adrian Hunter
Cc: Suzuki K Poulose, Mike Leach, Leo Yan, Will Deacon,
Peter Zijlstra, Ingo Molnar, Arnaldo Carvalho de Melo,
Namhyung Kim, Mark Rutland, Alexander Shishkin, Jiri Olsa,
Ian Rogers, Mathieu Poirier, Jonathan Corbet, Shuah Khan,
Suyash Mahar, Amir Ayupov, linux-arm-kernel, coresight,
linux-perf-users, linux-kernel, Arnaldo Carvalho de Melo,
linux-doc, James Clark, John Garry
Intel PT has its own wrap search but the same pointer fixup logic as the
generic auxtrace_record__default_find_snapshot(). Replace the duplicate
code with auxtrace_record__snapshot_fixup().
No functional changes intended.
Signed-off-by: James Clark <james.clark@linaro.org>
---
tools/perf/arch/x86/util/intel-pt.c | 21 +--------------------
1 file changed, 1 insertion(+), 20 deletions(-)
diff --git a/tools/perf/arch/x86/util/intel-pt.c b/tools/perf/arch/x86/util/intel-pt.c
index a533114c0048..ce854209213d 100644
--- a/tools/perf/arch/x86/util/intel-pt.c
+++ b/tools/perf/arch/x86/util/intel-pt.c
@@ -1150,26 +1150,7 @@ static int intel_pt_find_snapshot(struct auxtrace_record *itr, int idx,
}
}
- /*
- * In full trace mode 'head' continually increases. However in snapshot
- * mode 'head' is an offset within the buffer. Here 'old' and 'head'
- * are adjusted to match the full trace case which expects that 'old' is
- * always less than 'head'.
- */
- if (wrapped) {
- *old = *head;
- *head += mm->len;
- } else {
- if (mm->mask)
- *old &= mm->mask;
- else
- *old %= mm->len;
- if (*old > *head)
- *head += mm->len;
- }
-
- pr_debug3("%s: wrap-around %sdetected, adjusted old head %zu adjusted new head %zu\n",
- __func__, wrapped ? "" : "not ", (size_t)*old, (size_t)*head);
+ auxtrace_record__snapshot_fixup(mm, wrapped, head, old);
return 0;
--
2.43.0
^ permalink raw reply related [flat|nested] 37+ messages in thread
* [PATCH v3 13/18] perf cs-etm: Queue partial AUX records
2026-11-02 12:15 [PATCH v3 00/18] perf cs-etm: Per-thread mode fixes and snapshot wrap support James Clark
` (11 preceding siblings ...)
2026-11-02 12:15 ` [PATCH v3 12/18] perf auxtrace: intel-pt: Use common snapshot pointer fixup James Clark
@ 2026-11-02 12:15 ` James Clark
2026-11-02 12:32 ` sashiko-bot
2026-11-02 12:15 ` [PATCH v3 14/18] perf cs-etm: Don't print missing buffers in snapshot mode James Clark
` (4 subsequent siblings)
17 siblings, 1 reply; 37+ messages in thread
From: James Clark @ 2026-11-02 12:15 UTC (permalink / raw)
To: Leo Yan, Adrian Hunter
Cc: Suzuki K Poulose, Mike Leach, Leo Yan, Will Deacon,
Peter Zijlstra, Ingo Molnar, Arnaldo Carvalho de Melo,
Namhyung Kim, Mark Rutland, Alexander Shishkin, Jiri Olsa,
Ian Rogers, Mathieu Poirier, Jonathan Corbet, Shuah Khan,
Suyash Mahar, Amir Ayupov, linux-arm-kernel, coresight,
linux-perf-users, linux-kernel, Arnaldo Carvalho de Melo,
linux-doc, James Clark, John Garry
In snapshot mode AUX records may straddle the boundary of multiple trace
buffers. Currently we only queue AUX records whose range is contained
wholly within a single buffer.
Fix it by queuing any partial AUX range that overlaps any buffer.
Reviewed-by: Leo Yan <leo.yan@arm.com>
Signed-off-by: James Clark <james.clark@linaro.org>
---
tools/perf/util/cs-etm.c | 54 +++++++++++++++++++++++++++---------------------
1 file changed, 30 insertions(+), 24 deletions(-)
diff --git a/tools/perf/util/cs-etm.c b/tools/perf/util/cs-etm.c
index f19aa4f787fa..e899d82b7c93 100644
--- a/tools/perf/util/cs-etm.c
+++ b/tools/perf/util/cs-etm.c
@@ -3238,7 +3238,8 @@ static int cs_etm__queue_aux_fragment(struct perf_session *session, off_t file_o
union perf_event *auxtrace_event_union;
struct perf_record_auxtrace *auxtrace_event;
union perf_event auxtrace_fragment;
- __u64 aux_offset, aux_size;
+ __u64 aux_start, aux_end, auxtrace_start, auxtrace_end;
+ __u64 frag_start, frag_end, frag_size;
enum cs_etm_format format;
struct cs_etm_auxtrace *etm = container_of(session->auxtrace,
@@ -3286,25 +3287,23 @@ static int cs_etm__queue_aux_fragment(struct perf_session *session, off_t file_o
}
if (aux_event->flags & PERF_AUX_FLAG_OVERWRITE) {
- /*
- * Clamp size in snapshot mode. The buffer size is clamped in
- * __auxtrace_mmap__read() for snapshots, so the aux record size doesn't reflect
- * the buffer size.
- */
- aux_size = min(aux_event->aux_size, auxtrace_event->size);
-
/*
* In this mode, the head also points to the end of the buffer so aux_offset
* needs to have the size subtracted so it points to the beginning as in normal mode
*/
- aux_offset = aux_event->aux_offset - aux_size;
+ aux_start = aux_event->aux_offset - aux_event->aux_size;
+ aux_end = aux_event->aux_offset;
} else {
- aux_size = aux_event->aux_size;
- aux_offset = aux_event->aux_offset;
+ aux_start = aux_event->aux_offset;
+ aux_end = aux_event->aux_offset + aux_event->aux_size;
}
- if (aux_offset >= auxtrace_event->offset &&
- aux_offset + aux_size <= auxtrace_event->offset + auxtrace_event->size) {
+ auxtrace_start = auxtrace_event->offset;
+ auxtrace_end = auxtrace_start + auxtrace_event->size;
+ frag_start = max(aux_start, auxtrace_start);
+ frag_end = min(aux_end, auxtrace_end);
+
+ if (frag_start < frag_end) {
/* Old per-thread files have no CPU (before v6.4) */
int queue_idx = sample->cpu == (u32)-1 ? 0 : sample->cpu;
struct cs_etm_queue *etmq = cs_etm__get_queue(etm, queue_idx);
@@ -3313,18 +3312,19 @@ static int cs_etm__queue_aux_fragment(struct perf_session *session, off_t file_o
return -EINVAL;
/*
- * 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.
+ * If this AUX event overlaps this buffer, create a new auxtrace event
+ * for the overlapping range and queue that fragment.
*/
+ frag_size = frag_end - frag_start;
auxtrace_fragment.auxtrace = *auxtrace_event;
- auxtrace_fragment.auxtrace.size = aux_size;
- auxtrace_fragment.auxtrace.offset = aux_offset;
+ auxtrace_fragment.auxtrace.size = frag_size;
+ auxtrace_fragment.auxtrace.offset = frag_start;
auxtrace_fragment.auxtrace.idx = etmq->queue_nr;
auxtrace_fragment.auxtrace.cpu = etmq->queue_nr;
- file_offset += aux_offset - auxtrace_event->offset + auxtrace_event->header.size;
+ file_offset += frag_start - auxtrace_start + auxtrace_event->header.size;
pr_debug3("CS ETM: Queue buffer size: %#"PRI_lx64" offset: %#"PRI_lx64
- " tid: %d cpu: %d\n", aux_size, aux_offset, sample->tid, sample->cpu);
+ " tid: %d cpu: %d\n", frag_size, frag_start, sample->tid, sample->cpu);
err = auxtrace_queues__add_event(&etm->queues, session, &auxtrace_fragment,
file_offset, NULL);
if (err)
@@ -3364,6 +3364,7 @@ static int cs_etm__queue_aux_records_cb(struct perf_session *session, union perf
struct auxtrace_index *auxtrace_index;
struct evsel *evsel;
size_t i;
+ bool found = false;
/* Don't care about any other events, we're only queuing buffers for AUX events */
if (event->header.type != PERF_RECORD_AUX)
@@ -3397,11 +3398,14 @@ static int cs_etm__queue_aux_records_cb(struct perf_session *session, union perf
ret = cs_etm__queue_aux_fragment(session, ent->file_offset,
ent->sz, &event->aux, &sample);
/*
- * Stop search on error or successful values. Continue search on
- * 1 ('not found')
+ * Stop search on error values. Continue search on
+ * 1 ('not found') or 0 ('queued')
*/
- if (ret != 1)
+ if (ret < 0)
goto out;
+
+ if (!ret)
+ found = true;
}
}
@@ -3409,8 +3413,10 @@ static int cs_etm__queue_aux_records_cb(struct perf_session *session, union perf
* Couldn't find the buffer corresponding to this aux record, something went wrong. Warn but
* don't exit with an error because it will still be possible to decode other aux records.
*/
- pr_err("CS ETM: Couldn't find auxtrace buffer for aux_offset: %#"PRI_lx64
- " tid: %d cpu: %d\n", event->aux.aux_offset, sample.tid, sample.cpu);
+ if (!found) {
+ pr_err("CS ETM: Couldn't find auxtrace buffer for aux_offset: %#"PRI_lx64
+ " tid: %d cpu: %d\n", event->aux.aux_offset, sample.tid, sample.cpu);
+ }
ret = 0;
out:
perf_sample__exit(&sample);
--
2.43.0
^ permalink raw reply related [flat|nested] 37+ messages in thread
* [PATCH v3 14/18] perf cs-etm: Don't print missing buffers in snapshot mode
2026-11-02 12:15 [PATCH v3 00/18] perf cs-etm: Per-thread mode fixes and snapshot wrap support James Clark
` (12 preceding siblings ...)
2026-11-02 12:15 ` [PATCH v3 13/18] perf cs-etm: Queue partial AUX records James Clark
@ 2026-11-02 12:15 ` James Clark
2026-11-02 12:26 ` sashiko-bot
2026-11-02 12:15 ` [PATCH v3 15/18] perf auxtrace: cs-etm: Capture wrapped snapshots James Clark
` (3 subsequent siblings)
17 siblings, 1 reply; 37+ messages in thread
From: James Clark @ 2026-11-02 12:15 UTC (permalink / raw)
To: Leo Yan, Adrian Hunter
Cc: Suzuki K Poulose, Mike Leach, Leo Yan, Will Deacon,
Peter Zijlstra, Ingo Molnar, Arnaldo Carvalho de Melo,
Namhyung Kim, Mark Rutland, Alexander Shishkin, Jiri Olsa,
Ian Rogers, Mathieu Poirier, Jonathan Corbet, Shuah Khan,
Suyash Mahar, Amir Ayupov, linux-arm-kernel, coresight,
linux-perf-users, linux-kernel, Arnaldo Carvalho de Melo,
linux-doc, James Clark, John Garry
The driver produces lots of AUX records for trace data that was never
captured in snapshot mode. Downgrade this to a pr_debug3 message as it's
only an error in normal mode.
Reviewed-by: Leo Yan <leo.yan@arm.com>
Signed-off-by: James Clark <james.clark@linaro.org>
---
tools/perf/util/cs-etm.c | 9 +++++++--
1 file changed, 7 insertions(+), 2 deletions(-)
diff --git a/tools/perf/util/cs-etm.c b/tools/perf/util/cs-etm.c
index e899d82b7c93..c880a1ca7533 100644
--- a/tools/perf/util/cs-etm.c
+++ b/tools/perf/util/cs-etm.c
@@ -3361,6 +3361,9 @@ static int cs_etm__queue_aux_records_cb(struct perf_session *session, union perf
struct perf_sample sample;
int ret;
struct auxtrace_index_entry *ent;
+ struct cs_etm_auxtrace *etm = container_of(session->auxtrace,
+ struct cs_etm_auxtrace,
+ auxtrace);
struct auxtrace_index *auxtrace_index;
struct evsel *evsel;
size_t i;
@@ -3414,8 +3417,10 @@ static int cs_etm__queue_aux_records_cb(struct perf_session *session, union perf
* don't exit with an error because it will still be possible to decode other aux records.
*/
if (!found) {
- pr_err("CS ETM: Couldn't find auxtrace buffer for aux_offset: %#"PRI_lx64
- " tid: %d cpu: %d\n", event->aux.aux_offset, sample.tid, sample.cpu);
+ int level = etm->snapshot_mode ? 3 : 0;
+
+ pr_debugN(level, "CS ETM: Couldn't find auxtrace buffer for aux_offset: %#"PRI_lx64
+ " tid: %d cpu: %d\n", event->aux.aux_offset, sample.tid, sample.cpu);
}
ret = 0;
out:
--
2.43.0
^ permalink raw reply related [flat|nested] 37+ messages in thread
* [PATCH v3 15/18] perf auxtrace: cs-etm: Capture wrapped snapshots
2026-11-02 12:15 [PATCH v3 00/18] perf cs-etm: Per-thread mode fixes and snapshot wrap support James Clark
` (13 preceding siblings ...)
2026-11-02 12:15 ` [PATCH v3 14/18] perf cs-etm: Don't print missing buffers in snapshot mode James Clark
@ 2026-11-02 12:15 ` James Clark
2026-11-02 12:28 ` sashiko-bot
2026-11-02 12:15 ` [PATCH v3 16/18] perf test: Allow infinite named_thread loops James Clark
` (2 subsequent siblings)
17 siblings, 1 reply; 37+ messages in thread
From: James Clark @ 2026-11-02 12:15 UTC (permalink / raw)
To: Leo Yan, Adrian Hunter
Cc: Suzuki K Poulose, Mike Leach, Leo Yan, Will Deacon,
Peter Zijlstra, Ingo Molnar, Arnaldo Carvalho de Melo,
Namhyung Kim, Mark Rutland, Alexander Shishkin, Jiri Olsa,
Ian Rogers, Mathieu Poirier, Jonathan Corbet, Shuah Khan,
Suyash Mahar, Amir Ayupov, linux-arm-kernel, coresight,
linux-perf-users, linux-kernel, Arnaldo Carvalho de Melo,
linux-doc, James Clark, John Garry
find_snapshot() isn't implemented for Coresight, so when a wrap occurs
we only save from offset 0 to current head, modulo buffer size, rather
than capturing the whole buffer. Coresight drivers currently use a
monotonic head pointer even in snapshot mode, which gives a lot more
information for taking snapshots than a wrapped pointer. It means we
don't need to search for data at the end of the buffer to guess if a
wrap has occurred, and we can go back to saving partial buffers after a
wrap by checking if the next size to take is less than the buffer
size.
The drivers will always be able to generate a monotonic head because
TRBE has a bytes written counter, so even if we stop generating an AUX
record on every overflow, we can check if the counter indicated an
overwrite and offset head by a whole buffer size if it wrapped. This
forces userspace to read the whole buffer rather than between last and
current head. ETR and other sinks use software double buffering, so can
continue as they are.
Add auxtrace_record__monotonic_find_snapshot() and use it for Coresight.
A monotonic head can't be auto-detected in the default find_snapshot()
by checking if head > buffer size, because before the first wrap a
monotonic head looks the same as a wrapped one. The search would then
falsely detect a wrap when head is near the end of the buffer, and the
wrapped pointer fixup moves 'old' and 'head' a whole buffer ahead of the
driver's head. With a wrapped head that doesn't matter because only the
offset within the buffer is used, but with a monotonic head the next
snapshot would either save already saved data again or drop up to a
buffer's worth of new data.
Signed-off-by: James Clark <james.clark@linaro.org>
---
tools/perf/arch/arm/util/cs-etm.c | 1 +
.../perf/tests/shell/coresight/raw_dump_stress.sh | 5 -----
tools/perf/util/auxtrace.c | 23 ++++++++++++++++++++++
tools/perf/util/auxtrace.h | 4 ++++
4 files changed, 28 insertions(+), 5 deletions(-)
diff --git a/tools/perf/arch/arm/util/cs-etm.c b/tools/perf/arch/arm/util/cs-etm.c
index 5e7d09b7170c..84a5d7392a5f 100644
--- a/tools/perf/arch/arm/util/cs-etm.c
+++ b/tools/perf/arch/arm/util/cs-etm.c
@@ -922,6 +922,7 @@ struct auxtrace_record *cs_etm_record_init(int *err)
ptr->itr.info_fill = cs_etm_info_fill;
ptr->itr.snapshot_start = cs_etm_snapshot_start;
ptr->itr.snapshot_finish = cs_etm_snapshot_finish;
+ ptr->itr.find_snapshot = auxtrace_record__monotonic_find_snapshot;
ptr->itr.reference = cs_etm_reference;
ptr->itr.free = cs_etm_recording_free;
ptr->itr.read_finish = auxtrace_record__read_finish;
diff --git a/tools/perf/tests/shell/coresight/raw_dump_stress.sh b/tools/perf/tests/shell/coresight/raw_dump_stress.sh
index bea70d825596..c9459ded4cbe 100755
--- a/tools/perf/tests/shell/coresight/raw_dump_stress.sh
+++ b/tools/perf/tests/shell/coresight/raw_dump_stress.sh
@@ -48,11 +48,6 @@ read -r size offset last_idx <<< "$(awk '
# everything was dumped. Allow 48 bytes difference to cover 3 frames: current
# frame length, a partial frame and a final empty one, all of which aren't
# dumped.
-#
-# TODO: for a single snapshot, offset should always be zero. However, we
-# currently output AUX records in snapshot mode when we shouldn't, which
-# increments the offset. Allow for that until it's fixed so we can test raw
-# dumping.
decode_size=$((1 + last_idx - offset))
if [ "$decode_size" -gt "$((size - 48))" ] && [ "$decode_size" -le "$((size))" ]; then
echo "PASS: AUXTRACE buffer length matches dumped packet index"
diff --git a/tools/perf/util/auxtrace.c b/tools/perf/util/auxtrace.c
index c613efd83223..3aabf2f4bd47 100644
--- a/tools/perf/util/auxtrace.c
+++ b/tools/perf/util/auxtrace.c
@@ -750,6 +750,29 @@ int auxtrace_record__default_find_snapshot(struct auxtrace_record *itr, int idx,
return 0;
}
+/*
+ * auxtrace_record::find_snapshot() implementation for drivers that don't wrap
+ * 'head' in snapshot mode, so it's already monotonic like in full trace mode.
+ * No search is needed, only force a full buffer read if more than a buffer's
+ * worth of data was written since the last read.
+ */
+int auxtrace_record__monotonic_find_snapshot(struct auxtrace_record *itr __maybe_unused,
+ int idx, struct auxtrace_mmap *mm,
+ unsigned char *data __maybe_unused,
+ u64 *head, u64 *old)
+{
+ pr_debug3("%s: mmap index %d old head 0x%"PRIx64" new head 0x%"PRIx64"\n",
+ __func__, idx, *old, *head);
+
+ if (*head - *old > mm->len) {
+ *old = *head - mm->len;
+ pr_debug3("%s: wrap-around detected, adjusted old head 0x%"PRIx64"\n",
+ __func__, *old);
+ }
+
+ return 0;
+}
+
int auxtrace_record__options(struct auxtrace_record *itr,
struct evlist *evlist,
struct record_opts *opts)
diff --git a/tools/perf/util/auxtrace.h b/tools/perf/util/auxtrace.h
index 9d86f8366ad7..980acb2dc687 100644
--- a/tools/perf/util/auxtrace.h
+++ b/tools/perf/util/auxtrace.h
@@ -607,6 +607,10 @@ int auxtrace_record__default_find_snapshot(struct auxtrace_record *itr, int idx,
struct auxtrace_mmap *mm,
unsigned char *data, u64 *head,
u64 *old);
+int auxtrace_record__monotonic_find_snapshot(struct auxtrace_record *itr,
+ int idx, struct auxtrace_mmap *mm,
+ unsigned char *data, u64 *head,
+ u64 *old);
void auxtrace_record__snapshot_fixup(struct auxtrace_mmap *mm, bool wrapped,
u64 *head, u64 *old);
u64 auxtrace_record__reference(struct auxtrace_record *itr);
--
2.43.0
^ permalink raw reply related [flat|nested] 37+ messages in thread
* [PATCH v3 16/18] perf test: Allow infinite named_thread loops
2026-11-02 12:15 [PATCH v3 00/18] perf cs-etm: Per-thread mode fixes and snapshot wrap support James Clark
` (14 preceding siblings ...)
2026-11-02 12:15 ` [PATCH v3 15/18] perf auxtrace: cs-etm: Capture wrapped snapshots James Clark
@ 2026-11-02 12:15 ` James Clark
2026-11-02 12:27 ` sashiko-bot
2026-11-02 12:15 ` [PATCH v3 17/18] perf test: cs-etm: Add test for per-thread mode James Clark
2026-11-02 12:15 ` [PATCH v3 18/18] perf test: cs-etm: Test multiple per-thread threads James Clark
17 siblings, 1 reply; 37+ messages in thread
From: James Clark @ 2026-11-02 12:15 UTC (permalink / raw)
To: Leo Yan, Adrian Hunter
Cc: Suzuki K Poulose, Mike Leach, Leo Yan, Will Deacon,
Peter Zijlstra, Ingo Molnar, Arnaldo Carvalho de Melo,
Namhyung Kim, Mark Rutland, Alexander Shishkin, Jiri Olsa,
Ian Rogers, Mathieu Poirier, Jonathan Corbet, Shuah Khan,
Suyash Mahar, Amir Ayupov, linux-arm-kernel, coresight,
linux-perf-users, linux-kernel, Arnaldo Carvalho de Melo,
linux-doc, James Clark, John Garry
The following test will want this to run forever, so add support by
treating 0 loops as infinite.
Convert i and named_threads_work to be unsigned to avoid undefined
signed overflow behavior in infinite loop mode.
Signed-off-by: James Clark <james.clark@linaro.org>
---
tools/perf/Documentation/perf-test.txt | 2 +-
tools/perf/tests/workloads/named_threads.c | 7 ++++---
2 files changed, 5 insertions(+), 4 deletions(-)
diff --git a/tools/perf/Documentation/perf-test.txt b/tools/perf/Documentation/perf-test.txt
index f66ccd77bfe7..9fbfe7819a61 100644
--- a/tools/perf/Documentation/perf-test.txt
+++ b/tools/perf/Documentation/perf-test.txt
@@ -98,7 +98,7 @@ OPTIONS
nrloops: brstack, context_switch_loop
'named_threads' accepts the number of threads and the number of loops to
- do in each thread.
+ do in each thread. Use 0 for an infinite loop.
The datasym, landlock, deterministic and callchain workloads don't accept
any.
diff --git a/tools/perf/tests/workloads/named_threads.c b/tools/perf/tests/workloads/named_threads.c
index d051d41a3cfe..6672c2f77684 100644
--- a/tools/perf/tests/workloads/named_threads.c
+++ b/tools/perf/tests/workloads/named_threads.c
@@ -11,7 +11,7 @@
#define MAX_THREADS 25
static int iterations = 500;
-int named_threads_work = 1234;
+unsigned int named_threads_work = 1234;
typedef void *(*thread_fn_t)(void *);
@@ -19,7 +19,8 @@ typedef void *(*thread_fn_t)(void *);
noinline void *named_threads_thread##n(void *arg __maybe_unused) \
{ \
pthread_setname_np(pthread_self(), "thread" #n); \
- for (int i = 0; i < iterations; i++) \
+ for (unsigned int i = 0; \
+ i < (unsigned int) iterations || iterations == 0; i++) \
named_threads_work += 3; \
\
return NULL; \
@@ -65,7 +66,7 @@ static thread_fn_t thread_fns[MAX_THREADS] = {
/*
* Creates argv[0] threads that run a unique function named "thread[x]" which performs
- * a multiplication in a loop for argv[1] loops.
+ * a multiplication in a loop for argv[1] loops (use 0 for infinite loop).
*/
static int named_threads(int argc, const char **argv)
{
--
2.43.0
^ permalink raw reply related [flat|nested] 37+ messages in thread
* [PATCH v3 17/18] perf test: cs-etm: Add test for per-thread mode
2026-11-02 12:15 [PATCH v3 00/18] perf cs-etm: Per-thread mode fixes and snapshot wrap support James Clark
` (15 preceding siblings ...)
2026-11-02 12:15 ` [PATCH v3 16/18] perf test: Allow infinite named_thread loops James Clark
@ 2026-11-02 12:15 ` James Clark
2026-11-02 12:32 ` sashiko-bot
2026-11-02 12:15 ` [PATCH v3 18/18] perf test: cs-etm: Test multiple per-thread threads James Clark
17 siblings, 1 reply; 37+ messages in thread
From: James Clark @ 2026-11-02 12:15 UTC (permalink / raw)
To: Leo Yan, Adrian Hunter
Cc: Suzuki K Poulose, Mike Leach, Leo Yan, Will Deacon,
Peter Zijlstra, Ingo Molnar, Arnaldo Carvalho de Melo,
Namhyung Kim, Mark Rutland, Alexander Shishkin, Jiri Olsa,
Ian Rogers, Mathieu Poirier, Jonathan Corbet, Shuah Khan,
Suyash Mahar, Amir Ayupov, linux-arm-kernel, coresight,
linux-perf-users, linux-kernel, Arnaldo Carvalho de Melo,
linux-doc, James Clark, John Garry
Test that per-thread mode works when the process migrates across CPUs
and that the decode happens in the correct order.
A TODO is left in for when support for decoding in order with Perf
records is added.
Signed-off-by: James Clark <james.clark@linaro.org>
---
tools/perf/tests/shell/coresight/per-thread.sh | 48 ++++++++++++++++++++++++++
1 file changed, 48 insertions(+)
diff --git a/tools/perf/tests/shell/coresight/per-thread.sh b/tools/perf/tests/shell/coresight/per-thread.sh
new file mode 100755
index 000000000000..8a1ab9d8f1e7
--- /dev/null
+++ b/tools/perf/tests/shell/coresight/per-thread.sh
@@ -0,0 +1,48 @@
+#!/bin/bash -e
+# CoreSight per-thread CPU attribution (exclusive)
+
+# SPDX-License-Identifier: GPL-2.0
+
+# If CoreSight is not available, skip the test
+perf list pmu | grep -q cs_etm || exit 2
+
+if ! tmpdir=$(mktemp -d /tmp/perf-cs-per-thread-test.XXXXXX); then
+ echo "mktemp failed"
+ exit 1
+fi
+
+cleanup_files()
+{
+ rm -rf "$tmpdir"
+ trap - EXIT TERM INT
+}
+
+trap cleanup_files EXIT
+trap 'cleanup_files; exit 1' TERM INT
+
+echo "Recording..."
+perf record -o "$tmpdir/data" -e cs_etm//u --per-thread -- \
+ taskset --cpu-list 0 taskset --cpu-list 1 taskset --cpu-list 2 true > /dev/null 2>&1
+
+echo "Decoding..."
+perf script -i "$tmpdir/data" --itrace=b -F comm,cpu 2> /dev/null | \
+ grep -Eo '(taskset|true).*(\[[0-9]+\])' | \
+ uniq | tail -n 3 > "$tmpdir/script" 2>/dev/null
+
+# Check that the decode says it ran on CPU 0, 1, 2, in that order. TODO: The
+# correct result should be "taskset [0,1,2], true 2" but we don't decode trace
+# in order of the Perf events yet, so everything is associated with the last
+# exec.
+cat > "$tmpdir/expected" << EOF
+true [000]
+true [001]
+true [002]
+EOF
+
+if ! diff -q "$tmpdir/script" "$tmpdir/expected"; then
+ echo "FAIL: per-thread output doesn't match expected:"
+ cat "$tmpdir/script"
+ exit 1
+fi
+
+exit 0
--
2.43.0
^ permalink raw reply related [flat|nested] 37+ messages in thread
* [PATCH v3 18/18] perf test: cs-etm: Test multiple per-thread threads
2026-11-02 12:15 [PATCH v3 00/18] perf cs-etm: Per-thread mode fixes and snapshot wrap support James Clark
` (16 preceding siblings ...)
2026-11-02 12:15 ` [PATCH v3 17/18] perf test: cs-etm: Add test for per-thread mode James Clark
@ 2026-11-02 12:15 ` James Clark
2026-11-02 12:40 ` sashiko-bot
17 siblings, 1 reply; 37+ messages in thread
From: James Clark @ 2026-11-02 12:15 UTC (permalink / raw)
To: Leo Yan, Adrian Hunter
Cc: Suzuki K Poulose, Mike Leach, Leo Yan, Will Deacon,
Peter Zijlstra, Ingo Molnar, Arnaldo Carvalho de Melo,
Namhyung Kim, Mark Rutland, Alexander Shishkin, Jiri Olsa,
Ian Rogers, Mathieu Poirier, Jonathan Corbet, Shuah Khan,
Suyash Mahar, Amir Ayupov, linux-arm-kernel, coresight,
linux-perf-users, linux-kernel, Arnaldo Carvalho de Melo,
linux-doc, James Clark, John Garry
Launch something that has multiple child threads, attach to it and
make sure that all child threads are present in the samples
This will only work with 1:1 sinks/sources, and looking for TRBE is an
easy way to guarantee this. In theory this could also work for ETR
sinks, but we'd have to attempt to record and skip if that fails, which
would make the test silently skip if the record command started failing
on TRBE too.
Signed-off-by: James Clark <james.clark@linaro.org>
---
.../perf/tests/shell/coresight/per-thread-multi.sh | 76 ++++++++++++++++++++++
1 file changed, 76 insertions(+)
diff --git a/tools/perf/tests/shell/coresight/per-thread-multi.sh b/tools/perf/tests/shell/coresight/per-thread-multi.sh
new file mode 100755
index 000000000000..c6bf1ff2e53d
--- /dev/null
+++ b/tools/perf/tests/shell/coresight/per-thread-multi.sh
@@ -0,0 +1,76 @@
+#!/bin/bash -e
+# CoreSight per-thread multiple threads (exclusive)
+
+# SPDX-License-Identifier: GPL-2.0
+
+# If CoreSight is not available, skip the test
+perf list pmu | grep -q cs_etm || exit 2
+
+if ! tmpdir=$(mktemp -d /tmp/perf-cs-per-thread-multi-test.XXXXXX); then
+ echo "mktemp failed"
+ exit 1
+fi
+
+cleanup_files()
+{
+ if [[ $parent ]]; then
+ kill -9 $parent
+ wait $parent || true
+ fi
+ if [[ $perf ]]; then
+ kill -9 $perf
+ wait $perf || true
+ fi
+ rm -rf "$tmpdir"
+ trap - EXIT TERM INT
+}
+
+trap cleanup_files EXIT
+trap 'cleanup_files; exit 1' TERM INT
+
+# Multiple --per-thread events are only supported with 1:1 sinks/sources
+if ! ls /sys/bus/coresight/devices/trbe* > /dev/null 2>&1; then
+ echo "No TRBE sinks, skipping"
+ exit 2
+fi
+
+# Launch 2 threads to run indefinitely
+nthreads=2
+perf test -w named_threads $nthreads 0 &
+parent=$!
+
+# While parent still exists, wait for the 2 children to spawn
+while kill -0 "$parent" 2>/dev/null; do
+ threads=(/proc/"$parent"/task/*)
+
+ if (( ${#threads[@]} >= $(($nthreads + 1)))); then
+ break
+ fi
+
+ sleep 0.1
+done
+
+echo "Recording..."
+perf record -o "$tmpdir/data" -e cs_etm//u --per-thread -Se -m,64K --pid $parent > /dev/null 2>&1 &
+perf=$!
+
+sleep 1
+
+kill $parent
+wait $parent || true
+unset parent
+wait $perf
+unset perf
+
+echo "Decoding..."
+perf script -i "$tmpdir/data" > "$tmpdir/script" 2>/dev/null
+
+# Check all threads were traced and they have the correct thread name and symbol
+for i in $(seq 1 $nthreads); do
+ if ! grep -q "thread${i} .* named_threads_thread${i}" "$tmpdir/script"; then
+ echo "Error: thread${i} missing" >&2
+ exit 1
+ fi
+done
+
+exit 0
--
2.43.0
^ permalink raw reply related [flat|nested] 37+ messages in thread
* Re: [PATCH v3 10/18] perf auxtrace: Turn Intel BTS snapshot search into a generic one
2026-11-02 12:15 ` [PATCH v3 10/18] perf auxtrace: Turn Intel BTS snapshot search into a generic one James Clark
@ 2026-11-02 12:21 ` sashiko-bot
0 siblings, 0 replies; 37+ messages in thread
From: sashiko-bot @ 2026-11-02 12:21 UTC (permalink / raw)
To: James Clark; +Cc: linux-perf-users
> Currently each auxtrace user has to implement both the snapshot search
> and the logic to fixup the 'head' and 'old' values after finding a wrap.
> IntelPT and BTS have the same fixup logic but different wrap searches.
> BTS and Arm SPE have the same search but SPE modifies head differently
> (although it probably shouldn’t), and Arm Coresight doesn't have a
> search at all, but later it will want the same search and fixup logic.
>
> Add auxtrace_record__default_find_snapshot() as a generic
> find_snapshot() implementation that tracers can use without
> reimplementing it. It's based on intel_bts_find_snapshot() and the
> search includes the caching from it. Split the pointer fixup into
> auxtrace_record__snapshot_fixup() so that tracers with their own search,
> like Intel PT, can still share it.
>
> No functional changes intended apart from changing the pr_debug3()s
> to print hex to match __auxtrace_mmap__read() and no longer leaking
> snapshot_refs when expanding the array length.
>
> Signed-off-by: James Clark <james.clark@linaro.org>
Sashiko has reviewed this patch and found no issues.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261102-james-cs-unformatted-per-thread-fix-v3-0-dbcb0c281b9b@linaro.org?part=10
^ permalink raw reply [flat|nested] 37+ messages in thread
* Re: [PATCH v3 07/18] perf python: Expose sample instruction length
2026-11-02 12:15 ` [PATCH v3 07/18] perf python: Expose sample instruction length James Clark
@ 2026-11-02 12:23 ` sashiko-bot
0 siblings, 0 replies; 37+ messages in thread
From: sashiko-bot @ 2026-11-02 12:23 UTC (permalink / raw)
To: James Clark; +Cc: linux-perf-users
> Expose perf_sample::insn_len as a read-only insn_len attribute.
>
> Trace decoders supply instruction lengths on synthesized samples to
> indicate whether an instruction was executed or not during a call or
> exception. Let scripts read this value directly.
>
> Signed-off-by: James Clark <james.clark@linaro.org>
Sashiko has reviewed this patch and found no issues.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261102-james-cs-unformatted-per-thread-fix-v3-0-dbcb0c281b9b@linaro.org?part=7
^ permalink raw reply [flat|nested] 37+ messages in thread
* Re: [PATCH v3 11/18] perf arm-spe: Use generic snapshot search
2026-11-02 12:15 ` [PATCH v3 11/18] perf arm-spe: Use generic snapshot search James Clark
@ 2026-11-02 12:23 ` sashiko-bot
0 siblings, 0 replies; 37+ messages in thread
From: sashiko-bot @ 2026-11-02 12:23 UTC (permalink / raw)
To: James Clark; +Cc: linux-perf-users
> The new generic snapshot search gives a similar result as the
> reimplemented version of it here. One slight difference is in the
> pointer fixup. SPE has:
>
> *head += mm->len;
> *old = *head - mm->len;
>
> Which gives exactly the same result but written in reverse order to the
> generic one:
>
> *old = *head;
> *head += mm->len;
>
> SPE also had a special fixup cases (if (head >= buffer_size)):
>
> [ ... ]
> [head..mm->len] contains zero data. This should be fine, as zero data
> are treated as PAD packets and discarded during decoding.
>
> Reviewed-by: Leo Yan <leo.yan@arm.com>
> Signed-off-by: James Clark <james.clark@linaro.org>
Sashiko has reviewed this patch and found no issues.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261102-james-cs-unformatted-per-thread-fix-v3-0-dbcb0c281b9b@linaro.org?part=11
^ permalink raw reply [flat|nested] 37+ messages in thread
* Re: [PATCH v3 02/18] perf cs-etm: Warn for invalid timestamp option
2026-11-02 12:15 ` [PATCH v3 02/18] perf cs-etm: Warn for invalid timestamp option James Clark
@ 2026-11-02 12:24 ` sashiko-bot
0 siblings, 0 replies; 37+ messages in thread
From: sashiko-bot @ 2026-11-02 12:24 UTC (permalink / raw)
To: James Clark; +Cc: linux-perf-users
> Instead of silently returning an error, print a useful warning.
>
> Reviewed-by: Leo Yan <leo.yan@arm.com>
> Signed-off-by: James Clark <james.clark@linaro.org>
Sashiko has reviewed this patch and found no issues.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261102-james-cs-unformatted-per-thread-fix-v3-0-dbcb0c281b9b@linaro.org?part=2
^ permalink raw reply [flat|nested] 37+ messages in thread
* Re: [PATCH v3 09/18] perf cs-etm: Increase default timestamp generation period
2026-11-02 12:15 ` [PATCH v3 09/18] perf cs-etm: Increase default timestamp generation period James Clark
@ 2026-11-02 12:24 ` sashiko-bot
0 siblings, 0 replies; 37+ messages in thread
From: sashiko-bot @ 2026-11-02 12:24 UTC (permalink / raw)
To: James Clark; +Cc: linux-perf-users
> The original purpose of timestamps in per-CPU mode was to roughly emit
> samples in the order they occurred across multiple CPUs, rather than
> emit all the trace from CPU 1 followed by all from CPU 2 etc.
> Previously there was no way to change the interval from the minimum of 1
> cycle, but custom intervals are now supported by the driver.
>
> Drastically reduce the number of timestamps emitted by increasing the
> interval, which is more than enough to interleave sample generation.
> This reduces the size of the trace generated.
>
> Reviewed-by: Leo Yan <leo.yan@arm.com>
> Signed-off-by: James Clark <james.clark@linaro.org>
Sashiko has reviewed this patch and found no issues.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261102-james-cs-unformatted-per-thread-fix-v3-0-dbcb0c281b9b@linaro.org?part=9
^ permalink raw reply [flat|nested] 37+ messages in thread
* Re: [PATCH v3 05/18] perf test: cs-etm: Increase window size in disassembly test
2026-11-02 12:15 ` [PATCH v3 05/18] perf test: cs-etm: Increase window size in disassembly test James Clark
@ 2026-11-02 12:24 ` sashiko-bot
0 siblings, 0 replies; 37+ messages in thread
From: sashiko-bot @ 2026-11-02 12:24 UTC (permalink / raw)
To: James Clark; +Cc: linux-perf-users
> When we increase the default timestamp interval in a later commit, less
> trace will be recorded. At the moment the amount of trace overflows the
> 64K buffer, so the exit snapshot trace is what happened at the end of
> the process rather than the beginning. With the new default interval,
> the entire execution will be contained in one buffer and the first few
> samples are always various instruction and data faults. These go to the
> kernel so don't result in ranges that can be disassembled.
>
> Increase the window to 6 samples to ensure something with a branch can
> be disassembled even with the fault samples.
>
> Signed-off-by: James Clark <james.clark@linaro.org>
Sashiko has reviewed this patch and found no issues.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261102-james-cs-unformatted-per-thread-fix-v3-0-dbcb0c281b9b@linaro.org?part=5
^ permalink raw reply [flat|nested] 37+ messages in thread
* Re: [PATCH v3 08/18] perf cs-etm: Use instruction length in disassembly script
2026-11-02 12:15 ` [PATCH v3 08/18] perf cs-etm: Use instruction length in disassembly script James Clark
@ 2026-11-02 12:24 ` sashiko-bot
0 siblings, 0 replies; 37+ messages in thread
From: sashiko-bot @ 2026-11-02 12:24 UTC (permalink / raw)
To: James Clark; +Cc: linux-perf-users
> The disassembly script adds four bytes to each branch sample's IP to
> include the final instruction in the range. For an exception sample
> whose IP identifies a faulting or interrupted instruction, this
> incorrectly includes an instruction that did not execute.
>
> Use the decoder-supplied insn_len to calculate the exclusive end
> address. This preserves zero-length exception entries, includes executed
> syscall instructions and also uses the decoded size for Thumb
> instructions.
>
> Signed-off-by: James Clark <james.clark@linaro.org>
Sashiko has reviewed this patch and found no issues.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261102-james-cs-unformatted-per-thread-fix-v3-0-dbcb0c281b9b@linaro.org?part=8
^ permalink raw reply [flat|nested] 37+ messages in thread
* Re: [PATCH v3 12/18] perf auxtrace: intel-pt: Use common snapshot pointer fixup
2026-11-02 12:15 ` [PATCH v3 12/18] perf auxtrace: intel-pt: Use common snapshot pointer fixup James Clark
@ 2026-11-02 12:25 ` sashiko-bot
0 siblings, 0 replies; 37+ messages in thread
From: sashiko-bot @ 2026-11-02 12:25 UTC (permalink / raw)
To: James Clark; +Cc: linux-perf-users
> Intel PT has its own wrap search but the same pointer fixup logic as the
> generic auxtrace_record__default_find_snapshot(). Replace the duplicate
> code with auxtrace_record__snapshot_fixup().
>
> No functional changes intended.
>
> Signed-off-by: James Clark <james.clark@linaro.org>
Sashiko has reviewed this patch and found no issues.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261102-james-cs-unformatted-per-thread-fix-v3-0-dbcb0c281b9b@linaro.org?part=12
^ permalink raw reply [flat|nested] 37+ messages in thread
* Re: [PATCH v3 14/18] perf cs-etm: Don't print missing buffers in snapshot mode
2026-11-02 12:15 ` [PATCH v3 14/18] perf cs-etm: Don't print missing buffers in snapshot mode James Clark
@ 2026-11-02 12:26 ` sashiko-bot
0 siblings, 0 replies; 37+ messages in thread
From: sashiko-bot @ 2026-11-02 12:26 UTC (permalink / raw)
To: James Clark; +Cc: linux-perf-users
> The driver produces lots of AUX records for trace data that was never
> captured in snapshot mode. Downgrade this to a pr_debug3 message as it's
> only an error in normal mode.
>
> Reviewed-by: Leo Yan <leo.yan@arm.com>
> Signed-off-by: James Clark <james.clark@linaro.org>
Sashiko has reviewed this patch and found no issues.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261102-james-cs-unformatted-per-thread-fix-v3-0-dbcb0c281b9b@linaro.org?part=14
^ permalink raw reply [flat|nested] 37+ messages in thread
* Re: [PATCH v3 16/18] perf test: Allow infinite named_thread loops
2026-11-02 12:15 ` [PATCH v3 16/18] perf test: Allow infinite named_thread loops James Clark
@ 2026-11-02 12:27 ` sashiko-bot
0 siblings, 0 replies; 37+ messages in thread
From: sashiko-bot @ 2026-11-02 12:27 UTC (permalink / raw)
To: James Clark; +Cc: linux-perf-users
> The following test will want this to run forever, so add support by
> treating 0 loops as infinite.
>
> Convert i and named_threads_work to be unsigned to avoid undefined
> signed overflow behavior in infinite loop mode.
>
> Signed-off-by: James Clark <james.clark@linaro.org>
Sashiko has reviewed this patch and found no issues.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261102-james-cs-unformatted-per-thread-fix-v3-0-dbcb0c281b9b@linaro.org?part=16
^ permalink raw reply [flat|nested] 37+ messages in thread
* Re: [PATCH v3 15/18] perf auxtrace: cs-etm: Capture wrapped snapshots
2026-11-02 12:15 ` [PATCH v3 15/18] perf auxtrace: cs-etm: Capture wrapped snapshots James Clark
@ 2026-11-02 12:28 ` sashiko-bot
0 siblings, 0 replies; 37+ messages in thread
From: sashiko-bot @ 2026-11-02 12:28 UTC (permalink / raw)
To: James Clark; +Cc: linux-perf-users
> find_snapshot() isn't implemented for Coresight, so when a wrap occurs
> we only save from offset 0 to current head, modulo buffer size, rather
> than capturing the whole buffer. Coresight drivers currently use a
> monotonic head pointer even in snapshot mode, which gives a lot more
> information for taking snapshots than a wrapped pointer. It means we
> don't need to search for data at the end of the buffer to guess if a
> wrap has occurred, and we can go back to saving partial buffers after a
> wrap by checking if the next size to take is less than the buffer
> size.
>
> The drivers will always be able to generate a monotonic head because
> TRBE has a bytes written counter, so even if we stop generating an AUX
> record on every overflow, we can check if the counter indicated an
> overwrite and offset head by a whole buffer size if it wrapped. This
> forces userspace to read the whole buffer rather than between last and
> current head. ETR and other sinks use software double buffering, so can
> continue as they are.
>
> Add auxtrace_record__monotonic_find_snapshot() and use it for Coresight.
> A monotonic head can't be auto-detected in the default find_snapshot()
> by checking if head > buffer size, because before the first wrap a
> monotonic head looks the same as a wrapped one. The search would then
> falsely detect a wrap when head is near the end of the buffer, and the
> wrapped pointer fixup moves 'old' and 'head' a whole buffer ahead of the
> driver's head. With a wrapped head that doesn't matter because only the
> offset within the buffer is used, but with a monotonic head the next
> snapshot would either save already saved data again or drop up to a
> buffer's worth of new data.
>
> Signed-off-by: James Clark <james.clark@linaro.org>
Sashiko has reviewed this patch and found no issues.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261102-james-cs-unformatted-per-thread-fix-v3-0-dbcb0c281b9b@linaro.org?part=15
^ permalink raw reply [flat|nested] 37+ messages in thread
* Re: [PATCH v3 06/18] perf cs-etm: Don't drop previous target for kernel samples
2026-11-02 12:15 ` [PATCH v3 06/18] perf cs-etm: Don't drop previous target for kernel samples James Clark
@ 2026-11-02 12:29 ` sashiko-bot
0 siblings, 0 replies; 37+ messages in thread
From: sashiko-bot @ 2026-11-02 12:29 UTC (permalink / raw)
To: James Clark; +Cc: linux-perf-users
> The branch target of the previous sample is used as the start of the
> range to disassemble, but if that sample's DSO is "[unknown]" then we'll
> drop it even though it's useful information. This happens when recording
> userspace only. Save the address earlier for "Trace On" samples, before
> we get to the early exit condition for unknown DSOs.
>
> In a later commit to change the default timestamp interval, trace will
> be collected that triggers this issue, so fix it now.
>
> Signed-off-by: James Clark <james.clark@linaro.org>
Sashiko has reviewed this patch and found no issues.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261102-james-cs-unformatted-per-thread-fix-v3-0-dbcb0c281b9b@linaro.org?part=6
^ permalink raw reply [flat|nested] 37+ messages in thread
* Re: [PATCH v3 13/18] perf cs-etm: Queue partial AUX records
2026-11-02 12:15 ` [PATCH v3 13/18] perf cs-etm: Queue partial AUX records James Clark
@ 2026-11-02 12:32 ` sashiko-bot
0 siblings, 0 replies; 37+ messages in thread
From: sashiko-bot @ 2026-11-02 12:32 UTC (permalink / raw)
To: James Clark; +Cc: linux-perf-users
> In snapshot mode AUX records may straddle the boundary of multiple trace
> buffers. Currently we only queue AUX records whose range is contained
> wholly within a single buffer.
>
> Fix it by queuing any partial AUX range that overlaps any buffer.
>
> Reviewed-by: Leo Yan <leo.yan@arm.com>
> Signed-off-by: James Clark <james.clark@linaro.org>
Sashiko has reviewed this patch and found no issues.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261102-james-cs-unformatted-per-thread-fix-v3-0-dbcb0c281b9b@linaro.org?part=13
^ permalink raw reply [flat|nested] 37+ messages in thread
* Re: [PATCH v3 03/18] perf cs-etm: Turn on context packet timestamps in per-thread mode
2026-11-02 12:15 ` [PATCH v3 03/18] perf cs-etm: Turn on context packet timestamps in per-thread mode James Clark
@ 2026-11-02 12:32 ` sashiko-bot
0 siblings, 0 replies; 37+ messages in thread
From: sashiko-bot @ 2026-11-02 12:32 UTC (permalink / raw)
To: James Clark; +Cc: linux-perf-users
> Practically this costs nothing and preempts future changes that will be
> made to support decoding trace in order with Perf MMAP and JIT events.
> It also helps keep samples in order when we add support for multiple
> per-thread mode threads in the next commit.
>
> Signed-off-by: James Clark <james.clark@linaro.org>
Sashiko has reviewed this patch and found no issues.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261102-james-cs-unformatted-per-thread-fix-v3-0-dbcb0c281b9b@linaro.org?part=3
^ permalink raw reply [flat|nested] 37+ messages in thread
* Re: [PATCH v3 17/18] perf test: cs-etm: Add test for per-thread mode
2026-11-02 12:15 ` [PATCH v3 17/18] perf test: cs-etm: Add test for per-thread mode James Clark
@ 2026-11-02 12:32 ` sashiko-bot
0 siblings, 0 replies; 37+ messages in thread
From: sashiko-bot @ 2026-11-02 12:32 UTC (permalink / raw)
To: James Clark; +Cc: linux-perf-users
> Test that per-thread mode works when the process migrates across CPUs
> and that the decode happens in the correct order.
>
> A TODO is left in for when support for decoding in order with Perf
> records is added.
>
> Signed-off-by: James Clark <james.clark@linaro.org>
Sashiko has reviewed this patch and found no issues.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261102-james-cs-unformatted-per-thread-fix-v3-0-dbcb0c281b9b@linaro.org?part=17
^ permalink raw reply [flat|nested] 37+ messages in thread
* Re: [PATCH v3 04/18] perf cs-etm: Use per-CPU queues for per-thread mode
2026-11-02 12:15 ` [PATCH v3 04/18] perf cs-etm: Use per-CPU queues for " James Clark
@ 2026-11-02 12:34 ` sashiko-bot
0 siblings, 0 replies; 37+ messages in thread
From: sashiko-bot @ 2026-11-02 12:34 UTC (permalink / raw)
To: James Clark; +Cc: linux-perf-users
Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
- [Medium] perf cs-etm: cs_etm__get_data_block() clobbers frontend_thread before flush
- [Medium] perf cs-etm: missing NULL checks in cs_etm__get_data_block()
- [Medium] perf cs-etm: per-CPU queues break TID matching in cs_etm__process_timeless_queues()
--
commit e38f1bcfae5e4494bb005e6c5cf9aa519d0b1814
Author: James Clark <james.clark@linaro.org>
perf cs-etm: Use per-CPU queues for per-thread mode
This commit switches per-thread CoreSight ETM decoding to use per-CPU queues
and re-initializes the trace ID queue's threads in cs_etm__get_data_block()
when the AUX buffer TID changes on a CPU.
> diff --git a/tools/perf/util/cs-etm.c b/tools/perf/util/cs-etm.c
> index 509ec014b2760..f19aa4f787fa3 100644
> --- a/tools/perf/util/cs-etm.c
> +++ b/tools/perf/util/cs-etm.c
[ ... ]
> @@ -2215,6 +2209,29 @@ static int cs_etm__get_data_block(struct cs_etm_queue *etmq)
> */
> cs_etm__flush_all_stack(etmq);
>
> + /*
> + * Per-thread mode still uses a queue for each CPU, but that CPU can run
> + * different threads. When the TID from the AUX record on a CPU changes,
> + * re-initialize the thread using the AUX record/buffer fragment TID so
> + * we can start decoding even if the context ID packet was cropped or
> + * they're disabled.
> + */
> + if (etm->per_thread_decoding) {
> + struct cs_etm_traceid_queue *tidq = cs_etm__etmq_get_traceid_queue(etmq,
> + CS_ETM_PER_THREAD_TRACEID);
> +
> + if (thread__tid(tidq->decode_thread) != etmq->buffer->tid) {
> + thread__zput(tidq->frontend_thread);
> + thread__zput(tidq->decode_thread);
> + tidq->frontend_thread = machine__findnew_thread(
> + &etm->session->machines.host, -1,
> + etmq->buffer->tid);
> + tidq->decode_thread = machine__findnew_thread(
> + &etm->session->machines.host, -1,
> + etmq->buffer->tid);
> + }
> + }
[Severity: Medium]
When two threads run consecutively on the same CPU in per-thread mode, can
replacing tidq->frontend_thread here in cs_etm__get_data_block() clobber the
previous thread before its trailing packets and thread stack are flushed?
At the end of an AUX buffer for thread A, the last CS_ETM_RANGE or
CS_ETM_EXCEPTION packet remains in tidq->prev_packet. When
cs_etm__get_data_block() loads the next AUX buffer on that CPU for thread B,
cs_etm_decoder__reset() queues a CS_ETM_DISCONTINUITY packet into
tidq->packet_queue, and cs_etm__flush_all_stack() is a no-op because pid_fmt
is CS_ETM_PIDFMT_NONE in per-thread mode:
tools/perf/util/cs-etm.c:cs_etm__flush_all_stack() {
...
switch (pid_fmt) {
...
case CS_ETM_PIDFMT_NONE:
default:
break;
}
}
Because cs_etm__get_data_block() immediately overwrites tidq->frontend_thread
with thread B before the frontend processes the queued CS_ETM_DISCONTINUITY
packet, the subsequent call to cs_etm__process_traceid_queue() ->
cs_etm__flush() runs with tidq->frontend_thread already pointing to thread B:
tools/perf/util/cs-etm.c:cs_etm__flush() {
...
err = cs_etm__synth_last_instruction_sample(etmq, tidq);
if (err)
return err;
err = cs_etm__synth_branch_sample(etmq, tidq);
if (err)
return err;
swap_packet:
cs_etm__packet_swap(etm, tidq);
/* Reset last branches after flush the trace */
if (etm->use_thread_stack)
thread_stack__flush(tidq->frontend_thread);
...
}
Wouldn't this attribute thread A's trailing instruction and TRACE_END branch
samples to thread B, read instruction bytes from thread B's memory maps via
cs_etm__copy_insn() -> cs_etm__frontend_mem_access(), and flush thread B's
thread_stack while leaving thread A's thread_stack unflushed?
[Severity: Medium]
As reported by Sashiko on v2 [1], what happens if
cs_etm__etmq_get_traceid_queue() or machine__findnew_thread() returns NULL in
cs_etm__get_data_block()?
In timestamped per-thread mode, cs_etm__process_timestamped_queues() ->
cs_etm__queue_first_cs_timestamp() -> cs_etm__get_data_block() is the first
call site that allocates the trace ID queue for etmq. If memory allocation
fails inside cs_etm__etmq_get_traceid_queue(), tidq is NULL and passing
tidq->decode_thread to thread__tid() will dereference a NULL pointer.
Similarly, if either machine__findnew_thread() call returns NULL when
re-initializing tidq->frontend_thread and tidq->decode_thread,
cs_etm__get_data_block() still returns 1, unlike cs_etm__init_traceid_queue()
which checks both pointers:
tools/perf/util/cs-etm.c:cs_etm__init_traceid_queue() {
...
tidq->frontend_thread = machine__findnew_thread(&etm->session->machines.host, -1,
queue->tid);
tidq->decode_thread = machine__findnew_thread(&etm->session->machines.host, -1,
queue->tid);
if (!tidq->frontend_thread || !tidq->decode_thread)
goto out;
}
Should cs_etm__get_data_block() check for NULL returns from
cs_etm__etmq_get_traceid_queue() and machine__findnew_thread() and return
-ENOMEM on failure?
> +
> return 1;
> }
>
> @@ -3288,7 +3305,9 @@ 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);
> + /* Old per-thread files have no CPU (before v6.4) */
> + int queue_idx = sample->cpu == (u32)-1 ? 0 : sample->cpu;
> + struct cs_etm_queue *etmq = cs_etm__get_queue(etm, queue_idx);
>
> if (!etmq)
> return -EINVAL;
> @@ -3301,6 +3320,7 @@ static int cs_etm__queue_aux_fragment(struct perf_session *session, off_t file_o
> auxtrace_fragment.auxtrace.size = aux_size;
> auxtrace_fragment.auxtrace.offset = aux_offset;
> auxtrace_fragment.auxtrace.idx = etmq->queue_nr;
> + auxtrace_fragment.auxtrace.cpu = etmq->queue_nr;
[Severity: Medium]
Now that cs_etm__queue_aux_fragment() places per-thread AUX fragments into
per-CPU queues indexed by sample->cpu, how does timeless per-thread decoding
work when PERF_RECORD_EXIT triggers cs_etm__process_timeless_queues()?
In cs_etm__process_event(), PERF_RECORD_EXIT passes the exiting thread's TID
to cs_etm__process_timeless_queues():
tools/perf/util/cs-etm.c:cs_etm__process_event() {
...
case PERF_RECORD_EXIT:
if (etm->per_thread_decoding && etm->timeless_decoding)
return cs_etm__process_timeless_queues(etm,
event->fork.tid);
break;
}
And cs_etm__process_timeless_queues() still assumes each queue belongs to a
single thread matching tidq->frontend_thread:
tools/perf/util/cs-etm.c:cs_etm__process_timeless_queues() {
...
if (etm->per_thread_decoding) {
tidq = cs_etm__etmq_get_traceid_queue(
etmq, CS_ETM_PER_THREAD_TRACEID);
if (!tidq)
continue;
if (tid == -1 || thread__tid(tidq->frontend_thread) == tid)
cs_etm__run_per_thread_timeless_decoder(etmq);
} else
cs_etm__run_per_cpu_timeless_decoder(etmq);
}
Because auxtrace_queues__queue_buffer() only sets queue->tid once when the
first buffer is queued on a CPU (and leaves queue->tid as 0 on CPUs with no
queued buffers):
1. For every CPU queue that never received an AUX buffer, etmq is non-NULL and
queue->tid is 0, so cs_etm__etmq_get_traceid_queue() allocates a trace ID
queue and creates a bogus TID 0 thread in machines.host.
2. If thread T1 and thread T2 both ran on the same CPU (with T1 queued first),
when T1 exits, thread__tid(tidq->frontend_thread) == T1 matches and
cs_etm__run_per_thread_timeless_decoder() drains every buffer in that CPU's
queue, decoding T2's buffers prematurely before T2's subsequent MMAP or
COMM events have been processed.
3. Conversely, if T2 exits before T1, thread__tid(tidq->frontend_thread) is T1
rather than T2, so T2's buffers on that CPU are not decoded on T2's
PERF_RECORD_EXIT event.
Does timeless per-thread decoding also need to be updated to handle per-CPU
queues shared across multiple threads?
[1] https://lore.kernel.org/r/20260821-james-cs-unformatted-per-thread-fix-v2-4-00c4fd0701b4@linaro.org
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261102-james-cs-unformatted-per-thread-fix-v3-0-dbcb0c281b9b@linaro.org?part=4
^ permalink raw reply [flat|nested] 37+ messages in thread
* Re: [PATCH v3 01/18] perf cs-etm: Fix nVHE per-thread decoding
2026-11-02 12:15 ` [PATCH v3 01/18] perf cs-etm: Fix nVHE per-thread decoding James Clark
@ 2026-11-02 12:36 ` sashiko-bot
0 siblings, 0 replies; 37+ messages in thread
From: sashiko-bot @ 2026-11-02 12:36 UTC (permalink / raw)
To: James Clark; +Cc: linux-perf-users
> pid_fmt is unset in per-thread mode because we don't need PIDs, so we
> can't use it as a hint to determine which EL the host is running at.
> That results in falling though to the next part where EL1 is treated as
> guest, which is wrong for nVHE.
>
> Fix it by only returning a guest machine when run from perf kvm where a
> guest image would be provided, which matches existing behavior in other
> parts of Perf. The exception is when pid_fmt shows that the host is
> running at EL2 (VHE). Then EL1 is always a guest, so keep using the guest
> machine rather than decoding guest trace with the host kernel maps.
>
> Reviewed-by: Leo Yan <leo.yan@arm.com>
> Signed-off-by: James Clark <james.clark@linaro.org>
Sashiko has reviewed this patch and found no issues.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261102-james-cs-unformatted-per-thread-fix-v3-0-dbcb0c281b9b@linaro.org?part=1
^ permalink raw reply [flat|nested] 37+ messages in thread
* Re: [PATCH v3 18/18] perf test: cs-etm: Test multiple per-thread threads
2026-11-02 12:15 ` [PATCH v3 18/18] perf test: cs-etm: Test multiple per-thread threads James Clark
@ 2026-11-02 12:40 ` sashiko-bot
0 siblings, 0 replies; 37+ messages in thread
From: sashiko-bot @ 2026-11-02 12:40 UTC (permalink / raw)
To: James Clark; +Cc: linux-perf-users
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [Medium] perf test: cs-etm: unguarded kill in cleanup_files() aborts under set -e
- [Medium] perf test: cs-etm: unsynchronized perf record startup and wait loop
--
commit 6aa8bbe9530a4ed93f2099e972a4b1384053a19f
Author: James Clark <james.clark@linaro.org>
perf test: cs-etm: Test multiple per-thread threads
This commit adds a shell test for CoreSight ETM with TRBE sinks that records
a workload with multiple child threads in per-thread mode and checks that
samples from all child threads are present in the decoded trace.
> diff --git a/tools/perf/tests/shell/coresight/per-thread-multi.sh b/tools/perf/tests/shell/coresight/per-thread-multi.sh
> new file mode 100755
> index 0000000000000..c6bf1ff2e53d1
> --- /dev/null
> +++ b/tools/perf/tests/shell/coresight/per-thread-multi.sh
> @@ -0,0 +1,76 @@
> +#!/bin/bash -e
> +# CoreSight per-thread multiple threads (exclusive)
> +
> +# SPDX-License-Identifier: GPL-2.0
> +
> +# If CoreSight is not available, skip the test
> +perf list pmu | grep -q cs_etm || exit 2
> +
> +if ! tmpdir=$(mktemp -d /tmp/perf-cs-per-thread-multi-test.XXXXXX); then
> + echo "mktemp failed"
> + exit 1
> +fi
> +
> +cleanup_files()
> +{
> + if [[ $parent ]]; then
> + kill -9 $parent
> + wait $parent || true
> + fi
> + if [[ $perf ]]; then
> + kill -9 $perf
> + wait $perf || true
> + fi
> + rm -rf "$tmpdir"
> + trap - EXIT TERM INT
> +}
[Severity: Medium]
Can cleanup_files() abort before removing tmpdir or killing the remaining
process when the script runs under #!/bin/bash -e?
Later in per-thread-multi.sh:
kill $parent
wait $parent || true
unset parent
wait $perf
unset perf
If the background perf record process exits with a non-zero status,
wait $perf reaps the process and triggers set -e exit before unset perf
runs. When the EXIT trap enters cleanup_files(), $perf is still set to the
already-reaped PID, so kill -9 $perf fails with ESRCH and aborts
cleanup_files() under set -e before rm -rf "$tmpdir" runs.
Similarly, if $parent exits early before unset parent runs, kill $parent
fails under set -e and cleanup_files() aborts on kill -9 $parent before
killing $perf or removing "$tmpdir".
Should kill -9 $parent and kill -9 $perf in cleanup_files() be guarded with
|| true (or 2>/dev/null || true), similar to wait?
> +
> +trap cleanup_files EXIT
> +trap 'cleanup_files; exit 1' TERM INT
> +
> +# Multiple --per-thread events are only supported with 1:1 sinks/sources
> +if ! ls /sys/bus/coresight/devices/trbe* > /dev/null 2>&1; then
> + echo "No TRBE sinks, skipping"
> + exit 2
> +fi
> +
> +# Launch 2 threads to run indefinitely
> +nthreads=2
> +perf test -w named_threads $nthreads 0 &
> +parent=$!
> +
> +# While parent still exists, wait for the 2 children to spawn
> +while kill -0 "$parent" 2>/dev/null; do
> + threads=(/proc/"$parent"/task/*)
> +
> + if (( ${#threads[@]} >= $(($nthreads + 1)))); then
> + break
> + fi
> +
> + sleep 0.1
> +done
> +
> +echo "Recording..."
> +perf record -o "$tmpdir/data" -e cs_etm//u --per-thread -Se -m,64K --pid $parent > /dev/null 2>&1 &
> +perf=$!
> +
> +sleep 1
> +
> +kill $parent
[Severity: Medium]
Could starting perf record in the background and immediately running sleep 1
before killing $parent race with perf record initialization on slower or
loaded systems?
In tools/perf/builtin-record.c:__cmd_record(), events are opened disabled in
record__open(), followed by record__synthesize() (including BPF and auxtrace
synthesis), and only enabled afterward via evlist__enable(). If startup and
synthesis take most or all of the 1-second sleep window, kill $parent can
terminate the workload before evlist__enable() or before both threads are
sampled, causing perf script to miss thread1 or thread2.
Also, the /proc/"$parent"/task/* polling loop above has no timeout and breaks
as soon as the task directories exist, which can happen before the child
threads call pthread_setname_np() in tools/perf/tests/workloads/named_threads.c.
Would it be more reliable to source tools/perf/tests/shell/lib/waiting.sh to
use wait_for_threads() and wait_for_perf_to_start(), or pass sleep 1 directly
as the perf record command workload so the 1-second window begins only after
events are enabled?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261102-james-cs-unformatted-per-thread-fix-v3-0-dbcb0c281b9b@linaro.org?part=18
^ permalink raw reply [flat|nested] 37+ messages in thread
end of thread, other threads:[~2026-11-02 12:40 UTC | newest]
Thread overview: 37+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-11-02 12:15 [PATCH v3 00/18] perf cs-etm: Per-thread mode fixes and snapshot wrap support James Clark
2026-11-02 12:15 ` [PATCH v3 01/18] perf cs-etm: Fix nVHE per-thread decoding James Clark
2026-11-02 12:36 ` sashiko-bot
2026-11-02 12:15 ` [PATCH v3 02/18] perf cs-etm: Warn for invalid timestamp option James Clark
2026-11-02 12:24 ` sashiko-bot
2026-11-02 12:15 ` [PATCH v3 03/18] perf cs-etm: Turn on context packet timestamps in per-thread mode James Clark
2026-11-02 12:32 ` sashiko-bot
2026-11-02 12:15 ` [PATCH v3 04/18] perf cs-etm: Use per-CPU queues for " James Clark
2026-11-02 12:34 ` sashiko-bot
2026-11-02 12:15 ` [PATCH v3 05/18] perf test: cs-etm: Increase window size in disassembly test James Clark
2026-11-02 12:24 ` sashiko-bot
2026-11-02 12:15 ` [PATCH v3 06/18] perf cs-etm: Don't drop previous target for kernel samples James Clark
2026-11-02 12:29 ` sashiko-bot
2026-11-02 12:15 ` [PATCH v3 07/18] perf python: Expose sample instruction length James Clark
2026-11-02 12:23 ` sashiko-bot
2026-11-02 12:15 ` [PATCH v3 08/18] perf cs-etm: Use instruction length in disassembly script James Clark
2026-11-02 12:24 ` sashiko-bot
2026-11-02 12:15 ` [PATCH v3 09/18] perf cs-etm: Increase default timestamp generation period James Clark
2026-11-02 12:24 ` sashiko-bot
2026-11-02 12:15 ` [PATCH v3 10/18] perf auxtrace: Turn Intel BTS snapshot search into a generic one James Clark
2026-11-02 12:21 ` sashiko-bot
2026-11-02 12:15 ` [PATCH v3 11/18] perf arm-spe: Use generic snapshot search James Clark
2026-11-02 12:23 ` sashiko-bot
2026-11-02 12:15 ` [PATCH v3 12/18] perf auxtrace: intel-pt: Use common snapshot pointer fixup James Clark
2026-11-02 12:25 ` sashiko-bot
2026-11-02 12:15 ` [PATCH v3 13/18] perf cs-etm: Queue partial AUX records James Clark
2026-11-02 12:32 ` sashiko-bot
2026-11-02 12:15 ` [PATCH v3 14/18] perf cs-etm: Don't print missing buffers in snapshot mode James Clark
2026-11-02 12:26 ` sashiko-bot
2026-11-02 12:15 ` [PATCH v3 15/18] perf auxtrace: cs-etm: Capture wrapped snapshots James Clark
2026-11-02 12:28 ` sashiko-bot
2026-11-02 12:15 ` [PATCH v3 16/18] perf test: Allow infinite named_thread loops James Clark
2026-11-02 12:27 ` sashiko-bot
2026-11-02 12:15 ` [PATCH v3 17/18] perf test: cs-etm: Add test for per-thread mode James Clark
2026-11-02 12:32 ` sashiko-bot
2026-11-02 12:15 ` [PATCH v3 18/18] perf test: cs-etm: Test multiple per-thread threads James Clark
2026-11-02 12:40 ` sashiko-bot
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox