| // Copyright 2025 The Pigweed Authors |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); you may not |
| // use this file except in compliance with the License. You may obtain a copy of |
| // the License at |
| // |
| // https://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT |
| // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the |
| // License for the specific language governing permissions and limitations under |
| // the License. |
| |
| #include "pw_trace_tokenized/decoder.h" |
| |
| #include <limits> |
| #include <optional> |
| #include <string_view> |
| #include <type_traits> |
| |
| #include "lib/stdcompat/utility.h" |
| #include "pw_bytes/endian.h" |
| #include "pw_bytes/span.h" |
| #include "pw_log/log.h" |
| #include "pw_status/status.h" |
| #include "pw_status/try.h" |
| #include "pw_stream/memory_stream.h" |
| #include "pw_varint/stream.h" |
| |
| namespace pw::trace { |
| namespace { |
| |
| using cpp23::to_underlying; |
| |
| constexpr std::string_view kDomain = "trace"; |
| |
| // Token string: "event_type|flag|module|group|label|<optional DATA_FMT>" |
| enum class TokenIdx { |
| kEventType = 0, |
| kFlag = 1, |
| kModule = 2, |
| kGroup = 3, |
| kLabel = 4, |
| kDataFmt = 5, // optional |
| }; |
| |
| template <typename T> |
| pw::Result<T> ReadInt(stream::Reader& reader) { |
| static_assert(std::is_trivially_copyable_v<T>); |
| std::array<std::byte, sizeof(T)> buffer; |
| PW_TRY(reader.ReadExact(buffer)); |
| return bytes::ReadInOrder<T>(endian::little, buffer); |
| } |
| |
| EventType ParseEventType(std::string_view type_str) { |
| if (type_str == "PW_TRACE_EVENT_TYPE_INSTANT") |
| return EventType::PW_TRACE_EVENT_TYPE_INSTANT; |
| if (type_str == "PW_TRACE_EVENT_TYPE_INSTANT_GROUP") |
| return EventType::PW_TRACE_EVENT_TYPE_INSTANT_GROUP; |
| if (type_str == "PW_TRACE_EVENT_TYPE_ASYNC_START") |
| return EventType::PW_TRACE_EVENT_TYPE_ASYNC_START; |
| if (type_str == "PW_TRACE_EVENT_TYPE_ASYNC_STEP") |
| return EventType::PW_TRACE_EVENT_TYPE_ASYNC_STEP; |
| if (type_str == "PW_TRACE_EVENT_TYPE_ASYNC_END") |
| return EventType::PW_TRACE_EVENT_TYPE_ASYNC_END; |
| if (type_str == "PW_TRACE_EVENT_TYPE_DURATION_START") |
| return EventType::PW_TRACE_EVENT_TYPE_DURATION_START; |
| if (type_str == "PW_TRACE_EVENT_TYPE_DURATION_END") |
| return EventType::PW_TRACE_EVENT_TYPE_DURATION_END; |
| if (type_str == "PW_TRACE_EVENT_TYPE_DURATION_GROUP_START") |
| return EventType::PW_TRACE_EVENT_TYPE_DURATION_GROUP_START; |
| if (type_str == "PW_TRACE_EVENT_TYPE_DURATION_GROUP_END") |
| return EventType::PW_TRACE_EVENT_TYPE_DURATION_GROUP_END; |
| return EventType::PW_TRACE_EVENT_TYPE_INVALID; |
| } |
| |
| bool HasTraceId(EventType event_type) { |
| switch (event_type) { |
| case EventType::PW_TRACE_EVENT_TYPE_ASYNC_START: |
| case EventType::PW_TRACE_EVENT_TYPE_ASYNC_STEP: |
| case EventType::PW_TRACE_EVENT_TYPE_ASYNC_END: |
| return true; |
| case EventType::PW_TRACE_EVENT_TYPE_INVALID: |
| case EventType::PW_TRACE_EVENT_TYPE_INSTANT: |
| case EventType::PW_TRACE_EVENT_TYPE_INSTANT_GROUP: |
| case EventType::PW_TRACE_EVENT_TYPE_DURATION_START: |
| case EventType::PW_TRACE_EVENT_TYPE_DURATION_END: |
| case EventType::PW_TRACE_EVENT_TYPE_DURATION_GROUP_START: |
| case EventType::PW_TRACE_EVENT_TYPE_DURATION_GROUP_END: |
| return false; |
| } |
| return false; |
| } |
| |
| std::vector<std::string_view> Split(std::string_view input, char delimiter) { |
| std::vector<std::string_view> result; |
| while (!input.empty()) { |
| size_t found_pos = input.find(delimiter); |
| if (found_pos == input.npos) { |
| break; |
| } |
| result.emplace_back(input.substr(0, found_pos)); |
| input = input.substr(found_pos + 1); |
| } |
| result.emplace_back(input); |
| return result; |
| } |
| |
| } // namespace |
| |
| Result<DecodedEvent> TokenizedDecoder::ReadSizePrefixed( |
| stream::Reader& reader) { |
| // Trace entry as returned via pw_trace_tokenized:transfer_handler. |
| PW_TRY_ASSIGN(uint8_t entry_size, ReadInt<uint8_t>(reader)); |
| |
| std::array<std::byte, std::numeric_limits<uint8_t>::max()> buffer; |
| const ByteSpan entry_buf = span(buffer).first(entry_size); |
| |
| PW_TRY(reader.Read(entry_buf)); |
| return Decode(entry_buf); |
| } |
| |
| Result<DecodedEvent> TokenizedDecoder::Decode(ConstByteSpan data) { |
| stream::MemoryReader reader(data); |
| |
| // Read token |
| PW_TRY_ASSIGN(uint32_t token, ReadInt<uint32_t>(reader)); |
| |
| // Detokenize |
| tokenizer::DetokenizedString detok_result = |
| detokenizer_.Detokenize(ObjectAsBytes(token), kDomain); |
| std::string_view token_string = detok_result.BestString(); |
| if (token_string.empty()) { |
| PW_LOG_WARN("Failed to detokenize: 0x%08x", |
| static_cast<unsigned int>(token)); |
| return Status::DataLoss(); |
| } |
| |
| // Split token string: |
| // "event_type|flag|module|group|label|<optional DATA_FMT>" |
| std::vector<std::string_view> token_string_values = Split(token_string, '|'); |
| if (token_string_values.size() < 5) { |
| PW_LOG_WARN("Too few token values: %zu", token_string_values.size()); |
| return Status::DataLoss(); |
| } |
| DecodedEvent event{}; |
| event.type = |
| ParseEventType(token_string_values[to_underlying(TokenIdx::kEventType)]); |
| event.module = token_string_values[to_underlying(TokenIdx::kModule)]; |
| event.group = token_string_values[to_underlying(TokenIdx::kGroup)]; |
| event.label = token_string_values[to_underlying(TokenIdx::kLabel)]; |
| |
| // TODO: https://pwbug.dev/448489618 - The 'flag' field in the token is |
| // ostensibly a decimal integer string, but could actually be any arbitrary C |
| // expression that evaluates to an integer. Rather than try to parse it and |
| // risk failing, simply return it as a string for now. |
| event.flags_str = token_string_values[to_underlying(TokenIdx::kFlag)]; |
| |
| const bool has_data = |
| (token_string_values.size() > to_underlying(TokenIdx::kDataFmt)); |
| if (has_data) { |
| event.data_fmt = token_string_values[to_underlying(TokenIdx::kDataFmt)]; |
| } |
| |
| // Read time |
| uint64_t time_delta; |
| PW_TRY(varint::Read(reader, &time_delta)); |
| last_timestamp_us_ += (usec_per_tick() * time_delta); |
| event.timestamp_usec = last_timestamp_us_; |
| |
| // Trace ID |
| if (HasTraceId(event.type)) { |
| uint64_t trace_id_val; |
| PW_TRY(varint::Read(reader, &trace_id_val)); |
| event.trace_id = trace_id_val; |
| } |
| |
| // Data |
| if (has_data) { |
| size_t n = reader.ConservativeReadLimit(); |
| event.data.resize(n); |
| PW_TRY(reader.Read(event.data)); |
| } |
| |
| return event; |
| } |
| |
| } // namespace pw::trace |