From 14789ef162d0fa7d37158ff4ec6ce15a3bfccf8c Mon Sep 17 00:00:00 2001 From: Thorsten de Buhr Date: Mon, 31 Aug 2026 10:59:49 +0200 Subject: [PATCH 1/3] feat(ctrace): emit periodic PC samples Decode periodic PC and processor-sleep samples into CSV and compact CTF output. Add a Trace Compass sleep timeline and reject unsupported PC-sample payloads. --- tools/ctrace/docs/architecture.md | 9 +-- tools/ctrace/docs/todo.md | 1 - tools/ctrace/src/decode/DwtPacketDecoder.cpp | 27 +++++++- .../src/diagnostics/TraceIssueReporter.cpp | 1 + tools/ctrace/src/model/TraceEvent.h | 9 ++- tools/ctrace/src/model/TraceSelection.cpp | 6 ++ tools/ctrace/src/output/csv/CsvRowMapper.cpp | 4 ++ tools/ctrace/src/output/ctf/CtfEncoder.cpp | 23 +++++++ tools/ctrace/src/output/ctf/CtfEncoder.h | 2 + .../src/output/ctf/CtfMetadataWriter.cpp | 22 +++++++ tools/ctrace/src/output/ctf/CtfSchema.h | 15 +++++ .../src/output/ctf/TraceCompassXmlWriter.cpp | 64 +++++++++++++++++++ .../test/integration/src/CtraceIntegTests.cpp | 3 +- .../unit/src/decode/DecodePipelineTests.cpp | 22 +++++++ .../unit/src/decode/DwtPacketDecoderTests.cpp | 40 +++++++++++- .../unit/src/model/TraceSelectionTests.cpp | 6 ++ .../src/output/csv/CsvFileOutputTests.cpp | 4 +- .../unit/src/output/csv/CsvRowMapperTests.cpp | 7 ++ .../unit/src/output/ctf/CtfEncoderTests.cpp | 45 +++++++++++++ .../src/output/ctf/CtfMetadataWriterTests.cpp | 15 ++++- .../unit/src/output/ctf/CtfSchemaTests.cpp | 10 ++- .../ctrace/test/unit/support/CtfTestSupport.h | 7 ++ 22 files changed, 326 insertions(+), 16 deletions(-) diff --git a/tools/ctrace/docs/architecture.md b/tools/ctrace/docs/architecture.md index 3609119c0..57a5b03cd 100644 --- a/tools/ctrace/docs/architecture.md +++ b/tools/ctrace/docs/architecture.md @@ -13,10 +13,11 @@ backend-independent semantic events. Output backends consume these events to cre The first release profile supports SWO data containing ITM and DWT packets. The command line accepts the stable type names `itm`, `dwt`, `event`, `pmu`, `exception`, `pcsample`, `global_ts`, `overflow`, and `error`. Output semantics are -currently implemented for `itm`, `dwt`, `exception`, `global_ts`, `overflow`, and `error`. DWT event-counter and PMU -packets are retained internally but are not mapped to their selectors yet; periodic PC samples remain disabled. Trace -Bus input is discovered so that a complete trace directory can be inspected, but `*.TB.raw` files are reported and -skipped until a decoder is implemented. +currently implemented for `itm`, `dwt`, `exception`, `pcsample`, `global_ts`, `overflow`, and `error`. DWT event-counter +and PMU packets are retained internally but are not mapped to their selectors yet. Periodic PC samples reach CSV and +CTF as semantic events; the CTF event distinguishes a sampled PC from a processor-sleep indication, and Trace Compass +shows processor-sleep intervals as a timeline. Trace Bus input is discovered so that a complete trace directory can be +inspected, but `*.TB.raw` files are reported and skipped until a decoder is implemented. 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. diff --git a/tools/ctrace/docs/todo.md b/tools/ctrace/docs/todo.md index d736ac26c..f4a82d41c 100644 --- a/tools/ctrace/docs/todo.md +++ b/tools/ctrace/docs/todo.md @@ -12,7 +12,6 @@ - [ ] Add Armv8-M and Armv8.1-M DWT decoding. - [ ] Add `event` output in its own PR. - [ ] Add `pmu` output in its own PR. -- [ ] Add PC Sampling in its own PR after agreeing the sleep/`PC_SAMPLE` CTF contract. ## Multiple streams diff --git a/tools/ctrace/src/decode/DwtPacketDecoder.cpp b/tools/ctrace/src/decode/DwtPacketDecoder.cpp index c61b2a6c0..6e8d7885f 100644 --- a/tools/ctrace/src/decode/DwtPacketDecoder.cpp +++ b/tools/ctrace/src/decode/DwtPacketDecoder.cpp @@ -102,10 +102,31 @@ std::vector DwtPacketDecoder::decode(const DwtPayloadPacket& payload } if (source == DwtPacketSource::PeriodicPcSample) { - // PC samples need a dedicated output event. Until that event is - // defined, flush preceding data trace but do not expose the sample as - // an address event. output = flush(payload.quality, payload.tcyc); + const auto isPc = payload.size == 4U; + const auto isSleeping = payload.size == 1U && payload.value == 0U; + if (!isPc && !isSleeping) { + TraceEvent error{TraceIssueEvent{ + TraceIssueCode::UnsupportedDwtPcSamplePayload, + TraceIssueSeverity::Error, + "unsupported DWT PC-sample payload size " + std::to_string(payload.size) + + "; expected a 4-byte PC or 1-byte zero sleep indication", + std::nullopt, + std::nullopt, + }}; + error.index = payload.index; + error.traceBusId = payload.traceBusId; + error.tcyc = payload.tcyc; + error.quality = payload.quality; + output.push_back(std::move(error)); + return output; + } + TraceEvent packet{PcSampleTraceEvent{payload.value, isSleeping}}; + packet.index = payload.index; + packet.traceBusId = payload.traceBusId; + packet.tcyc = payload.tcyc; + packet.quality = payload.quality; + output.push_back(std::move(packet)); return output; } diff --git a/tools/ctrace/src/diagnostics/TraceIssueReporter.cpp b/tools/ctrace/src/diagnostics/TraceIssueReporter.cpp index 851f23a8e..4c4424c82 100644 --- a/tools/ctrace/src/diagnostics/TraceIssueReporter.cpp +++ b/tools/ctrace/src/diagnostics/TraceIssueReporter.cpp @@ -45,6 +45,7 @@ static std::string displayErrorMessage(const TraceEvent& event, const TraceIssue case TraceIssueCode::DecodeError: case TraceIssueCode::InvalidExceptionAction: case TraceIssueCode::UnsupportedDwtAddressPayload: + case TraceIssueCode::UnsupportedDwtPcSamplePayload: case TraceIssueCode::OpenCsdDecodeError: return atRawOffset("trace decode error", event); } diff --git a/tools/ctrace/src/model/TraceEvent.h b/tools/ctrace/src/model/TraceEvent.h index 49c414d3a..1d4d03f3d 100644 --- a/tools/ctrace/src/model/TraceEvent.h +++ b/tools/ctrace/src/model/TraceEvent.h @@ -40,6 +40,7 @@ enum class TraceIssueCode { DataLoss, InvalidExceptionAction, UnsupportedDwtAddressPayload, + UnsupportedDwtPcSamplePayload, OpenCsdDecodeError, OpenCsdBadPacketSequence, OpenCsdInvalidPacketHeader, @@ -135,6 +136,12 @@ struct PmuTraceEvent { std::uint32_t value = 0; }; +/** @brief Contains a periodic DWT PC sample or its processor-sleep indication. */ +struct PcSampleTraceEvent { + std::uint32_t pc = 0; + bool sleeping = false; +}; + /** @brief Marks a decoded local timestamp packet. */ struct LocalTimestampTraceEvent {}; @@ -163,7 +170,7 @@ struct TraceIssueEvent { /** @brief Stores the semantic payload of a decoded trace event. */ using TraceEventPayload = std::variant; /** @brief Describes timestamp and data-loss quality at an event. */ diff --git a/tools/ctrace/src/model/TraceSelection.cpp b/tools/ctrace/src/model/TraceSelection.cpp index bcacde28f..c766a5afc 100644 --- a/tools/ctrace/src/model/TraceSelection.cpp +++ b/tools/ctrace/src/model/TraceSelection.cpp @@ -54,6 +54,12 @@ static std::optional typeFor(const PmuTraceEvent&) return std::nullopt; } +/** @brief Exposes periodic DWT PC samples through their public output selector. */ +static std::optional typeFor(const PcSampleTraceEvent&) +{ + return TraceEventType::PcSample; +} + /** @brief Excludes local timestamp control packets from type selection. */ static std::optional typeFor(const LocalTimestampTraceEvent&) { diff --git a/tools/ctrace/src/output/csv/CsvRowMapper.cpp b/tools/ctrace/src/output/csv/CsvRowMapper.cpp index 65381c9c7..e4f9b6fd2 100644 --- a/tools/ctrace/src/output/csv/CsvRowMapper.cpp +++ b/tools/ctrace/src/output/csv/CsvRowMapper.cpp @@ -159,6 +159,10 @@ static CsvRow eventToCsvRow(const TraceEvent& event) } else if (const auto* exception = traceEventPayload(event)) { row[column(CsvColumn::Source)] = std::to_string(exception->number); row[column(CsvColumn::Value)] = exceptionActionCsvValue(exception->action); + } else if (const auto* sample = traceEventPayload(event)) { + if (!sample->sleeping) { + row[column(CsvColumn::Pc)] = hexValue(sample->pc, 4); + } } else if (const auto* timestamp = traceEventPayload(event)) { row[column(CsvColumn::Cycles)] = std::to_string(timestamp->value); } else if (const auto* overflow = traceEventPayload(event)) { diff --git a/tools/ctrace/src/output/ctf/CtfEncoder.cpp b/tools/ctrace/src/output/ctf/CtfEncoder.cpp index cd6427f58..8fa4d69f1 100644 --- a/tools/ctrace/src/output/ctf/CtfEncoder.cpp +++ b/tools/ctrace/src/output/ctf/CtfEncoder.cpp @@ -200,6 +200,10 @@ void CtfEncoder::writeEvent(const TraceEvent& event) if (selected) { writeDwtAddrEvent(event, *address); } + } else if (const auto* sample = traceEventPayload(event)) { + if (selected) { + writePcSampleEvent(event, *sample); + } } else if (isTraceEvent(event)) { auto& streamState = m_streamStates[event.traceBusId]; if (event.quality.has_value()) { @@ -231,6 +235,25 @@ void CtfEncoder::writeEvent(const TraceEvent& event) } } +void CtfEncoder::writePcSampleEvent(const TraceEvent& event, const PcSampleTraceEvent& sample) +{ + const auto pcSize = sample.sleeping ? 0U : 4U; + const auto payloadSize = 1U + pcSize + 1U + 4U; + const auto eventTimestamp = allocateEventTimestamp(event.traceBusId); + const auto quality = computeSampleQuality(event); + const auto state = CtfSchema::value(sample.sleeping ? CtfSchema::PcSampleState::Sleep + : CtfSchema::PcSampleState::Pc); + m_stream.writeRecord(CtfSchema::value(CtfSchema::EventId::PcSample), eventTimestamp, event.traceBusId, payloadSize, + [&](CtfStreamWriter::Record& record) { + record.writeU8(state); + if (!sample.sleeping) { + record.writeU32(sample.pc); + } + record.writeU8(quality.first); + record.writeU32(quality.second); + }); +} + std::uint64_t CtfEncoder::allocateEventTimestamp(std::uint8_t traceBusId) { // CtfStreamWriter applies the final monotonic clamp across the multiplexed diff --git a/tools/ctrace/src/output/ctf/CtfEncoder.h b/tools/ctrace/src/output/ctf/CtfEncoder.h index d7659eb39..b183cfbff 100644 --- a/tools/ctrace/src/output/ctf/CtfEncoder.h +++ b/tools/ctrace/src/output/ctf/CtfEncoder.h @@ -75,6 +75,8 @@ class CtfEncoder final { void reportDwtSizeMismatch(const TraceEvent& event, const DwtDataTraceEvent& data, const ResolvedTraceSource* source); /** @brief Encodes one DWT address event. */ void writeDwtAddrEvent(const TraceEvent& event, const DwtAddressTraceEvent& address); + /** @brief Encodes one periodic PC-sample or processor-sleep event. */ + void writePcSampleEvent(const TraceEvent& event, const PcSampleTraceEvent& sample); /** @brief Encodes one reconstructed global timestamp event. */ void writeGlobalTimestampEvent(const TraceEvent& event, const GlobalTimestampTraceEvent& timestamp); /** @brief Applies one exception transition to its CTF lane state. */ diff --git a/tools/ctrace/src/output/ctf/CtfMetadataWriter.cpp b/tools/ctrace/src/output/ctf/CtfMetadataWriter.cpp index 9632103e1..e10dcbcdd 100644 --- a/tools/ctrace/src/output/ctf/CtfMetadataWriter.cpp +++ b/tools/ctrace/src/output/ctf/CtfMetadataWriter.cpp @@ -418,6 +418,27 @@ event { )"; } +/** @brief Writes the periodic PC-sample event declaration. */ +static void writePcSampleEvent(std::ostream& out) +{ + out << R"( +event { + id = )" + << CtfSchema::value(CtfSchema::EventId::PcSample) << R"(; + name = ")" + << CtfSchema::eventName(CtfSchema::EventId::PcSample) << R"("; + stream_id = )" + << CtfSchema::SwoStreamId << R"(; + fields := struct { + uint8_t cmsis_pc_sample_state; + uint32_t cmsis_pc[cmsis_pc_sample_state]; + uint8_t cmsis_sample_flags; + uint32_t cmsis_overflow_count; + }; +}; +)"; +} + /** @brief Writes status, exception, and global timestamp declarations. */ static void writeStatusEvents(std::ostream& out) { @@ -482,6 +503,7 @@ void CtfMetadataWriter::write(const std::filesystem::path& outputDir, const std: writeDwtValueEvent(out); writeDwtAddressEvent(out); writeStatusEvents(out); + writePcSampleEvent(out); out.close(); if (!out) { throw std::runtime_error("Failed to write CTF metadata " + metadataPath.string()); diff --git a/tools/ctrace/src/output/ctf/CtfSchema.h b/tools/ctrace/src/output/ctf/CtfSchema.h index dee4dd6f9..cd8a3c975 100644 --- a/tools/ctrace/src/output/ctf/CtfSchema.h +++ b/tools/ctrace/src/output/ctf/CtfSchema.h @@ -26,6 +26,7 @@ enum class EventId : std::uint32_t { TraceStatus = 3U, Exception = 4U, GlobalTimestamp = 5U, + PcSample = 6U, }; /** @brief Classifies CTF trace-status records. */ @@ -49,6 +50,12 @@ enum class ExceptionAction : std::uint8_t { Exited = 1U, }; +/** @brief Identifies whether a periodic PC sample carries a PC or reports processor sleep. */ +enum class PcSampleState : std::uint8_t { + Sleep = 0U, + Pc = 1U, +}; + /** @brief Identifies the supported CTF sample value encodings. */ enum class ValueTag : std::uint8_t { Signed8 = 0U, @@ -147,6 +154,12 @@ constexpr std::uint8_t value(ExceptionAction action) return static_cast(action); } +/** @brief Returns the integer representation of a PC-sample state. */ +constexpr std::uint8_t value(PcSampleState state) +{ + return static_cast(state); +} + /** @brief Returns the stable schema name of a CTF event ID. */ constexpr std::string_view eventName(EventId id) { @@ -163,6 +176,8 @@ constexpr std::string_view eventName(EventId id) return "EXCEPTION"; case EventId::GlobalTimestamp: return "GLOBAL_TIMESTAMP"; + case EventId::PcSample: + return "PC_SAMPLE"; } return "UNKNOWN"; } diff --git a/tools/ctrace/src/output/ctf/TraceCompassXmlWriter.cpp b/tools/ctrace/src/output/ctf/TraceCompassXmlWriter.cpp index 2de1a1ce3..bc4cd97f8 100644 --- a/tools/ctrace/src/output/ctf/TraceCompassXmlWriter.cpp +++ b/tools/ctrace/src/output/ctf/TraceCompassXmlWriter.cpp @@ -79,6 +79,8 @@ static std::string valueHandlers(CtfSchema::EventId eventId, const char* prefix, /** @brief Generates the Trace Compass state-provider definition. */ static std::string stateProviderXml() { + // Numeric time-graph states are exposed as TSP style keys; string states are + // serialized without a style and appear as gaps in compatible clients. std::ostringstream xml; xml << R"( )"; @@ -187,6 +243,14 @@ static std::string viewsXml() << CtfSchema::eventName(CtfSchema::EventId::TraceStatus) << R"(/*" displayText="true"> + + )"; return xml.str(); } diff --git a/tools/ctrace/test/integration/src/CtraceIntegTests.cpp b/tools/ctrace/test/integration/src/CtraceIntegTests.cpp index 48de3c7e9..d180e588f 100644 --- a/tools/ctrace/test/integration/src/CtraceIntegTests.cpp +++ b/tools/ctrace/test/integration/src/CtraceIntegTests.cpp @@ -125,12 +125,13 @@ TEST_F(CtraceIntegTests, GeneratesAllOutputs) ctrace-refs: [] )yml"); - const std::string raw{"\0\0\0\0\0\x80\x09\x41", 8U}; + const std::string raw{"\0\0\0\0\0\x80\x17\x34\x12\x00\x08\x09\x41", 13U}; writeFile(workDirectory() / "Minimal.SWO.raw", raw); const auto result = run({"ctrace", workDirectory().string(), "--target", "Minimal", "--all"}); EXPECT_EQ(0, result.exitCode) << result.stderrText; EXPECT_EQ("cycles,stream,type,source,value,pc,offset,note\n" + "0,,pcsample,,,0x08001234,,\n" "0,,itm,1,0x41,,,\n", readTextFile(workDirectory() / "Minimal.SWO.csv")); expectNonEmptyFile(workDirectory() / "Minimal.ctf" / "metadata"); diff --git a/tools/ctrace/test/unit/src/decode/DecodePipelineTests.cpp b/tools/ctrace/test/unit/src/decode/DecodePipelineTests.cpp index b15d8e3c3..7964fe09a 100644 --- a/tools/ctrace/test/unit/src/decode/DecodePipelineTests.cpp +++ b/tools/ctrace/test/unit/src/decode/DecodePipelineTests.cpp @@ -581,6 +581,28 @@ TEST(CtraceUnitTests, testDecodePipelinePreservesDwtEventAndPmuPackets) ASSERT_TRUE(foundPmu) << "OpenCSD PMU-overflow packet must survive post-decoding"; } +TEST(CtraceUnitTests, testDecodePipelinePreservesPeriodicPcSamples) +{ + const std::uint8_t trace[] = { + 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x80U, + 0x17U, 0x34U, 0x12U, 0x00U, 0x08U, + 0x15U, 0x00U, + }; + const auto decoded = decodeTrace({rawBytes(trace)}); + + std::vector samples; + for (const auto& event : decoded.events) { + if (const auto* sample = traceEventPayload(event)) { + samples.push_back(*sample); + } + } + ASSERT_EQ(samples.size(), 2U) << "OpenCSD periodic PC-sample packet count mismatch"; + EXPECT_EQ(samples[0].pc, 0x08001234U) << "OpenCSD periodic PC sample payload mismatch"; + EXPECT_FALSE(samples[0].sleeping) << "OpenCSD periodic PC sample payload mismatch"; + EXPECT_EQ(samples[1].pc, 0U) << "OpenCSD periodic PC sleep indication mismatch"; + EXPECT_TRUE(samples[1].sleeping) << "OpenCSD periodic PC sleep indication mismatch"; +} + TEST(CtraceUnitTests, testDecodePipelineDoesNotInjectSync) { const std::uint8_t validWithoutAsync[] = {0x01U, static_cast('A')}; diff --git a/tools/ctrace/test/unit/src/decode/DwtPacketDecoderTests.cpp b/tools/ctrace/test/unit/src/decode/DwtPacketDecoderTests.cpp index 347361757..2ed733ab4 100644 --- a/tools/ctrace/test/unit/src/decode/DwtPacketDecoderTests.cpp +++ b/tools/ctrace/test/unit/src/decode/DwtPacketDecoderTests.cpp @@ -25,14 +25,50 @@ static DwtPayloadPacket dwtPayload(std::uint8_t discriminator, std::uint8_t size return {index, traceBusId, discriminator, size, value, tcyc, {}}; } -TEST(CtraceUnitTests, testDwtPcSampleIsSuppressedUntilDedicatedEventExists) +TEST(CtraceUnitTests, testDwtPcSampleProducesDedicatedEvent) { DwtPacketDecoder decoder; auto payload = dwtPayload(2U, 4U, 0x08001234U, 19U, 3U, 949339000U); payload.quality.timestampReliable = true; const auto packets = decoder.decode(payload); - ASSERT_TRUE(packets.empty()) << "DWT PC samples must remain suppressed until their output event is defined"; + ASSERT_EQ(packets.size(), 1U) << "DWT PC sample event count mismatch"; + const auto* sample = traceEventPayload(packets.front()); + ASSERT_NE(sample, nullptr); + EXPECT_EQ(sample->pc, 0x08001234U) << "DWT PC sample payload mismatch"; + EXPECT_FALSE(sample->sleeping) << "DWT PC sample payload mismatch"; + EXPECT_EQ(packets.front().index, 19U) << "DWT PC sample identity mismatch"; + EXPECT_EQ(packets.front().traceBusId, 3U) << "DWT PC sample identity mismatch"; + EXPECT_EQ(packets.front().tcyc, std::optional(949339000U)) + << "DWT PC sample identity mismatch"; + ASSERT_TRUE(packets.front().quality.has_value()) << "DWT PC sample quality mismatch"; + EXPECT_TRUE(packets.front().quality->timestampReliable) << "DWT PC sample quality mismatch"; + EXPECT_EQ(traceEventType(packets.front()), TraceEventType::PcSample) + << "DWT PC sample selector mapping mismatch"; +} + +TEST(CtraceUnitTests, testDwtPcSamplePreservesProcessorSleep) +{ + DwtPacketDecoder decoder; + const auto packets = decoder.decode(dwtPayload(2U, 1U, 0U, 20U, 4U, 949339100U)); + ASSERT_EQ(packets.size(), 1U) << "DWT PC sleep event count mismatch"; + const auto* sample = traceEventPayload(packets.front()); + ASSERT_NE(sample, nullptr); + EXPECT_EQ(sample->pc, 0U) << "DWT PC sleep indication mismatch"; + EXPECT_TRUE(sample->sleeping) << "DWT PC sleep indication mismatch"; +} + +TEST(CtraceUnitTests, testDwtPcSampleRejectsUnsupportedPayloads) +{ + for (const auto payload : {dwtPayload(2U, 1U, 1U), dwtPayload(2U, 2U, 0x1234U)}) { + DwtPacketDecoder decoder; + const auto packets = decoder.decode(payload); + ASSERT_EQ(packets.size(), 1U) << "unsupported DWT PC sample must emit one error"; + const auto* issue = traceEventPayload(packets.front()); + ASSERT_NE(issue, nullptr); + EXPECT_EQ(issue->code, TraceIssueCode::UnsupportedDwtPcSamplePayload) + << "unsupported DWT PC sample error mismatch"; + } } TEST(CtraceUnitTests, testDwtCounterPacketsArePreservedUntilOutputSemanticsExist) diff --git a/tools/ctrace/test/unit/src/model/TraceSelectionTests.cpp b/tools/ctrace/test/unit/src/model/TraceSelectionTests.cpp index 9d95870da..3c98264f2 100644 --- a/tools/ctrace/test/unit/src/model/TraceSelectionTests.cpp +++ b/tools/ctrace/test/unit/src/model/TraceSelectionTests.cpp @@ -70,4 +70,10 @@ TEST(CtraceUnitTests, testTraceSelection) TraceEvent exception{ExceptionTraceEvent{11U, ExceptionAction::Entered}}; ASSERT_TRUE(traceEventSelectedForOutput(exception, TraceSelection{{"exception"}, {}})) << "TraceSelection exception type mismatch"; + + TraceEvent pcSample{PcSampleTraceEvent{0x08001234U, false}}; + ASSERT_TRUE(traceEventSelectedForOutput(pcSample, TraceSelection{{"pcsample"}, {}})) + << "TraceSelection PC-sample type mismatch"; + ASSERT_FALSE(traceEventSelectedForOutput(pcSample, TraceSelection{{"dwt"}, {}})) + << "TraceSelection must keep PC samples separate from DWT data trace"; } diff --git a/tools/ctrace/test/unit/src/output/csv/CsvFileOutputTests.cpp b/tools/ctrace/test/unit/src/output/csv/CsvFileOutputTests.cpp index 1980ca2e1..0a7e99e56 100644 --- a/tools/ctrace/test/unit/src/output/csv/CsvFileOutputTests.cpp +++ b/tools/ctrace/test/unit/src/output/csv/CsvFileOutputTests.cpp @@ -133,15 +133,17 @@ TEST(CtraceUnitTests, testCsvFileOutputMatchesSpecification) }}, 949338400U)); output.writeEvent(atCycle(TraceEvent{ExceptionTraceEvent{11U, ExceptionAction::Entered}}, 950364820U)); + output.writeEvent(atCycle(TraceEvent{PcSampleTraceEvent{0x08000100U, false}}, 950364900U)); output.stop(); const auto lines = readTestLines(csvPath); - ASSERT_TRUE(lines.size() == 3U) << "CSV specification row count mismatch"; + ASSERT_TRUE(lines.size() == 4U) << "CSV specification row count mismatch"; ASSERT_TRUE(lines[0] == "cycles,stream,type,source,value,pc,offset,note") << "CSV specification header mismatch"; ASSERT_TRUE(lines[1] == "949338400,,dwt,2,0xfffffdf9,0x08001234,0xfdf9,") << "CSV DWT row schema mismatch"; ASSERT_TRUE(lines[2] == "950364820,,exception,11,0x1,,,") << "CSV exception state schema mismatch"; + ASSERT_TRUE(lines[3] == "950364900,,pcsample,,,0x08000100,,") << "CSV PC-sample row schema mismatch"; } TEST(CtraceUnitTests, testCsvFileOutputWritesTraceIssues) diff --git a/tools/ctrace/test/unit/src/output/csv/CsvRowMapperTests.cpp b/tools/ctrace/test/unit/src/output/csv/CsvRowMapperTests.cpp index bc5733fb8..a7dd0fb47 100644 --- a/tools/ctrace/test/unit/src/output/csv/CsvRowMapperTests.cpp +++ b/tools/ctrace/test/unit/src/output/csv/CsvRowMapperTests.cpp @@ -42,6 +42,7 @@ TEST(CtraceUnitTests, testCsvRowMapperAndTraceEventSchema) {TraceEvent{ExceptionTraceEvent{}}, TraceEventType::Exception}, {TraceEvent{DwtEventTraceEvent{}}, std::nullopt}, {TraceEvent{PmuTraceEvent{}}, std::nullopt}, + {TraceEvent{PcSampleTraceEvent{}}, TraceEventType::PcSample}, {TraceEvent{LocalTimestampTraceEvent{}}, std::nullopt}, {TraceEvent{GlobalTimestampTraceEvent{}}, TraceEventType::GlobalTimestamp}, {TraceEvent{OverflowTraceEvent{}}, TraceEventType::Overflow}, @@ -64,6 +65,12 @@ TEST(CtraceUnitTests, testCsvRowMapperAndTraceEventSchema) << "CSV must render the raw hexadecimal DWT value with the two-byte SWO width"; ASSERT_TRUE(CsvRowMapper::row(softwarePacket(1U)) == ",,itm,1,0x00,,,") << "CSV must leave the stream field empty for unformatted input"; + ASSERT_TRUE(CsvRowMapper::row(atCycle(TraceEvent{PcSampleTraceEvent{0x08001234U, false}}, 949339000U)) == + "949339000,,pcsample,,,0x08001234,,") + << "CSV PC-sample row mismatch"; + ASSERT_TRUE(CsvRowMapper::row(atCycle(TraceEvent{PcSampleTraceEvent{0U, true}}, 949339100U)) == + "949339100,,pcsample,,,,,") + << "CSV PC-sample sleep row mismatch"; } TEST(CtraceUnitTests, testCsvRowMapperCoversAddressAndExceptionVariants) diff --git a/tools/ctrace/test/unit/src/output/ctf/CtfEncoderTests.cpp b/tools/ctrace/test/unit/src/output/ctf/CtfEncoderTests.cpp index 2334208c5..82f55d6b4 100644 --- a/tools/ctrace/test/unit/src/output/ctf/CtfEncoderTests.cpp +++ b/tools/ctrace/test/unit/src/output/ctf/CtfEncoderTests.cpp @@ -98,6 +98,51 @@ TEST(CtraceUnitTests, testCtfEncoderWritesOnlyIntoProvidedDirectory) << "CtfEncoder abort must not delete a directory owned by its caller"; } +TEST(CtraceUnitTests, testCtfEncoderPcSampleEncoding) +{ + const TemporaryTestPath temporaryPath("ctrace-ctf-pc-sample-test"); + const auto& outputDirectory = temporaryPath.createDirectory(); + + CtfEncoder encoder(CtfEncoderConfig{ + 1000000U, + TraceSelection{{"pcsample"}, {}}, + {}, + }); + encoder.start(outputDirectory); + auto pc = onStream(atCycle(TraceEvent{PcSampleTraceEvent{0x08001234U, false}}, 10U), 3U); + pc.quality = TraceQuality{false, true, 0U}; + encoder.writeEvent(pc); + auto sleep = onStream(atCycle(TraceEvent{PcSampleTraceEvent{0x12345678U, true}}, 11U), 3U); + sleep.quality = TraceQuality{true, false, 7U}; + encoder.writeEvent(sleep); + encoder.stop(); + + const auto records = readCtfRecords(outputDirectory / "stream_0"); + ASSERT_EQ(records.size(), 2U); + for (const auto& record : records) { + EXPECT_EQ(record.id, CtfSchema::value(CtfSchema::EventId::PcSample)); + EXPECT_EQ(record.traceBusId, 3U); + } + ASSERT_EQ(records[0].payload.size(), 10U); + EXPECT_EQ(records[0].timestamp, 10U); + EXPECT_EQ(records[0].payload[0U], CtfSchema::value(CtfSchema::PcSampleState::Pc)); + EXPECT_EQ(readLe32(records[0].payload, 1U), 0x08001234U); + EXPECT_EQ(records[0].payload[5U], CtfSchema::SampleFlagTimestampReliable); + EXPECT_EQ(readLe32(records[0].payload, 6U), 0U); + + ASSERT_EQ(records[1].payload.size(), 6U); + EXPECT_EQ(records[1].timestamp, 11U); + EXPECT_EQ(records[1].payload[0U], CtfSchema::value(CtfSchema::PcSampleState::Sleep)); + EXPECT_EQ(records[1].payload[1U], CtfSchema::SampleFlagOverflow); + EXPECT_EQ(readLe32(records[1].payload, 2U), 7U); + + const auto metadata = readTestTextFile(outputDirectory / "metadata"); + EXPECT_NE(metadata.find("name = \"PC_SAMPLE\""), std::string::npos); + EXPECT_NE(metadata.find("uint8_t cmsis_pc_sample_state"), std::string::npos); + EXPECT_EQ(metadata.find("cmsis_pc_sample_state_t"), std::string::npos); + EXPECT_NE(metadata.find("uint32_t cmsis_pc[cmsis_pc_sample_state]"), std::string::npos); +} + TEST(CtraceUnitTests, testCtfEncoderPacketBoundaryAndUuid) { const TemporaryTestPath temporaryPath("ctrace-ctf-encoder-packet-boundary-test"); diff --git a/tools/ctrace/test/unit/src/output/ctf/CtfMetadataWriterTests.cpp b/tools/ctrace/test/unit/src/output/ctf/CtfMetadataWriterTests.cpp index 8f1aec669..b39735cf3 100644 --- a/tools/ctrace/test/unit/src/output/ctf/CtfMetadataWriterTests.cpp +++ b/tools/ctrace/test/unit/src/output/ctf/CtfMetadataWriterTests.cpp @@ -82,14 +82,25 @@ TEST(CtraceUnitTests, testTraceCompassXmlUsesCurrentCtfEvents) const TemporaryTestPath path("ctrace-trace-compass-schema.xml"); TraceCompassXmlWriter::writeFile(path.path()); const auto xml = readTestTextFile(path.path()); - constexpr std::array visualizedEvents{ + constexpr std::array visualizedEvents{ CtfSchema::EventId::Itm, CtfSchema::EventId::DwtValue, CtfSchema::EventId::DwtAddress, - CtfSchema::EventId::Exception, CtfSchema::EventId::TraceStatus, + CtfSchema::EventId::Exception, CtfSchema::EventId::TraceStatus, CtfSchema::EventId::PcSample, }; for (const auto eventId : visualizedEvents) { EXPECT_NE(xml.find("eventName=\"" + std::string(CtfSchema::eventName(eventId)) + "\""), std::string::npos); } + EXPECT_NE(xml.find("value=\"cmsis_pc_sample_state\""), std::string::npos); + EXPECT_NE(xml.find(""), std::string::npos); + EXPECT_NE(xml.find(""), std::string::npos); + EXPECT_EQ(xml.find(""), std::string::npos); + EXPECT_NE(xml.find("