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-0...
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-9...
--- 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
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
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; }
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); }
/*
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
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"
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
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"), {
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
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");
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);
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;
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;
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);
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:
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);
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) {
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
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