blob: 8e056d815d4e582fd0374866bffd0222ee44c097 [file] [edit]
// 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