From 9121ce555e5cb895551bf53bf3c2c97bc2ef4aff Mon Sep 17 00:00:00 2001 From: Thorsten de Buhr Date: Tue, 22 Sep 2026 17:19:01 +0200 Subject: [PATCH 1/6] feat(ctrace): process all trace channels per solution set Decode matching SWO, TB, and named-TB captures independently and continue after per-input failures. Qualify CTF bundle names with the channel to match CSV/XML output naming and prevent overwrites. Cover multi-channel selection, output modes, filters, failure isolation, and independent metadata. Update architecture and document the CTF path compatibility change. --- tools/ctrace/README.md | 23 +- tools/ctrace/docs/architecture.md | 47 ++- tools/ctrace/docs/architecture.svg | 24 +- tools/ctrace/docs/constraints.md | 24 +- tools/ctrace/docs/ctf-format.md | 14 +- tools/ctrace/docs/multi-source-design.md | 17 +- tools/ctrace/docs/todo.md | 3 +- .../ctrace/src/control/TraceDirectoryJob.cpp | 45 ++- tools/ctrace/src/control/TraceDirectoryJob.h | 7 +- .../ctrace/src/output/OutputRequirements.cpp | 2 +- .../ctrace/src/tracerun/TraceRunDiscovery.cpp | 20 +- tools/ctrace/src/tracerun/TraceRunDiscovery.h | 18 +- tools/ctrace/test/integration/README.md | 13 +- .../test/integration/src/CtraceIntegTests.cpp | 351 +++++++++++++----- .../src/ValidateBabeltrace2Consumer.cmake | 6 +- .../src/control/TraceDirectoryJobTests.cpp | 162 ++++---- .../src/output/OutputRequirementsTests.cpp | 25 +- .../src/output/ctf/CtfBundleOutputTests.cpp | 16 +- .../src/tracerun/TraceRunDiscoveryTests.cpp | 104 +++--- 19 files changed, 622 insertions(+), 299 deletions(-) diff --git a/tools/ctrace/README.md b/tools/ctrace/README.md index 627e373c0..eec703a66 100644 --- a/tools/ctrace/README.md +++ b/tools/ctrace/README.md @@ -39,17 +39,21 @@ For `ctrace .trace --target Board --all`, the supported input produces: ```text .trace/ Board.SWO.csv - Board.ctf/ + Board.SWO.ctf/ metadata stream_0 Board.SWO.traceanalysis.xml # only with graphical data and one retained clock domain ``` -Discovery requires exactly one `Board.SWO.raw`, `Board.TB.raw`, or -`Board.TB_.raw` input. Without a format declaration, SWO defaults to -unformatted ITM and TB or named-TB defaults to formatted CoreSight input. -Missing or null `trace-format` uses this channel-based default; an explicit -value overrides it for the selected input: +Discovery processes every existing `Board.SWO.raw`, `Board.TB.raw`, and `Board.TB_.raw` input independently, +one after another. `--target Board` selects the solution set and all its supported inputs. Each input gets its own +CSV, CTF bundle, and optional XML companion, using `.` as their common base name. CTF uses +this channel-qualified name even for a single input; older `Board.ctf` bundles are neither reused nor removed. +The [CTF profile](docs/ctf-format.md#files-and-common-structure) records the required specification alignment. + +Without a format declaration, each SWO input defaults to unformatted ITM and each TB or named-TB input defaults to +formatted CoreSight. Missing or null `trace-format` uses these channel-based defaults; an explicit value overrides +the format for every supported input in that trace-run: ```yaml ctrace-run: @@ -57,13 +61,16 @@ ctrace-run: # ctrace-setup and ctrace-refs follow here ``` -This optional, ctrace-private provisional field does not select a file or -resolve multiple candidates. The channel-based default is a heuristic, not +This optional, ctrace-private provisional field does not select a file. The channel-based default is a heuristic, not byte-content detection. Formatted input currently requires complete 16-byte memory-aligned CoreSight frames; there is no public `trace-framing` field yet. See the [constraints](docs/constraints.md) for the full discovery, routing, and compatibility contract. +Event Recorder (`Board.ER.raw`) remains unsupported and is skipped with a warning. A solution set without a supported +input reports an error. A failure in one input leaves the remaining inputs and solution sets available for processing; +the command returns non-zero if any input fails. This also applies when validating without output options. + ## Incomplete captures Formatted input accounts for payload skipped because its source ID is missing, NULL, reserved, or unconfigured, diff --git a/tools/ctrace/docs/architecture.md b/tools/ctrace/docs/architecture.md index e312c9106..80357be93 100644 --- a/tools/ctrace/docs/architecture.md +++ b/tools/ctrace/docs/architecture.md @@ -20,17 +20,19 @@ Backend-specific representations are documented in the [CTF profile](ctf-format. PC sampling accepts a four-byte PC or the one-byte status markers `0x00` (`CPU Sleeping`) and Armv8-M `0xff` (`Trace prohibited`). Status markers preserve route, timestamp, and sample quality without a PC address. -Exactly one raw input is active for each trace-run configuration. Formatted input distributes bytes to configured -ITM routes by Trace Bus ID; unformatted input uses one synthetic route. Other protocols require explicit decoder -integration, not guesses based on observed IDs. Deferred inputs and decoders are tracked in the [TODO list](todo.md). +Every matching SWO, TB, and named-TB raw input is processed independently and sequentially for each selected trace-run +configuration. Formatted input distributes bytes to configured ITM routes by Trace Bus ID; unformatted input uses +one synthetic route. Other protocols require explicit decoder integration, not guesses based on observed IDs. +Deferred inputs and decoders are tracked in the [TODO list](todo.md). The architecture separates protocol decoding, semantic interpretation, and output generation. This keeps output formats independent of OpenCSD and allows another raw trace channel to reuse the event model and output backends. ## How it works at a glance -`ctrace` processes one solution set at a time. The [README](../README.md#trace-directory) describes how configuration, -raw input, and generated output files are grouped by their common base name. +`ctrace` processes one solution set at a time and runs a separate file job for each supported raw input in that set. +The [README](../README.md#trace-directory) describes how configuration, raw inputs, and generated outputs are grouped +by solution-set and channel names. The following path runs once per input, with fresh decoder and output state. The main in-memory path is: @@ -84,11 +86,11 @@ there is deliberately no common base class for all decode stages. Input selection and route binding belong to `tracerun` and complete before decoder or output construction. The provisional, ctrace-private `trace-format` declaration describes effective capture bytes rather than target -capability or file identity. Discovery first selects exactly one SWO, TB, or named-TB input, independently of whether -the format is declared. An explicit format wins; otherwise SWO defaults to `unformatted` and TB or named-TB to -`formatted`. The selected format is resolved before `CtraceRunMeta` normalizes routes, so the decoder and route model -use the same effective format. This channel-based heuristic does not inspect capture bytes or resolve ambiguous input -selection. Framing remains an internal decoder contract. +capability or file identity. Discovery collects every matching SWO, TB, and named-TB input, independently of whether +the format is declared. Each input is preflighted and normalized separately. An explicit format applies to every input; +otherwise SWO defaults to `unformatted` and TB or named-TB to `formatted`. The format is resolved before +`CtraceRunMeta` normalizes that input's routes, so the decoder and route model use the same effective format. This +channel-based heuristic does not inspect capture bytes. Framing remains an internal decoder contract. The [input constraints](constraints.md#input-format-framing-and-discovery) define the accepted declarations, defaults, file candidates, and framing limits. The [routing invariants](constraints.md#routing-invariants) define reference @@ -96,7 +98,7 @@ binding, valid IDs, and the synthetic unformatted route; backends preserve its l ## Processing state and ownership -One `DecodePipeline` is created for the selected raw file. It owns the OpenCSD adapter and Cortex-M stream decoder, so +One `DecodePipeline` is created for each raw file. It owns the OpenCSD adapter and Cortex-M stream decoder, so protocol, formatter, timestamp, and DWT state survive arbitrary file-read boundaries. `RawFileReader` owns a single 64 KiB buffer; each `RawByteView` borrows that buffer only for the synchronous `DecodePipeline::push` call. Calling `DecodePipeline::finish` flushes both the OpenCSD and Cortex-M layers before the pipeline is destroyed. @@ -104,7 +106,9 @@ protocol, formatter, timestamp, and DWT state survive arbitrary file-read bounda Both input formats use the same `OpenCSD DecodeTree` ownership boundary. `SINGLE` connects one synthetic, no-ATB-ID route to one ITM decoder. `FRAME_FORMATTED` owns the frame deformatter and one route-bound ITM decoder for each configured Trace Bus ID. Ctrace creates and feeds one tree at a time because OpenCSD's alternate logger and live-tree -registry use process-global state; the session restores the previously installed logger when it is destroyed. +registry use process-global state; the session restores the previously installed logger when it is destroyed. Each +file job finishes and releases its decoder before the next input starts. Route IDs and clocks remain local to that +input, so files can reuse Trace Bus IDs without sharing decoder state or output streams. Ownership is deliberately split by responsibility while preserving one enclosing lifetime: `OpenCsdTreeSession` owns the tree and configured decoder components. For formatted input, `OpenCsdFormattedItmSession` additionally owns @@ -204,8 +208,8 @@ byte. Packet rollback remains separate from the fatal-input output policy descri 1. Start at [`CtraceMain.cpp`](../src/CtraceMain.cpp) for command-line handling and top-level error policy. 2. Follow [`TraceDirectoryJob.cpp`](../src/control/TraceDirectoryJob.cpp) and - [`TraceRunDiscovery.cpp`](../src/tracerun/TraceRunDiscovery.cpp) to see how a solution set, YAML, and exactly one - SWO/TB input become a preflighted descriptor. + [`TraceRunDiscovery.cpp`](../src/tracerun/TraceRunDiscovery.cpp) to see how a solution set and YAML select all + matching SWO/TB inputs, each with its own preflighted descriptor and file job. 3. Read [`FileDecodeJob.cpp`](../src/control/FileDecodeJob.cpp) for output preflight, chunked input, pipeline construction, and finalization. 4. Continue through [`DecodePipeline.cpp`](../src/decode/DecodePipeline.cpp), @@ -281,7 +285,12 @@ Output requirements are evaluated per backend and selected route. For example, m active route may disable CTF while an independent CSV output remains valid; metadata on a route excluded by the stream filter is not required. `--all` therefore does not make the backends share failure state unnecessarily. -CSV writes one combined file in decode callback order. `CtfBundleOutput` owns a bundle-local metadata model and +Each input writes separate `..csv`, `..ctf`, and optional +`..traceanalysis.xml` artifacts. CTF always uses the channel-qualified path, including single-input +runs; existing `.ctf` bundles are not migrated or removed. The [CTF profile](ctf-format.md#files-and-common-structure) +records this intentional difference from the published bundle path. + +CSV writes one combined file per input in decode callback order. `CtfBundleOutput` owns a bundle-local metadata model and lazily creates a stream writer for each formatted route that emits selected events. Representation changes stay in the backends: for example, CSV retains a DWT/PMU counter mask in one row while CTF expands it into individual records. @@ -330,9 +339,11 @@ of creating routes or clocks, and CLI Info remains visible in CTF-only mode. The source warning remains separate from byte accounting. A route-bound missing-sync Error follows ordinary output selection but always contributes to command failure; its text does not repeat the byte count already reported as Info. -An invocation-wide diagnostic sink aggregates failures while other solution sets continue where possible, then -determines the final process status. Errors are rendered as `error` even when their impact causes a non-zero exit -status. Unhandled internal ctrace failures also terminate the command after an error diagnostic. +An invocation-wide diagnostic sink aggregates failures while remaining inputs in the same set and other solution +sets continue, then determines the final process status. Errors are rendered as `error` even when their impact causes +a non-zero exit status. Unhandled internal ctrace failures also terminate the command after an error diagnostic. +An input-scoped forwarding sink adds `inputChannel` and `input` context to every file-job diagnostic while preserving +its severity and failure impact. Producer reference annotations are reported once per configuration, before file jobs. ## External dependencies diff --git a/tools/ctrace/docs/architecture.svg b/tools/ctrace/docs/architecture.svg index b0e2f364a..c17ce2383 100644 --- a/tools/ctrace/docs/architecture.svg +++ b/tools/ctrace/docs/architecture.svg @@ -7,13 +7,15 @@ aria-labelledby="title description"> ctrace software architecture - ITM and DWT runtime data flow from command-line, trace-run YAML, and one selected raw input through a channel-based - format default or explicit override, normalized routes, mode-specific output planning, a SINGLE or formatted + ITM and DWT runtime data flow from command-line, trace-run YAML, and every matching raw input, processed sequentially + with independent state, through a channel-based format default or explicit override, normalized routes, + mode-specific output planning, a SINGLE or formatted OpenCSD DecodeTree, route-local recovery and Cortex-M state, semantic trace events, diagnostics, CSV, multi-stream CTF, and conditional Trace Compass XML output. Native errors, bounded packet previews, and recovery details reach diagnostics and CSV. Formatter and initial synchronization skips follow a separate Info path. Fatal decode retains committed CSV rows with a global - abort record and removes incomplete CTF and XML artifacts. + abort record and removes incomplete CTF and XML artifacts for that input. Remaining inputs continue. CSV and CTF + outputs always include the channel in their names, also for single-input runs. Only one OpenCSD tree is live at a time. @@ -87,14 +89,14 @@ tracerun - read YAML · select exactly one input - resolve format · normalize routes - preflight · deferred output values + read YAML · collect all SWO / TB inputs + per input: format · routes · preflight + deferred output values control TraceDirectoryJob · FileDecodeJob - descriptor · output plan · 64 KiB reads + sequential file jobs · continue after input failure diagnostics @@ -121,7 +123,7 @@ OpenCSD ITM adapter SINGLE / FRAME_FORMATTED tree - one ITM decoder per route + one live tree · one ITM decoder per route cursor · transactions · route reset @@ -168,11 +170,11 @@ CSV backend - one file · callback order + <set>.<channel>.csv · callback order events + skipped-byte Info - CTF bundle + <set>.<channel>.ctf legacy stream_0 eager · formatted lazy explicit route clocks · no fallback @@ -181,7 +183,7 @@ observed graphical topics only requires one retained clock domain - Fatal decode: CSV retains committed rows + global abort record; incomplete CTF / XML artifacts are removed. + Per input: retain aborted CSV rows + error record; remove incomplete CTF / XML. Remaining inputs continue. diff --git a/tools/ctrace/docs/constraints.md b/tools/ctrace/docs/constraints.md index 74ede089f..90256d3f6 100644 --- a/tools/ctrace/docs/constraints.md +++ b/tools/ctrace/docs/constraints.md @@ -38,15 +38,19 @@ configuration with trace communication, separate from trace-source setup. - Optional `ctrace-run.trace-format` accepts only `unformatted` or `formatted`. An explicit value overrides the channel-based default. Missing or null selects `unformatted` for SWO and `formatted` for TB or named-TB without an - Error. Discovery resolves this effective format before route normalization. -- Exactly one existing `.SWO.raw`, `.TB.raw`, or `.TB_.raw` must be selected, independently of a - format declaration. Zero or multiple candidates fail before decoder or output construction; SWO has no priority - over coexisting TB input. The selected input must be a regular, readable file and is opened during preflight, - before decoder or output construction. Event Recorder input remains diagnosed and excluded from the active candidate count. + Error. Discovery resolves this effective format separately for each input before route normalization. An explicit + override applies to every supported input associated with that trace-run configuration. +- Every existing `.SWO.raw`, `.TB.raw`, and `.TB_.raw` is processed independently and sequentially. + `--target` selects a solution set, including all its supported inputs. A set without an eligible input reports an error. + Each input must be a regular, readable file and is opened during its own preflight, before decoder or output + construction. Event Recorder input remains diagnosed and excluded from processing. +- Input preflight, route normalization, decoding, and output failures do not prevent processing the remaining inputs + or solution sets. Each input has independent decoder and output state; any failing input contributes to a non-zero + command exit status. A configuration read failure prevents processing that set but does not stop later sets. - The standardized `trace-buffer` selection belongs to solution/build-run producer configuration, not to the `*.ctrace-run.yml` file consumed by ctrace. The caller-facing selection and format/framing contract remains - follow-up work; this does not imply adding fields to `*.ctrace-run.yml`. Current discovery still requires one - unambiguous input and applies the channel-based defaults above. + follow-up work; this does not imply adding fields to `*.ctrace-run.yml`. Current discovery processes all supported + matching files and applies the channel-based defaults above. - Formatted input globally uses 16-byte memory-aligned CoreSight frames. Its length must be a multiple of 16, and it contains neither FSYNC nor HSYNC framing. Ctrace does not parse or emit a `trace-framing` YAML field; supporting another framing mode requires a public trace contract first. @@ -116,7 +120,11 @@ configuration with trace communication, separate from trace-source setup. ## Observable behavior and output safety -- CSV remains one combined file in decode callback order. The unformatted route has an empty `stream` field; +- Each raw input has separate `..csv`, `..ctf`, and optional + `..traceanalysis.xml` outputs. The channel-qualified CTF path applies even with one input; legacy + `.ctf` bundles are not reused, migrated, or removed. Aligning this intentional path change with the published + CTF specification remains [follow-up work](todo.md#inputs-and-time-correlation). +- CSV remains one combined file per input in decode callback order. The unformatted route has an empty `stream` field; formatted routes expose their architectural IDs. Type and stream filters affect output, not decoding or diagnostic reporting. The seventh CSV column is `address`, matching the published CMSIS-Toolbox [CSV schema](https://open-cmsis-pack.github.io/cmsis-toolbox/Experimental-Features/#csv-format). diff --git a/tools/ctrace/docs/ctf-format.md b/tools/ctrace/docs/ctf-format.md index 633039b1b..3d2af7236 100644 --- a/tools/ctrace/docs/ctf-format.md +++ b/tools/ctrace/docs/ctf-format.md @@ -24,6 +24,13 @@ cmsis_ctf_profile_version = 1 ## Files and common structure +Each raw input writes an independent `..ctf` bundle, where `` is `SWO`, `TB`, or +`TB_`. The channel is always included, even when only one input exists. Inputs are processed sequentially; +their metadata, stream IDs, clocks, and output cleanup are independent. Existing `.ctf` directories +are not reused, migrated, or removed. This intentional pathname change differs from the published +[file layout](https://open-cmsis-pack.github.io/cmsis-toolbox/Experimental-Features/#directory-and-file-structure), +which still names `.ctf`; specification alignment remains outstanding. + Every CTF bundle contains a `metadata` file and zero or more binary stream files. When selected, unformatted single-source input preserves the established layout: stream class `0` is written eagerly to `stream_0` and references `swo_clock`. Formatted input uses the generalized layout: each emitted Trace Bus route has its own stream class, @@ -83,7 +90,8 @@ A fatal OpenCSD decode abort removes the incomplete CTF bundle and its XML compa for healthy routes. If CSV output remains healthy, ctrace retains its selected rows and appends one input-wide `error` row with the processed-byte count and abort reason. That final row has no cycle timestamp, stream, or source and bypasses type and stream filters. A recoverable route-local error instead allows normal output completion, as described -under [`TRACE_STATUS`](#trace_status-event-id-3). Both kinds of Error produce a failing command exit status. +under [`TRACE_STATUS`](#trace_status-event-id-3). Both kinds of Error produce a failing command exit status. Remaining +raw inputs are still processed, and their completed bundles are unaffected by another input's failure. ## Event catalogue @@ -389,6 +397,10 @@ described shared domains once the input contract can establish one. The state-provider version is a deterministic hash of the generated XML contents. A semantic XML change therefore changes the version automatically and prevents a Trace Compass server from reusing stale analysis state. +Each XML companion describes its own CTF bundle. Generated XML files still share fixed analysis and view identifiers; +their simultaneous registration in one viewer is not guaranteed to be collision-free. Channel-qualified file names +do not provide a viewer namespace. Supporting concurrent registration is tracked as follow-up work. + ## Current profile boundaries - Formatted, memory-aligned CoreSight frames carrying ITM/DWT are decoded and represented as separate route streams. diff --git a/tools/ctrace/docs/multi-source-design.md b/tools/ctrace/docs/multi-source-design.md index 86ddf6685..e42ed5711 100644 --- a/tools/ctrace/docs/multi-source-design.md +++ b/tools/ctrace/docs/multi-source-design.md @@ -97,8 +97,9 @@ legacy CTF stream-class ID `0`. These representations do not turn it into CoreSi An observed formatter ID without a configured ITM route is diagnosed and skipped. Its presence cannot establish whether its bytes contain ITM or an instruction-trace protocol. This keeps unsupported sources from corrupting -supported routes. If simultaneous raw inputs are added later, route identity must also distinguish inputs because -different formatter domains can reuse the same Trace Bus ID. +supported routes. The original plan deferred simultaneous raw inputs and anticipated extending route identity with +input identity because different formatter domains can reuse the same Trace Bus ID. The later independent-file +processing described below keeps these domains separate through per-input jobs and outputs. ## Semantic state, output, and clocks @@ -189,8 +190,16 @@ Reproducible commands and supported consumer versions belong to the current The following changes supersede assumptions in the original plan: -- Discovery now considers SWO, TB, and named-TB together and requires exactly one input. An absent or null format - defaults by channel: SWO is unformatted, TB is formatted. Originally, omission selected only the legacy SWO path. +- Discovery initially expanded eligibility to SWO, TB, and named-TB while retaining the one-input limit. It now + processes all matching supported inputs independently and sequentially. Each input is normalized separately: + absent or null format defaults by channel, with SWO unformatted and TB formatted; an explicit override applies + to every input in the trace-run. Originally, omission selected only the legacy SWO path. +- Input failures contribute to command failure while remaining inputs and solution sets continue. Each file has + independent decoder state and outputs, with only one OpenCSD tree live at a time. `--target` still selects a + solution set, including all its supported inputs. +- CTF bundles now always use `..ctf`, also for single-input runs. CSV and XML retain their + channel-qualified paths. Existing `.ctf` bundles are not migrated or removed; the published CTF path + still needs alignment as recorded in the [CTF profile](ctf-format.md#files-and-common-structure). - Formatter skips and initial unsynchronized ITM bytes now produce non-failing byte-count Info annotations. They carry no invented route or time; a route receiving payload without a committed hardware sync reports an Error. - A fatal decoder abort retains committed CSV rows with an unfiltered input-wide abort record. Incomplete CTF/XML diff --git a/tools/ctrace/docs/todo.md b/tools/ctrace/docs/todo.md index 7007c215e..dff3a3520 100644 --- a/tools/ctrace/docs/todo.md +++ b/tools/ctrace/docs/todo.md @@ -14,13 +14,14 @@ [devtools #2573](https://github.com/Open-CMSIS-Pack/devtools/issues/2573). Keep trace communication separate from trace-source setup and decide how to replace the temporary `trace-format` override without assuming its standardization. +- [ ] Align the published CTF bundle path with ctrace's `..ctf` naming, including single-input runs. - [ ] Support FSYNC and FSYNC+HSYNC formatted input after a public framing contract is specified. - [ ] Define cross-stream clock correlation and offsets once a common producer time reference is available. - [ ] Add per-clock-domain Trace Compass bundles/experiments for uncorrelated streams when required. +- [ ] Namespace generated Trace Compass analysis/view identifiers for simultaneous registration of multiple XML files. ## Additional decoders -- [ ] Support multiple simultaneous named trace-buffer inputs after explicit file association is specified. - [ ] Add ETM/ETE/PTM instruction trace decoding and output in its own PR. - [ ] Add MTB instruction trace decoding and output in its own PR. - [ ] Add Event Recorder decoding and output when it enters the implementation scope. diff --git a/tools/ctrace/src/control/TraceDirectoryJob.cpp b/tools/ctrace/src/control/TraceDirectoryJob.cpp index 2e117c321..e5697733e 100644 --- a/tools/ctrace/src/control/TraceDirectoryJob.cpp +++ b/tools/ctrace/src/control/TraceDirectoryJob.cpp @@ -23,6 +23,30 @@ #include #include +/** @brief Adds raw-input identity to every diagnostic without changing its severity or failure impact. */ +class InputDiagnosticSink final : public DiagnosticSink { +public: + /** @brief Binds diagnostics to one independently processed capture. */ + InputDiagnosticSink(DiagnosticSink& target, const TraceRunRawInput& input) + : m_target(target), + m_context{{"inputChannel", input.channel}, {"input", input.path.string()}} + { + } + +protected: + /** @brief Forwards the original diagnostic with the capture context prepended. */ + void write(const Event& event) override + { + auto contextual = event; + contextual.context.insert(contextual.context.begin(), m_context.begin(), m_context.end()); + m_target.report(contextual); + } + +private: + DiagnosticSink& m_target; + std::vector> m_context; +}; + /** @brief Builds diagnostic context for one trace-run reference. */ static std::vector> referenceContext(const TraceRunConfig& config, const TraceRunReference& reference) @@ -122,7 +146,7 @@ void TraceDirectoryJob::processConfigFile(const std::filesystem::path& configFil }, }); reportConsumedReferenceDiagnostics(config, m_diagnostics); - auto input = TraceRunDiscovery::resolveInput(std::move(config), [&](const auto& rawInput) { + const auto inputs = TraceRunDiscovery::selectInputs(config, [&](const auto& rawInput) { m_diagnostics.report({ DiagnosticSink::Severity::Warning, "skipping raw trace channel excluded from active input selection", @@ -133,9 +157,9 @@ void TraceDirectoryJob::processConfigFile(const std::filesystem::path& configFil }, }); }); - reportTraceRunWarnings(input.metadata(), m_diagnostics); - FileDecodeJob fileJob(m_options, std::move(input), m_diagnostics); - fileJob.run(); + for (const auto& rawInput : inputs) { + processInput(config, rawInput); + } } catch (const std::exception& error) { m_diagnostics.report({ DiagnosticSink::Severity::Error, @@ -147,3 +171,16 @@ void TraceDirectoryJob::processConfigFile(const std::filesystem::path& configFil }); } } + +void TraceDirectoryJob::processInput(const TraceRunConfig& config, const TraceRunRawInput& rawInput) +{ + InputDiagnosticSink diagnostics(m_diagnostics, rawInput); + try { + auto input = TraceRunDiscovery::resolveInput(config, rawInput); + reportTraceRunWarnings(input.metadata(), diagnostics); + FileDecodeJob fileJob(m_options, std::move(input), diagnostics); + fileJob.run(); + } catch (const std::exception& error) { + diagnostics.report({DiagnosticSink::Severity::Error, error.what(), {{"config", config.path}}}); + } +} diff --git a/tools/ctrace/src/control/TraceDirectoryJob.h b/tools/ctrace/src/control/TraceDirectoryJob.h index 90f52bf70..989e8bc17 100644 --- a/tools/ctrace/src/control/TraceDirectoryJob.h +++ b/tools/ctrace/src/control/TraceDirectoryJob.h @@ -14,6 +14,9 @@ #include +struct TraceRunConfig; +struct TraceRunRawInput; + /** @brief Discovers and decodes the selected trace-run configurations in a directory. */ class TraceDirectoryJob { public: @@ -34,8 +37,10 @@ class TraceDirectoryJob { void run(); private: - /** @brief Reads, normalizes, and decodes one selected trace-run configuration. */ + /** @brief Reads one configuration and processes all its supported raw inputs. */ void processConfigFile(const std::filesystem::path& configFile); + /** @brief Isolates one input's preflight, decoding, outputs, and contextual diagnostics. */ + void processInput(const TraceRunConfig& config, const TraceRunRawInput& rawInput); CliOptions m_options; DiagnosticSink& m_diagnostics; diff --git a/tools/ctrace/src/output/OutputRequirements.cpp b/tools/ctrace/src/output/OutputRequirements.cpp index abea7f9be..cf25bfdcb 100644 --- a/tools/ctrace/src/output/OutputRequirements.cpp +++ b/tools/ctrace/src/output/OutputRequirements.cpp @@ -46,7 +46,7 @@ static OutputPaths outputPaths(const std::filesystem::path& rawInputPath) const auto outputDirectory = rawInputPath.parent_path(); auto csvPath = outputDirectory / captureName; csvPath += ".csv"; - auto ctfPath = outputDirectory / solutionSetName; + auto ctfPath = outputDirectory / captureName; ctfPath += ".ctf"; auto traceCompassXmlPath = outputDirectory / captureName; traceCompassXmlPath += ".traceanalysis.xml"; diff --git a/tools/ctrace/src/tracerun/TraceRunDiscovery.cpp b/tools/ctrace/src/tracerun/TraceRunDiscovery.cpp index 8fefbbc59..f7229a530 100644 --- a/tools/ctrace/src/tracerun/TraceRunDiscovery.cpp +++ b/tools/ctrace/src/tracerun/TraceRunDiscovery.cpp @@ -204,18 +204,18 @@ std::vector TraceRunDiscovery::rawInputs(const std::filesystem return inputs; } -TraceRunInputDescriptor TraceRunDiscovery::resolveInput(TraceRunConfig config, - const SkippedTraceRunInputSink& skippedInputSink) +std::vector TraceRunDiscovery::selectInputs(const TraceRunConfig& config, + const SkippedTraceRunInputSink& skippedInputSink) { if (config.path.empty()) { throw std::runtime_error("trace-run configuration has no source path"); } const std::filesystem::path configFile(config.path); const auto rawInputs = TraceRunDiscovery::rawInputs(configFile); - std::vector eligible; + std::vector eligible; for (const auto& rawInput : rawInputs) { if (isEligibleTraceChannel(rawInput.channel)) { - eligible.push_back(&rawInput); + eligible.push_back(rawInput); } else if (skippedInputSink) { skippedInputSink(rawInput); } @@ -225,15 +225,11 @@ TraceRunInputDescriptor TraceRunDiscovery::resolveInput(TraceRunConfig config, if (eligible.empty()) { throw std::runtime_error("no eligible raw trace input found for solution-set " + solutionSet); } - if (eligible.size() > 1U) { - std::string message = "multiple eligible raw trace inputs found for solution-set " + solutionSet + ":"; - for (const auto* rawInput : eligible) { - message += " " + rawInput->path.string(); - } - throw std::runtime_error(message); - } + return eligible; +} - const auto& selected = *eligible.front(); +TraceRunInputDescriptor TraceRunDiscovery::resolveInput(TraceRunConfig config, const TraceRunRawInput& selected) +{ if (!std::filesystem::is_regular_file(selected.path)) { throw std::runtime_error("raw trace input is not a regular file: " + selected.path.string()); } diff --git a/tools/ctrace/src/tracerun/TraceRunDiscovery.h b/tools/ctrace/src/tracerun/TraceRunDiscovery.h index e61d505fa..9a2225b0b 100644 --- a/tools/ctrace/src/tracerun/TraceRunDiscovery.h +++ b/tools/ctrace/src/tracerun/TraceRunDiscovery.h @@ -43,7 +43,7 @@ class TraceRunInputDescriptor { /** @brief Returns the selected raw-input path used for diagnostics and output naming. */ const std::filesystem::path& path() const noexcept; - /** @brief Returns the effective global byte format. */ + /** @brief Returns the effective byte format of this input. */ TraceRunFormat format() const noexcept; /** @brief Returns the normalized trace-run metadata and routes. */ const CtraceRunMeta& metadata() const noexcept; @@ -88,14 +88,22 @@ class TraceRunDiscovery final { */ static std::string solutionSetName(const std::filesystem::path& configFile); /** - * @brief Selects and preflights one raw input, resolves its format, and normalizes its routes. + * @brief Selects every supported raw input associated with one trace-run configuration. * @param config Parsed trace-run configuration with its source path and optional format override. * @param skippedInputSink Optional observer for recognized inputs excluded from selection. + * @return Deterministically ordered inputs for independent preflight and decoding. + * @throws std::runtime_error If the source path is missing or no eligible input exists. + */ + static std::vector selectInputs(const TraceRunConfig& config, + const SkippedTraceRunInputSink& skippedInputSink = {}); + /** + * @brief Preflights one selected raw input, resolves its format, and normalizes its routes. + * @param config Configuration copied so each input resolves its own effective format and routes. + * @param selected Input returned by selectInputs(). * @return Fully normalized input descriptor safe to pass to a decode job. - * @throws std::runtime_error If selection, file access, formatted alignment, or route metadata is invalid. + * @throws std::runtime_error If file access, formatted alignment, or route metadata is invalid. */ - static TraceRunInputDescriptor resolveInput(TraceRunConfig config, - const SkippedTraceRunInputSink& skippedInputSink = {}); + static TraceRunInputDescriptor resolveInput(TraceRunConfig config, const TraceRunRawInput& selected); private: /** @brief Discovers recognized raw inputs associated with one trace-run file. */ diff --git a/tools/ctrace/test/integration/README.md b/tools/ctrace/test/integration/README.md index f04c6b931..225180442 100644 --- a/tools/ctrace/test/integration/README.md +++ b/tools/ctrace/test/integration/README.md @@ -15,6 +15,15 @@ Fixture provenance and the scenarios covered by each checked-in capture and inline-generated formatted input are documented in the [test-data README](../data/README.md). +## Multiple input channels + +Inline-generated SWO, TB, and named-TB captures share one trace-run configuration +but carry distinct values and timestamps. Tests verify independent CSV, CTF, and +XML artifacts in every output mode, target selection, batch processing, and +type/stream filters. A failed channel must not prevent sibling channels or other +solution sets from completing. Preflight failures preserve existing artifacts; +legacy target-only CTF directories are not deleted. + ## Decode errors and retained output The formatted recovery tests verify that a damaged route can resynchronize @@ -75,8 +84,8 @@ analysis requests were repeated until their response status was `COMPLETED`. ### Single-clock DWT match -The generated `trace-match.ctf` and `trace-match.SWO.traceanalysis.xml` from -`ConvertsDwtMatchAcrossCsvAndCtf` were registered through: +The generated CTF and XML from `ConvertsDwtMatchAcrossCsvAndCtf` were registered through the following endpoints. +Their current output names are `trace-match.SWO.ctf` and `trace-match.SWO.traceanalysis.xml`: ```http POST /tsp/api/config/types/org.eclipse.tracecompass.tmf.core.config.xmlsourcetype/configs diff --git a/tools/ctrace/test/integration/src/CtraceIntegTests.cpp b/tools/ctrace/test/integration/src/CtraceIntegTests.cpp index a0082c7cb..9e53f7f59 100644 --- a/tools/ctrace/test/integration/src/CtraceIntegTests.cpp +++ b/tools/ctrace/test/integration/src/CtraceIntegTests.cpp @@ -290,14 +290,14 @@ void expectOnlyCtfEventIds(const std::filesystem::path& streamPath, void expectSyntheticArtifacts(const std::filesystem::path& directory, bool csvExpected, bool ctfExpected) { EXPECT_EQ(std::filesystem::is_regular_file(directory / "Synthetic.TB.csv"), csvExpected); - EXPECT_EQ(std::filesystem::is_directory(directory / "Synthetic.ctf"), ctfExpected); + EXPECT_EQ(std::filesystem::is_directory(directory / "Synthetic.TB.ctf"), ctfExpected); EXPECT_FALSE(std::filesystem::exists(directory / "Synthetic.TB.traceanalysis.xml")); if (!ctfExpected) { return; } std::vector files; - for (const auto& entry : std::filesystem::directory_iterator(directory / "Synthetic.ctf")) { + for (const auto& entry : std::filesystem::directory_iterator(directory / "Synthetic.TB.ctf")) { ASSERT_TRUE(entry.is_regular_file()) << entry.path(); files.push_back(entry.path().filename().string()); } @@ -334,6 +334,104 @@ std::size_t countCsvStreamRows(std::string_view csv, std::string_view stream) return count; } +/** @brief Gives each input channel distinct data and a fresh timestamp origin. */ +struct ChannelFixture { + std::string_view channel; + std::uint8_t value; + std::uint32_t cycles; +}; + +constexpr std::array kChannelFixtures{{ + {"SWO", 0x41U, 1U}, + {"TB", 0x42U, 2U}, + {"TB_ETB", 0x43U, 3U}, +}}; + +/** @brief Writes three independent captures sharing one processor configuration. */ +void writeMultipleChannelFixture(const std::filesystem::path& directory, std::string_view solutionSet, + bool nullFormat = false) +{ + auto traceRun = std::string("ctrace-run:\n"); + if (nullFormat) { + traceRun += " trace-format: null\n"; + } + traceRun += R"yml( ctrace-setup: + - pname: core + timestamps: + clock: 1000000 + itm-prescaler: 1 + ctrace-refs: + - { ctrace-ref: core/itm, type: itm, pname: core, stream: 1 } +)yml"; + writeTestFile(directory / (std::string(solutionSet) + ".ctrace-run.yml"), traceRun); + for (const auto& fixture : kChannelFixtures) { + auto raw = FormattedTraceTestSupport::itmHardwareSync(); + appendBytes(raw, FormattedTraceTestSupport::itmSoftwarePacket(1U, fixture.value)); + appendBytes(raw, FormattedTraceTestSupport::itmHardwarePacket(2U, 1U, 0U)); + appendBytes(raw, FormattedTraceTestSupport::itmLocalTimestampPacket(fixture.cycles)); + if (fixture.channel != "SWO") { + raw = FormattedTraceTestSupport::memoryAlignedFrames({{1U, raw}}); + } + const auto fileName = std::string(solutionSet) + "." + std::string(fixture.channel) + ".raw"; + writeTestFile(directory / fileName, {reinterpret_cast(raw.data()), raw.size()}); + } +} + +/** @brief Verifies that an independent channel kept its own semantic CTF records. */ +void expectChannelCtf(const std::filesystem::path& directory, const ChannelFixture& fixture) +{ + const bool formatted = fixture.channel != "SWO"; + const auto layout = formatted ? CtfStreamWriter::EventContextLayout::RouteLabeled + : CtfStreamWriter::EventContextLayout::Legacy; + const auto allRecords = CtfTestSupport::readCtfRecords(directory / (formatted ? "stream_1" : "stream_0"), layout); + std::vector records; + for (const auto& record : allRecords) { + EXPECT_EQ(record.traceBusId, formatted ? 1U : 0U); + if (record.id == CtfSchema::value(CtfSchema::EventId::Itm) || + record.id == CtfSchema::value(CtfSchema::EventId::PcSample)) { + records.push_back(record); + } + } + ASSERT_EQ(records.size(), 2U); + for (const auto& record : records) { + EXPECT_EQ(record.timestamp, fixture.cycles); + } + EXPECT_EQ(records[0U].id, CtfSchema::value(CtfSchema::EventId::Itm)); + ASSERT_EQ(records[0U].payload.size(), 8U); + EXPECT_EQ(records[0U].payload[0U], 1U); + EXPECT_EQ(records[0U].payload[2U], fixture.value); + EXPECT_EQ(records[1U].id, CtfSchema::value(CtfSchema::EventId::PcSample)); + ASSERT_FALSE(records[1U].payload.empty()); + EXPECT_EQ(records[1U].payload.front(), CtfSchema::value(CtfSchema::PcSampleState::Sleep)); +} + +/** @brief Checks channel-qualified artifacts and their distinct decoded content. */ +void expectChannelArtifacts(const std::filesystem::path& directory, std::string_view solutionSet, + const ChannelFixture& fixture, bool csv, bool ctf) +{ + SCOPED_TRACE(std::string(solutionSet) + "." + std::string(fixture.channel)); + const auto base = std::string(solutionSet) + "." + std::string(fixture.channel); + const auto csvPath = directory / (base + ".csv"); + const auto ctfPath = directory / (base + ".ctf"); + const auto xmlPath = directory / (base + ".traceanalysis.xml"); + EXPECT_EQ(std::filesystem::exists(csvPath), csv); + EXPECT_EQ(std::filesystem::exists(ctfPath), ctf); + EXPECT_EQ(std::filesystem::exists(xmlPath), ctf); + if (csv) { + const auto prefix = std::to_string(fixture.cycles) + (fixture.channel == "SWO" ? ",," : ",1,"); + std::ostringstream value; + value << std::hex << static_cast(fixture.value); + EXPECT_EQ(traceCsvRows(readTestTextFile(csvPath)), + "cycles,stream,type,source,value,pc,address,note\n" + prefix + "itm,1,0x" + value.str() + ",,,\n" + + prefix + "pcsample,,,,,CPU Sleeping\n"); + } + if (ctf) { + expectNonEmptyFile(ctfPath / "metadata"); + expectChannelCtf(ctfPath, fixture); + expectContains(readTestTextFile(xmlPath), "eventName=\"PC_SAMPLE\""); + } +} + /** @brief Uses the same marker payload as raw SWO or on two formatted Trace Bus routes. */ void writePcSamplingFixture(const std::filesystem::path& directory, const std::filesystem::path& fixtureDirectory, bool formatted) @@ -553,8 +651,8 @@ TEST_F(CtraceIntegTests, GeneratesCsvAndCtfWithoutEmptyGraphicalConfiguration) "0,,pcsample,,,0x08001234,,\n" "0,,itm,1,0x41,,,\n", readTestTextFile(workDirectory() / "Minimal.SWO.csv")); - expectNonEmptyFile(workDirectory() / "Minimal.ctf" / "metadata"); - expectNonEmptyFile(workDirectory() / "Minimal.ctf" / "stream_0"); + expectNonEmptyFile(workDirectory() / "Minimal.SWO.ctf" / "metadata"); + expectNonEmptyFile(workDirectory() / "Minimal.SWO.ctf" / "stream_0"); EXPECT_FALSE(std::filesystem::exists(workDirectory() / "Minimal.SWO.traceanalysis.xml")); } @@ -577,8 +675,8 @@ TEST_F(CtraceIntegTests, DecodesExplicitUnformattedNamedTraceBuffer) "0,,pcsample,,,0x08001234,,\n" "0,,itm,1,0x41,,,\n", readTestTextFile(workDirectory() / "Named.TB_MTB.csv")); - expectNonEmptyFile(workDirectory() / "Named.ctf" / "metadata"); - expectNonEmptyFile(workDirectory() / "Named.ctf" / "stream_0"); + expectNonEmptyFile(workDirectory() / "Named.TB_MTB.ctf" / "metadata"); + expectNonEmptyFile(workDirectory() / "Named.TB_MTB.ctf" / "stream_0"); EXPECT_FALSE(std::filesystem::exists(workDirectory() / "Named.TB_MTB.traceanalysis.xml")); } @@ -615,7 +713,7 @@ TEST_F(CtraceIntegTests, ExcludesUnformattedRawSwoWithoutRequiringClock) EXPECT_EQ(0, result.exitCode) << result.stderrText; expectNotContains(result.stderrText, "timestamps.clock"); - const auto ctfDirectory = workDirectory() / "Filtered.ctf"; + const auto ctfDirectory = workDirectory() / "Filtered.SWO.ctf"; const auto metadata = readTestTextFile(ctfDirectory / "metadata"); EXPECT_FALSE(metadata.empty()); expectNotContains(metadata, "\nclock {"); @@ -649,7 +747,7 @@ TEST_F(CtraceIntegTests, RejectsPartialFormattedFrameBeforeCreatingArtifacts) EXPECT_EQ(1, result.exitCode); expectContains(result.stderrText, "formatted raw trace input size must be a multiple of 16 bytes"); EXPECT_FALSE(std::filesystem::exists(directory / "Partial.TB.csv")); - EXPECT_FALSE(std::filesystem::exists(directory / "Partial.ctf")); + EXPECT_FALSE(std::filesystem::exists(directory / "Partial.TB.ctf")); EXPECT_FALSE(std::filesystem::exists(directory / "Partial.TB.traceanalysis.xml")); } } @@ -686,8 +784,8 @@ TEST_F(CtraceIntegTests, SkipsUnsupportedFormattedSourceOnceAndKeepsConfiguredRo traceCsvRows(readTestTextFile(workDirectory() / "Mixed.TB.csv"))); expectContains(readTestTextFile(workDirectory() / "Mixed.TB.csv"), ",42,info,,,,,4 bytes skipped for unconfigured source ID 42;"); - expectNonEmptyFile(workDirectory() / "Mixed.ctf" / "stream_1"); - EXPECT_FALSE(std::filesystem::exists(workDirectory() / "Mixed.ctf" / "stream_42")); + expectNonEmptyFile(workDirectory() / "Mixed.TB.ctf" / "stream_1"); + EXPECT_FALSE(std::filesystem::exists(workDirectory() / "Mixed.TB.ctf" / "stream_42")); EXPECT_FALSE(std::filesystem::exists(workDirectory() / "Mixed.TB.traceanalysis.xml")); } @@ -738,8 +836,8 @@ TEST_F(CtraceIntegTests, PublishesOutputsWithUnresolvedFormattedRouteRecovery) "0,1,error,,,,,\"ITM decoding did not resume before end of input at raw offset 16; " "no later hardware SYNC; affected raw interval [6, 16) spans 10 bytes; timestamp 0 .. unknown.\"\n", traceCsvRows(readTestTextFile(workDirectory() / "Invalid.TB.csv"))); - expectNonEmptyFile(workDirectory() / "Invalid.ctf" / "metadata"); - expectNonEmptyFile(workDirectory() / "Invalid.ctf" / "stream_1"); + expectNonEmptyFile(workDirectory() / "Invalid.TB.ctf" / "metadata"); + expectNonEmptyFile(workDirectory() / "Invalid.TB.ctf" / "stream_1"); EXPECT_FALSE(std::filesystem::exists(workDirectory() / "Invalid.TB.traceanalysis.xml")); } @@ -784,9 +882,9 @@ TEST_F(CtraceIntegTests, RecoversOneFormattedRouteWithoutLosingInterleavedOutput } expectNotContains(csv, ",2,itm,0,0x58"); expectNotContains(csv, ",1,error"); - expectNonEmptyFile(workDirectory() / "Recovery.ctf" / "metadata"); - expectNonEmptyFile(workDirectory() / "Recovery.ctf" / "stream_1"); - expectNonEmptyFile(workDirectory() / "Recovery.ctf" / "stream_2"); + expectNonEmptyFile(workDirectory() / "Recovery.TB.ctf" / "metadata"); + expectNonEmptyFile(workDirectory() / "Recovery.TB.ctf" / "stream_1"); + expectNonEmptyFile(workDirectory() / "Recovery.TB.ctf" / "stream_2"); EXPECT_FALSE(std::filesystem::exists(workDirectory() / "Recovery.TB.traceanalysis.xml")); } @@ -809,7 +907,7 @@ TEST_F(CtraceIntegTests, ReportsUnassignedFormatterOnlyInputWithoutInventingRout const auto result = run({"ctrace", workDirectory().string(), "--target", "Unassigned", "--all"}); EXPECT_EQ(0, result.exitCode) << result.stderrText; expectContains(result.stderrText, "[info] processed 16 input bytes in "); - expectContains(result.stderrText, "); trace/diagnostic records: 1\n"); + expectContains(result.stderrText, "); trace/diagnostic records: 1: inputChannel=TB,"); expectNotContains(result.stderrText, "[info] decoded "); expectContains(result.stderrText, "[info] 15 bytes skipped due to missing source ID; first formatter group at raw offset 0"); @@ -819,7 +917,7 @@ TEST_F(CtraceIntegTests, ReportsUnassignedFormatterOnlyInputWithoutInventingRout ",,info,,,,,15 bytes skipped due to missing source ID; " "first formatter group at raw offset 0\n", readTestTextFile(workDirectory() / "Unassigned.TB.csv")); - EXPECT_FALSE(std::filesystem::exists(workDirectory() / "Unassigned.ctf" / "stream_1")); + EXPECT_FALSE(std::filesystem::exists(workDirectory() / "Unassigned.TB.ctf" / "stream_1")); EXPECT_FALSE(std::filesystem::exists(workDirectory() / "Unassigned.TB.traceanalysis.xml")); } @@ -892,9 +990,9 @@ TEST_F(CtraceIntegTests, ReportsNeverSynchronizedFormattedRouteAndKeepsHealthyOu expectNotContains(csv, ",1,error,"); expectNotContains(csv, ",2,itm,"); expectNotContains(csv, ",sync,"); - expectNonEmptyFile(workDirectory() / "Synthetic.ctf" / "metadata"); - expectNonEmptyFile(workDirectory() / "Synthetic.ctf" / "stream_1"); - expectNonEmptyFile(workDirectory() / "Synthetic.ctf" / "stream_2"); + expectNonEmptyFile(workDirectory() / "Synthetic.TB.ctf" / "metadata"); + expectNonEmptyFile(workDirectory() / "Synthetic.TB.ctf" / "stream_1"); + expectNonEmptyFile(workDirectory() / "Synthetic.TB.ctf" / "stream_2"); } TEST_F(CtraceIntegTests, ReportsInitialRoutedByteSkipsAndDecodesAfterRealHardwareSync) @@ -929,9 +1027,9 @@ TEST_F(CtraceIntegTests, ReportsInitialRoutedByteSkipsAndDecodesAfterRealHardwar expectNotContains(csv, ",1,error,"); expectNotContains(csv, ",2,error,"); expectNotContains(csv, ",sync,"); - expectNonEmptyFile(workDirectory() / "Synthetic.ctf" / "metadata"); - expectNonEmptyFile(workDirectory() / "Synthetic.ctf" / "stream_1"); - expectNonEmptyFile(workDirectory() / "Synthetic.ctf" / "stream_2"); + expectNonEmptyFile(workDirectory() / "Synthetic.TB.ctf" / "metadata"); + expectNonEmptyFile(workDirectory() / "Synthetic.TB.ctf" / "stream_1"); + expectNonEmptyFile(workDirectory() / "Synthetic.TB.ctf" / "stream_2"); } TEST_F(CtraceIntegTests, ExpandsDwtEventCountersAcrossCsvAndCtf) @@ -952,8 +1050,8 @@ TEST_F(CtraceIntegTests, ExpandsDwtEventCountersAcrossCsvAndCtf) "0,,event,0,0x21,,,\n" "0,,itm,1,0x41,,,\n", readTestTextFile(workDirectory() / "Events.SWO.csv")); - expectContains(readTestTextFile(workDirectory() / "Events.ctf" / "metadata"), "name = \"DWT_EVENT\""); - expectNonEmptyFile(workDirectory() / "Events.ctf" / "stream_0"); + expectContains(readTestTextFile(workDirectory() / "Events.SWO.ctf" / "metadata"), "name = \"DWT_EVENT\""); + expectNonEmptyFile(workDirectory() / "Events.SWO.ctf" / "stream_0"); expectContains(readTestTextFile(workDirectory() / "Events.SWO.traceanalysis.xml"), "