diff --git a/src/trace_processor/importers/fuchsia/BUILD.gn b/src/trace_processor/importers/fuchsia/BUILD.gn index 8aa0826dc5..d048f8613c 100644 --- a/src/trace_processor/importers/fuchsia/BUILD.gn +++ b/src/trace_processor/importers/fuchsia/BUILD.gn @@ -22,6 +22,7 @@ source_set("minimal") { "../../../../include/perfetto/trace_processor:storage", "../../../base", "../../storage", + "../../types", ] } diff --git a/src/trace_processor/importers/fuchsia/fuchsia_trace_parser.cc b/src/trace_processor/importers/fuchsia/fuchsia_trace_parser.cc index fdc5e603d3..32c0fafeb3 100644 --- a/src/trace_processor/importers/fuchsia/fuchsia_trace_parser.cc +++ b/src/trace_processor/importers/fuchsia/fuchsia_trace_parser.cc @@ -18,6 +18,7 @@ #include #include #include +#include #include #include #include @@ -26,6 +27,7 @@ #include "perfetto/base/logging.h" #include "perfetto/ext/base/string_view.h" #include "src/trace_processor/importers/common/args_tracker.h" +#include "src/trace_processor/importers/common/cpu_tracker.h" #include "src/trace_processor/importers/common/event_tracker.h" #include "src/trace_processor/importers/common/flow_tracker.h" #include "src/trace_processor/importers/common/process_tracker.h" @@ -37,12 +39,19 @@ #include "src/trace_processor/importers/fuchsia/fuchsia_trace_utils.h" #include "src/trace_processor/storage/stats.h" #include "src/trace_processor/storage/trace_storage.h" +#include "src/trace_processor/tables/sched_tables_py.h" namespace perfetto::trace_processor { namespace { + // Record Types constexpr uint32_t kEvent = 4; +constexpr uint32_t kSchedulerEvent = 8; + +constexpr uint32_t kSchedulerEventLegacyContextSwitch = 0; +constexpr uint32_t kSchedulerEventContextSwitch = 1; +constexpr uint32_t kSchedulerEventThreadWakeup = 2; // Event Types constexpr uint32_t kInstant = 0; @@ -69,6 +78,16 @@ constexpr uint32_t kPointer = 7; constexpr uint32_t kKoid = 8; constexpr uint32_t kBool = 9; +// Thread states +constexpr uint32_t kThreadNew = 0; +constexpr uint32_t kThreadRunning = 1; +constexpr uint32_t kThreadSuspended = 2; +constexpr uint32_t kThreadBlocked = 3; +constexpr uint32_t kThreadDying = 4; +constexpr uint32_t kThreadDead = 5; + +constexpr int32_t kIdleWeight = std::numeric_limits::min(); + constexpr auto kCounterBlueprint = tracks::CounterBlueprint( "fuchsia_counter", tracks::UnknownUnitBlueprint(), @@ -79,7 +98,18 @@ constexpr auto kCounterBlueprint = tracks::CounterBlueprint( } // namespace FuchsiaTraceParser::FuchsiaTraceParser(TraceProcessorContext* context) - : context_(context) {} + : context_(context), + weight_id_(context->storage->InternString("weight")), + incoming_weight_id_(context->storage->InternString("incoming_weight")), + outgoing_weight_id_(context->storage->InternString("outgoing_weight")), + running_string_id_(context->storage->InternString("Running")), + runnable_string_id_(context->storage->InternString("R")), + preempted_string_id_(context->storage->InternString("R+")), + waking_string_id_(context->storage->InternString("W")), + blocked_string_id_(context->storage->InternString("S")), + suspended_string_id_(context->storage->InternString("T")), + exit_dying_string_id_(context->storage->InternString("Z")), + exit_dead_string_id_(context->storage->InternString("X")) {} FuchsiaTraceParser::~FuchsiaTraceParser() = default; @@ -206,24 +236,27 @@ void FuchsiaTraceParser::ParseFuchsiaRecord(int64_t, FuchsiaRecord fr) { ProcessTracker* procs = context_->process_tracker.get(); SliceTracker* slices = context_->slice_tracker.get(); + // Read arguments + const auto intern_string = [this](base::StringView string) { + return context_->storage->InternString(string); + }; + const auto get_string = [&fr](uint32_t index) { return fr.GetString(index); }; + uint64_t header; if (!cursor.ReadUint64(&header)) { context_->storage->IncrementStats(stats::fuchsia_invalid_event); return; } - uint32_t record_type = fuchsia_trace_utils::ReadField(header, 0, 3); + auto record_type = fuchsia_trace_utils::ReadField(header, 0, 3); switch (record_type) { case kEvent: { - uint32_t event_type = + auto event_type = fuchsia_trace_utils::ReadField(header, 16, 19); - uint32_t n_args = - fuchsia_trace_utils::ReadField(header, 20, 23); - uint32_t thread_ref = + auto n_args = fuchsia_trace_utils::ReadField(header, 20, 23); + auto thread_ref = fuchsia_trace_utils::ReadField(header, 24, 31); - uint32_t cat_ref = - fuchsia_trace_utils::ReadField(header, 32, 47); - uint32_t name_ref = - fuchsia_trace_utils::ReadField(header, 48, 63); + auto cat_ref = fuchsia_trace_utils::ReadField(header, 32, 47); + auto name_ref = fuchsia_trace_utils::ReadField(header, 48, 63); int64_t ts; if (!cursor.ReadTimestamp(fr.get_ticks_per_second(), &ts)) { @@ -262,14 +295,6 @@ void FuchsiaTraceParser::ParseFuchsiaRecord(int64_t, FuchsiaRecord fr) { name = fr.GetString(name_ref); } - // Read arguments - const auto intern_string = [this](base::StringView string) { - return context_->storage->InternString(string); - }; - const auto get_string = [&fr](uint32_t index) { - return fr.GetString(index); - }; - auto maybe_args = FuchsiaTraceParser::ParseArgs( cursor, n_args, intern_string, get_string); if (!maybe_args.has_value()) { @@ -484,6 +509,201 @@ void FuchsiaTraceParser::ParseFuchsiaRecord(int64_t, FuchsiaRecord fr) { } break; } + case kSchedulerEvent: { + auto event_type = + fuchsia_trace_utils::ReadField(header, 60, 63); + switch (event_type) { + case kSchedulerEventLegacyContextSwitch: { + auto cpu = fuchsia_trace_utils::ReadField(header, 16, 23); + auto outgoing_state = + fuchsia_trace_utils::ReadField(header, 24, 27); + auto outgoing_thread_ref = + fuchsia_trace_utils::ReadField(header, 28, 35); + auto incoming_thread_ref = + fuchsia_trace_utils::ReadField(header, 36, 43); + auto outgoing_priority = + fuchsia_trace_utils::ReadField(header, 44, 51); + auto incoming_priority = + fuchsia_trace_utils::ReadField(header, 52, 59); + + int64_t ts; + if (!cursor.ReadTimestamp(fr.get_ticks_per_second(), &ts)) { + context_->storage->IncrementStats(stats::fuchsia_invalid_event); + return; + } + if (ts < 0) { + context_->storage->IncrementStats(stats::fuchsia_invalid_event); + return; + } + + FuchsiaThreadInfo outgoing_thread_info; + if (fuchsia_trace_utils::IsInlineThread(outgoing_thread_ref)) { + if (!cursor.ReadInlineThread(&outgoing_thread_info)) { + context_->storage->IncrementStats(stats::fuchsia_invalid_event); + return; + } + } else { + outgoing_thread_info = fr.GetThread(outgoing_thread_ref); + } + Thread& outgoing_thread = GetThread(outgoing_thread_info.tid); + + FuchsiaThreadInfo incoming_thread_info; + if (fuchsia_trace_utils::IsInlineThread(incoming_thread_ref)) { + if (!cursor.ReadInlineThread(&incoming_thread_info)) { + context_->storage->IncrementStats(stats::fuchsia_invalid_event); + return; + } + } else { + incoming_thread_info = fr.GetThread(incoming_thread_ref); + } + Thread& incoming_thread = GetThread(incoming_thread_info.tid); + + // Idle threads are identified by pid == 0 and prio == 0. + const bool incoming_is_idle = + incoming_thread.info.pid == 0 && incoming_priority == 0; + const bool outgoing_is_idle = + outgoing_thread.info.pid == 0 && outgoing_priority == 0; + + // Handle switching away from the currently running thread. + if (!outgoing_is_idle) { + SwitchFrom(&outgoing_thread, ts, cpu, outgoing_state); + } + + // Handle switching to the new currently running thread. + if (!incoming_is_idle) { + SwitchTo(&incoming_thread, ts, cpu, incoming_priority); + } + break; + } + case kSchedulerEventContextSwitch: { + const auto argument_count = + fuchsia_trace_utils::ReadField(header, 16, 19); + const auto cpu = + fuchsia_trace_utils::ReadField(header, 20, 35); + const auto outgoing_state = + fuchsia_trace_utils::ReadField(header, 36, 39); + + int64_t ts; + if (!cursor.ReadTimestamp(fr.get_ticks_per_second(), &ts)) { + context_->storage->IncrementStats(stats::fuchsia_invalid_event); + return; + } + if (ts < 0) { + context_->storage->IncrementStats(stats::fuchsia_invalid_event); + return; + } + + uint64_t outgoing_tid; + if (!cursor.ReadUint64(&outgoing_tid)) { + context_->storage->IncrementStats(stats::fuchsia_invalid_event); + return; + } + Thread& outgoing_thread = GetThread(outgoing_tid); + + uint64_t incoming_tid; + if (!cursor.ReadUint64(&incoming_tid)) { + context_->storage->IncrementStats(stats::fuchsia_invalid_event); + return; + } + Thread& incoming_thread = GetThread(incoming_tid); + + auto maybe_args = FuchsiaTraceParser::ParseArgs( + cursor, argument_count, intern_string, get_string); + if (!maybe_args.has_value()) { + context_->storage->IncrementStats(stats::fuchsia_invalid_event); + return; + } + + int32_t incoming_weight = 0; + int32_t outgoing_weight = 0; + + for (const auto& arg : *maybe_args) { + if (arg.name == incoming_weight_id_) { + if (arg.value.Type() != + fuchsia_trace_utils::ArgValue::ArgType::kInt32) { + context_->storage->IncrementStats(stats::fuchsia_invalid_event); + return; + } + incoming_weight = arg.value.Int32(); + } else if (arg.name == outgoing_weight_id_) { + if (arg.value.Type() != + fuchsia_trace_utils::ArgValue::ArgType::kInt32) { + context_->storage->IncrementStats(stats::fuchsia_invalid_event); + return; + } + outgoing_weight = arg.value.Int32(); + } + } + + const bool incoming_is_idle = incoming_weight == kIdleWeight; + const bool outgoing_is_idle = outgoing_weight == kIdleWeight; + + // Handle switching away from the currently running thread. + if (!outgoing_is_idle) { + SwitchFrom(&outgoing_thread, ts, cpu, outgoing_state); + } + + // Handle switching to the new currently running thread. + if (!incoming_is_idle) { + SwitchTo(&incoming_thread, ts, cpu, incoming_weight); + } + break; + } + case kSchedulerEventThreadWakeup: { + const auto argument_count = + fuchsia_trace_utils::ReadField(header, 16, 19); + const auto cpu = + fuchsia_trace_utils::ReadField(header, 20, 35); + + int64_t ts; + if (!cursor.ReadTimestamp(fr.get_ticks_per_second(), &ts)) { + context_->storage->IncrementStats(stats::fuchsia_invalid_event); + return; + } + if (ts < 0) { + context_->storage->IncrementStats(stats::fuchsia_invalid_event); + return; + } + + uint64_t waking_tid; + if (!cursor.ReadUint64(&waking_tid)) { + context_->storage->IncrementStats(stats::fuchsia_invalid_event); + return; + } + Thread& waking_thread = GetThread(waking_tid); + + auto maybe_args = FuchsiaTraceParser::ParseArgs( + cursor, argument_count, intern_string, get_string); + if (!maybe_args.has_value()) { + context_->storage->IncrementStats(stats::fuchsia_invalid_event); + return; + } + + int32_t waking_weight = 0; + + for (const auto& arg : *maybe_args) { + if (arg.name == weight_id_) { + if (arg.value.Type() != + fuchsia_trace_utils::ArgValue::ArgType::kInt32) { + context_->storage->IncrementStats(stats::fuchsia_invalid_event); + return; + } + waking_weight = arg.value.Int32(); + } + } + + const bool waking_is_idle = waking_weight == kIdleWeight; + if (!waking_is_idle) { + Wake(&waking_thread, ts, cpu); + } + break; + } + default: + PERFETTO_DLOG("Skipping unknown scheduler event type %d", event_type); + break; + } + break; + } default: { PERFETTO_DFATAL("Unknown record type %d in FuchsiaTraceParser", record_type); @@ -492,4 +712,141 @@ void FuchsiaTraceParser::ParseFuchsiaRecord(int64_t, FuchsiaRecord fr) { } } +void FuchsiaTraceParser::SwitchFrom(Thread* thread, + int64_t ts, + uint32_t cpu, + uint32_t thread_state) { + TraceStorage* storage = context_->storage.get(); + ProcessTracker* procs = context_->process_tracker.get(); + + StringId state = IdForOutgoingThreadState(thread_state); + UniqueTid utid = procs->UpdateThread(static_cast(thread->info.tid), + static_cast(thread->info.pid)); + + const auto duration = ts - thread->last_ts; + thread->last_ts = ts; + + // Close the slice record if one is open for this thread. + if (thread->last_slice_row.has_value()) { + auto row_ref = thread->last_slice_row->ToRowReference( + storage->mutable_sched_slice_table()); + row_ref.set_dur(duration); + row_ref.set_end_state(state); + thread->last_slice_row.reset(); + } + + // Close the state record if one is open for this thread. + if (thread->last_state_row.has_value()) { + auto row_ref = thread->last_state_row->ToRowReference( + storage->mutable_thread_state_table()); + row_ref.set_dur(duration); + thread->last_state_row.reset(); + } + + // Open a new state record to track the duration of the outgoing + // state. + tables::ThreadStateTable::Row state_row; + state_row.ts = ts; + state_row.ucpu = context_->cpu_tracker->GetOrCreateCpu(cpu); + state_row.dur = -1; + state_row.state = state; + state_row.utid = utid; + auto state_row_number = + storage->mutable_thread_state_table()->Insert(state_row).row_number; + thread->last_state_row = state_row_number; +} + +void FuchsiaTraceParser::SwitchTo(Thread* thread, + int64_t ts, + uint32_t cpu, + int32_t weight) { + TraceStorage* storage = context_->storage.get(); + ProcessTracker* procs = context_->process_tracker.get(); + + UniqueTid utid = procs->UpdateThread(static_cast(thread->info.tid), + static_cast(thread->info.pid)); + + const auto duration = ts - thread->last_ts; + thread->last_ts = ts; + + // Close the state record if one is open for this thread. + if (thread->last_state_row.has_value()) { + auto row_ref = thread->last_state_row->ToRowReference( + storage->mutable_thread_state_table()); + row_ref.set_dur(duration); + thread->last_state_row.reset(); + } + + auto ucpu = context_->cpu_tracker->GetOrCreateCpu(cpu); + // Open a new slice record for this thread. + tables::SchedSliceTable::Row slice_row; + slice_row.ts = ts; + slice_row.ucpu = ucpu; + slice_row.dur = -1; + slice_row.utid = utid; + slice_row.priority = weight; + auto slice_row_number = + storage->mutable_sched_slice_table()->Insert(slice_row).row_number; + thread->last_slice_row = slice_row_number; + + // Open a new state record for this thread. + tables::ThreadStateTable::Row state_row; + state_row.ts = ts; + state_row.ucpu = context_->cpu_tracker->GetOrCreateCpu(cpu); + state_row.dur = -1; + state_row.state = running_string_id_; + state_row.utid = utid; + auto state_row_number = + storage->mutable_thread_state_table()->Insert(state_row).row_number; + thread->last_state_row = state_row_number; +} + +void FuchsiaTraceParser::Wake(Thread* thread, int64_t ts, uint32_t cpu) { + TraceStorage* storage = context_->storage.get(); + ProcessTracker* procs = context_->process_tracker.get(); + + UniqueTid utid = procs->UpdateThread(static_cast(thread->info.tid), + static_cast(thread->info.pid)); + + const auto duration = ts - thread->last_ts; + thread->last_ts = ts; + + // Close the state record if one is open for this thread. + if (thread->last_state_row.has_value()) { + auto row_ref = thread->last_state_row->ToRowReference( + storage->mutable_thread_state_table()); + row_ref.set_dur(duration); + thread->last_state_row.reset(); + } + + // Open a new state record for this thread. + tables::ThreadStateTable::Row state_row; + state_row.ts = ts; + state_row.ucpu = context_->cpu_tracker->GetOrCreateCpu(cpu); + state_row.dur = -1; + state_row.state = waking_string_id_; + state_row.utid = utid; + auto state_row_number = + storage->mutable_thread_state_table()->Insert(state_row).row_number; + thread->last_state_row = state_row_number; +} + +StringId FuchsiaTraceParser::IdForOutgoingThreadState(uint32_t state) { + switch (state) { + case kThreadNew: + case kThreadRunning: + return runnable_string_id_; + case kThreadBlocked: + return blocked_string_id_; + case kThreadSuspended: + return suspended_string_id_; + case kThreadDying: + return exit_dying_string_id_; + case kThreadDead: + return exit_dead_string_id_; + default: + return kNullStringId; + } +} + } // namespace perfetto::trace_processor diff --git a/src/trace_processor/importers/fuchsia/fuchsia_trace_parser.h b/src/trace_processor/importers/fuchsia/fuchsia_trace_parser.h index d463839815..361173b0b8 100644 --- a/src/trace_processor/importers/fuchsia/fuchsia_trace_parser.h +++ b/src/trace_processor/importers/fuchsia/fuchsia_trace_parser.h @@ -17,16 +17,20 @@ #ifndef SRC_TRACE_PROCESSOR_IMPORTERS_FUCHSIA_FUCHSIA_TRACE_PARSER_H_ #define SRC_TRACE_PROCESSOR_IMPORTERS_FUCHSIA_FUCHSIA_TRACE_PARSER_H_ +#include #include #include +#include #include +#include "perfetto/ext/base/string_view.h" #include "src/trace_processor/importers/common/trace_parser.h" #include "src/trace_processor/importers/fuchsia/fuchsia_record.h" #include "src/trace_processor/importers/fuchsia/fuchsia_trace_utils.h" +#include "src/trace_processor/storage/trace_storage.h" +#include "src/trace_processor/tables/sched_tables_py.h" -namespace perfetto { -namespace trace_processor { +namespace perfetto::trace_processor { class TraceProcessorContext; @@ -35,8 +39,28 @@ class FuchsiaTraceParser : public FuchsiaRecordParser { explicit FuchsiaTraceParser(TraceProcessorContext*); ~FuchsiaTraceParser() override; + // Tracks the state for updating sched slice and thread state tables. + struct Thread { + explicit Thread(uint64_t tid) : info{0, tid} {} + + FuchsiaThreadInfo info; + int64_t last_ts{0}; + std::optional last_slice_row; + std::optional last_state_row; + }; + void ParseFuchsiaRecord(int64_t timestamp, FuchsiaRecord fr) override; + // Allocates or returns an existing Thread instance for the given tid. + Thread& GetThread(uint64_t tid) { + auto search = threads_.find(tid); + if (search != threads_.end()) { + return search->second; + } + auto result = threads_.emplace(tid, tid); + return result.first->second; + } + struct Arg { StringId name; fuchsia_trace_utils::ArgValue value; @@ -54,10 +78,36 @@ class FuchsiaTraceParser : public FuchsiaRecordParser { std::function get_string); private: + void SwitchFrom(Thread* thread, + int64_t ts, + uint32_t cpu, + uint32_t thread_state); + void SwitchTo(Thread* thread, int64_t ts, uint32_t cpu, int32_t weight); + void Wake(Thread* thread, int64_t ts, uint32_t cpu); + + StringId IdForOutgoingThreadState(uint32_t state); + TraceProcessorContext* const context_; + + // Interned string ids for record arguments. + StringId weight_id_; + StringId incoming_weight_id_; + StringId outgoing_weight_id_; + + // Interned string ids for the relevant thread states. + StringId running_string_id_; + StringId runnable_string_id_; + StringId preempted_string_id_; + StringId waking_string_id_; + StringId blocked_string_id_; + StringId suspended_string_id_; + StringId exit_dying_string_id_; + StringId exit_dead_string_id_; + + // Map from tid to Thread. + std::unordered_map threads_; }; -} // namespace trace_processor -} // namespace perfetto +} // namespace perfetto::trace_processor #endif // SRC_TRACE_PROCESSOR_IMPORTERS_FUCHSIA_FUCHSIA_TRACE_PARSER_H_ diff --git a/src/trace_processor/importers/fuchsia/fuchsia_trace_tokenizer.cc b/src/trace_processor/importers/fuchsia/fuchsia_trace_tokenizer.cc index 4ffb9a257b..ea6197de4b 100644 --- a/src/trace_processor/importers/fuchsia/fuchsia_trace_tokenizer.cc +++ b/src/trace_processor/importers/fuchsia/fuchsia_trace_tokenizer.cc @@ -16,25 +16,34 @@ #include "src/trace_processor/importers/fuchsia/fuchsia_trace_tokenizer.h" -#include +#include +#include +#include #include +#include +#include +#include #include "perfetto/base/logging.h" #include "perfetto/base/status.h" #include "perfetto/ext/base/string_view.h" +#include "perfetto/trace_processor/status.h" #include "perfetto/trace_processor/trace_blob.h" +#include "perfetto/trace_processor/trace_blob_view.h" #include "src/trace_processor/importers/common/cpu_tracker.h" #include "src/trace_processor/importers/common/process_tracker.h" #include "src/trace_processor/importers/common/slice_tracker.h" #include "src/trace_processor/importers/fuchsia/fuchsia_record.h" #include "src/trace_processor/importers/fuchsia/fuchsia_trace_parser.h" +#include "src/trace_processor/importers/fuchsia/fuchsia_trace_utils.h" #include "src/trace_processor/importers/proto/proto_trace_reader.h" #include "src/trace_processor/sorter/trace_sorter.h" -#include "src/trace_processor/types/task_state.h" +#include "src/trace_processor/storage/stats.h" +#include "src/trace_processor/storage/trace_storage.h" +#include "src/trace_processor/tables/sched_tables_py.h" #include "src/trace_processor/types/trace_processor_context.h" -namespace perfetto { -namespace trace_processor { +namespace perfetto::trace_processor { namespace { @@ -59,36 +68,15 @@ constexpr uint32_t kProviderInfo = 1; constexpr uint32_t kProviderSection = 2; constexpr uint32_t kProviderEvent = 3; -// Thread states -constexpr uint32_t kThreadNew = 0; -constexpr uint32_t kThreadRunning = 1; -constexpr uint32_t kThreadSuspended = 2; -constexpr uint32_t kThreadBlocked = 3; -constexpr uint32_t kThreadDying = 4; -constexpr uint32_t kThreadDead = 5; - // Zircon object types constexpr uint32_t kZxObjTypeProcess = 1; constexpr uint32_t kZxObjTypeThread = 2; -constexpr int32_t kIdleWeight = std::numeric_limits::min(); - } // namespace FuchsiaTraceTokenizer::FuchsiaTraceTokenizer(TraceProcessorContext* context) : context_(context), proto_reader_(context), - running_string_id_(context->storage->InternString("Running")), - runnable_string_id_(context->storage->InternString("R")), - preempted_string_id_(context->storage->InternString("R+")), - waking_string_id_(context->storage->InternString("W")), - blocked_string_id_(context->storage->InternString("S")), - suspended_string_id_(context->storage->InternString("T")), - exit_dying_string_id_(context->storage->InternString("Z")), - exit_dead_string_id_(context->storage->InternString("X")), - incoming_weight_id_(context->storage->InternString("incoming_weight")), - outgoing_weight_id_(context->storage->InternString("outgoing_weight")), - weight_id_(context->storage->InternString("weight")), process_id_(context->storage->InternString("process")) { RegisterProvider(0, ""); } @@ -206,143 +194,6 @@ util::Status FuchsiaTraceTokenizer::Parse(TraceBlobView blob) { return proto_reader_.Parse(TraceBlobView(std::move(perfetto_blob))); } -StringId FuchsiaTraceTokenizer::IdForOutgoingThreadState(uint32_t state) { - switch (state) { - case kThreadNew: - case kThreadRunning: - return runnable_string_id_; - case kThreadBlocked: - return blocked_string_id_; - case kThreadSuspended: - return suspended_string_id_; - case kThreadDying: - return exit_dying_string_id_; - case kThreadDead: - return exit_dead_string_id_; - default: - return kNullStringId; - } -} - -void FuchsiaTraceTokenizer::SwitchFrom(Thread* thread, - int64_t ts, - uint32_t cpu, - uint32_t thread_state) { - TraceStorage* storage = context_->storage.get(); - ProcessTracker* procs = context_->process_tracker.get(); - - StringId state = IdForOutgoingThreadState(thread_state); - UniqueTid utid = procs->UpdateThread(static_cast(thread->info.tid), - static_cast(thread->info.pid)); - - const auto duration = ts - thread->last_ts; - thread->last_ts = ts; - - // Close the slice record if one is open for this thread. - if (thread->last_slice_row.has_value()) { - auto row_ref = thread->last_slice_row->ToRowReference( - storage->mutable_sched_slice_table()); - row_ref.set_dur(duration); - row_ref.set_end_state(state); - thread->last_slice_row.reset(); - } - - // Close the state record if one is open for this thread. - if (thread->last_state_row.has_value()) { - auto row_ref = thread->last_state_row->ToRowReference( - storage->mutable_thread_state_table()); - row_ref.set_dur(duration); - thread->last_state_row.reset(); - } - - // Open a new state record to track the duration of the outgoing - // state. - tables::ThreadStateTable::Row state_row; - state_row.ts = ts; - state_row.ucpu = context_->cpu_tracker->GetOrCreateCpu(cpu); - state_row.dur = -1; - state_row.state = state; - state_row.utid = utid; - auto state_row_number = - storage->mutable_thread_state_table()->Insert(state_row).row_number; - thread->last_state_row = state_row_number; -} - -void FuchsiaTraceTokenizer::SwitchTo(Thread* thread, - int64_t ts, - uint32_t cpu, - int32_t weight) { - TraceStorage* storage = context_->storage.get(); - ProcessTracker* procs = context_->process_tracker.get(); - - UniqueTid utid = procs->UpdateThread(static_cast(thread->info.tid), - static_cast(thread->info.pid)); - - const auto duration = ts - thread->last_ts; - thread->last_ts = ts; - - // Close the state record if one is open for this thread. - if (thread->last_state_row.has_value()) { - auto row_ref = thread->last_state_row->ToRowReference( - storage->mutable_thread_state_table()); - row_ref.set_dur(duration); - thread->last_state_row.reset(); - } - - auto ucpu = context_->cpu_tracker->GetOrCreateCpu(cpu); - // Open a new slice record for this thread. - tables::SchedSliceTable::Row slice_row; - slice_row.ts = ts; - slice_row.ucpu = ucpu; - slice_row.dur = -1; - slice_row.utid = utid; - slice_row.priority = weight; - auto slice_row_number = - storage->mutable_sched_slice_table()->Insert(slice_row).row_number; - thread->last_slice_row = slice_row_number; - - // Open a new state record for this thread. - tables::ThreadStateTable::Row state_row; - state_row.ts = ts; - state_row.ucpu = context_->cpu_tracker->GetOrCreateCpu(cpu); - state_row.dur = -1; - state_row.state = running_string_id_; - state_row.utid = utid; - auto state_row_number = - storage->mutable_thread_state_table()->Insert(state_row).row_number; - thread->last_state_row = state_row_number; -} - -void FuchsiaTraceTokenizer::Wake(Thread* thread, int64_t ts, uint32_t cpu) { - TraceStorage* storage = context_->storage.get(); - ProcessTracker* procs = context_->process_tracker.get(); - - UniqueTid utid = procs->UpdateThread(static_cast(thread->info.tid), - static_cast(thread->info.pid)); - - const auto duration = ts - thread->last_ts; - thread->last_ts = ts; - - // Close the state record if one is open for this thread. - if (thread->last_state_row.has_value()) { - auto row_ref = thread->last_state_row->ToRowReference( - storage->mutable_thread_state_table()); - row_ref.set_dur(duration); - thread->last_state_row.reset(); - } - - // Open a new state record for this thread. - tables::ThreadStateTable::Row state_row; - state_row.ts = ts; - state_row.ucpu = context_->cpu_tracker->GetOrCreateCpu(cpu); - state_row.dur = -1; - state_row.state = waking_string_id_; - state_row.utid = utid; - auto state_row_number = - storage->mutable_thread_state_table()->Insert(state_row).row_number; - thread->last_state_row = state_row_number; -} - // Most record types are read and recorded in |TraceStorage| here directly. // Event records are sorted by timestamp before processing, so instead of // recording them in |TraceStorage| they are given to |TraceSorter|. In order to @@ -360,7 +211,7 @@ void FuchsiaTraceTokenizer::ParseRecord(TraceBlobView tbv) { return; } - uint32_t record_type = fuchsia_trace_utils::ReadField(header, 0, 3); + auto record_type = fuchsia_trace_utils::ReadField(header, 0, 3); // All non-metadata events require current_provider_ to be set. if (record_type != kMetadata && current_provider_ == nullptr) { @@ -376,15 +227,55 @@ void FuchsiaTraceTokenizer::ParseRecord(TraceBlobView tbv) { return current_provider_->GetString(index); }; + const auto insert_args = [this](uint32_t n_args, + fuchsia_trace_utils::RecordCursor& cursor, + FuchsiaRecord& record) { + for (uint32_t i = 0; i < n_args; i++) { + const size_t arg_base = cursor.WordIndex(); + uint64_t arg_header; + if (!cursor.ReadUint64(&arg_header)) { + context_->storage->IncrementStats(stats::fuchsia_invalid_event); + return; + } + auto arg_type = + fuchsia_trace_utils::ReadField(arg_header, 0, 3); + auto arg_size_words = + fuchsia_trace_utils::ReadField(arg_header, 4, 15); + auto arg_name_ref = + fuchsia_trace_utils::ReadField(arg_header, 16, 31); + + if (fuchsia_trace_utils::IsInlineString(arg_name_ref)) { + // Skip over inline string + cursor.ReadInlineString(arg_name_ref, nullptr); + } else { + record.InsertString(arg_name_ref, + current_provider_->GetString(arg_name_ref)); + } + + if (arg_type == ArgValue::ArgType::kString) { + auto arg_value_ref = + fuchsia_trace_utils::ReadField(arg_header, 32, 47); + if (fuchsia_trace_utils::IsInlineString(arg_value_ref)) { + // Skip over inline string + cursor.ReadInlineString(arg_value_ref, nullptr); + } else { + record.InsertString(arg_value_ref, + current_provider_->GetString(arg_value_ref)); + } + } + cursor.SetWordIndex(arg_base + arg_size_words); + } + }; + switch (record_type) { case kMetadata: { - uint32_t metadata_type = + auto metadata_type = fuchsia_trace_utils::ReadField(header, 16, 19); switch (metadata_type) { case kProviderInfo: { - uint32_t provider_id = + auto provider_id = fuchsia_trace_utils::ReadField(header, 20, 51); - uint32_t name_len = + auto name_len = fuchsia_trace_utils::ReadField(header, 52, 59); base::StringView name_view; if (!cursor.ReadInlineString(name_len, &name_view)) { @@ -395,7 +286,7 @@ void FuchsiaTraceTokenizer::ParseRecord(TraceBlobView tbv) { break; } case kProviderSection: { - uint32_t provider_id = + auto provider_id = fuchsia_trace_utils::ReadField(header, 20, 51); current_provider_ = providers_[provider_id].get(); break; @@ -417,9 +308,9 @@ void FuchsiaTraceTokenizer::ParseRecord(TraceBlobView tbv) { break; } case kString: { - uint32_t index = fuchsia_trace_utils::ReadField(header, 16, 30); + auto index = fuchsia_trace_utils::ReadField(header, 16, 30); if (index != 0) { - uint32_t len = fuchsia_trace_utils::ReadField(header, 32, 46); + auto len = fuchsia_trace_utils::ReadField(header, 32, 46); base::StringView s; if (!cursor.ReadInlineString(len, &s)) { context_->storage->IncrementStats(stats::fuchsia_invalid_event); @@ -432,7 +323,7 @@ void FuchsiaTraceTokenizer::ParseRecord(TraceBlobView tbv) { break; } case kThread: { - uint32_t index = fuchsia_trace_utils::ReadField(header, 16, 23); + auto index = fuchsia_trace_utils::ReadField(header, 16, 23); if (index != 0) { FuchsiaThreadInfo tinfo; if (!cursor.ReadInlineThread(&tinfo)) { @@ -445,12 +336,10 @@ void FuchsiaTraceTokenizer::ParseRecord(TraceBlobView tbv) { break; } case kEvent: { - uint32_t thread_ref = + auto thread_ref = fuchsia_trace_utils::ReadField(header, 24, 31); - uint32_t cat_ref = - fuchsia_trace_utils::ReadField(header, 32, 47); - uint32_t name_ref = - fuchsia_trace_utils::ReadField(header, 48, 63); + auto cat_ref = fuchsia_trace_utils::ReadField(header, 32, 47); + auto name_ref = fuchsia_trace_utils::ReadField(header, 48, 63); // Build the FuchsiaRecord for the event, i.e. extract the thread // information if not inline, and any non-inline strings (name, category @@ -492,57 +381,19 @@ void FuchsiaTraceTokenizer::ParseRecord(TraceBlobView tbv) { record.InsertString(name_ref, current_provider_->GetString(name_ref)); } - uint32_t n_args = - fuchsia_trace_utils::ReadField(header, 20, 23); - for (uint32_t i = 0; i < n_args; i++) { - const size_t arg_base = cursor.WordIndex(); - uint64_t arg_header; - if (!cursor.ReadUint64(&arg_header)) { - storage->IncrementStats(stats::fuchsia_invalid_event); - return; - } - uint32_t arg_type = - fuchsia_trace_utils::ReadField(arg_header, 0, 3); - uint32_t arg_size_words = - fuchsia_trace_utils::ReadField(arg_header, 4, 15); - uint32_t arg_name_ref = - fuchsia_trace_utils::ReadField(arg_header, 16, 31); - - if (fuchsia_trace_utils::IsInlineString(arg_name_ref)) { - // Skip over inline string - cursor.ReadInlineString(arg_name_ref, nullptr); - } else { - record.InsertString(arg_name_ref, - current_provider_->GetString(arg_name_ref)); - } - - if (arg_type == ArgValue::ArgType::kString) { - uint32_t arg_value_ref = - fuchsia_trace_utils::ReadField(arg_header, 32, 47); - if (fuchsia_trace_utils::IsInlineString(arg_value_ref)) { - // Skip over inline string - cursor.ReadInlineString(arg_value_ref, nullptr); - } else { - record.InsertString(arg_value_ref, - current_provider_->GetString(arg_value_ref)); - } - } - - cursor.SetWordIndex(arg_base + arg_size_words); - } - + auto n_args = fuchsia_trace_utils::ReadField(header, 20, 23); + insert_args(n_args, cursor, record); sorter->PushFuchsiaRecord(ts, std::move(record)); break; } case kBlob: { constexpr uint32_t kPerfettoBlob = 3; - uint32_t blob_type = - fuchsia_trace_utils::ReadField(header, 48, 55); + auto blob_type = fuchsia_trace_utils::ReadField(header, 48, 55); if (blob_type == kPerfettoBlob) { FuchsiaRecord record(std::move(tbv)); - uint32_t blob_size = + auto blob_size = fuchsia_trace_utils::ReadField(header, 32, 46); - uint32_t name_ref = + auto name_ref = fuchsia_trace_utils::ReadField(header, 16, 31); // We don't need the name, but we still need to parse it in case it is @@ -565,10 +416,8 @@ void FuchsiaTraceTokenizer::ParseRecord(TraceBlobView tbv) { break; } case kKernelObject: { - uint32_t obj_type = - fuchsia_trace_utils::ReadField(header, 16, 23); - uint32_t name_ref = - fuchsia_trace_utils::ReadField(header, 24, 39); + auto obj_type = fuchsia_trace_utils::ReadField(header, 16, 23); + auto name_ref = fuchsia_trace_utils::ReadField(header, 24, 39); uint64_t obj_id; if (!cursor.ReadUint64(&obj_id)) { @@ -600,7 +449,7 @@ void FuchsiaTraceTokenizer::ParseRecord(TraceBlobView tbv) { break; } case kZxObjTypeThread: { - uint32_t n_args = + auto n_args = fuchsia_trace_utils::ReadField(header, 40, 43); auto maybe_args = FuchsiaTraceParser::ParseArgs( @@ -621,7 +470,13 @@ void FuchsiaTraceTokenizer::ParseRecord(TraceBlobView tbv) { } } - Thread& thread = GetThread(obj_id); + // TODO(lalitm): this is a gross hack we're adding to unblock a crash + // (b/383877212). This should be refactored properly out into a + // tracker (which is the pattern for handling this sort of thing + // in the rest of TP) but that is a bunch of boilerplate. + auto* parser = static_cast( + context_->fuchsia_record_parser.get()); + auto& thread = parser->GetThread(obj_id); thread.info.pid = pid; UniqueTid utid = procs->UpdateThread(static_cast(obj_id), @@ -640,22 +495,17 @@ void FuchsiaTraceTokenizer::ParseRecord(TraceBlobView tbv) { case kSchedulerEvent: { // Context switch records come in order, so they do not need to go through // TraceSorter. - uint32_t event_type = + auto event_type = fuchsia_trace_utils::ReadField(header, 60, 63); switch (event_type) { case kSchedulerEventLegacyContextSwitch: { - uint32_t cpu = - fuchsia_trace_utils::ReadField(header, 16, 23); - uint32_t outgoing_state = - fuchsia_trace_utils::ReadField(header, 24, 27); - uint32_t outgoing_thread_ref = + auto outgoing_thread_ref = fuchsia_trace_utils::ReadField(header, 28, 35); - int32_t outgoing_priority = - fuchsia_trace_utils::ReadField(header, 44, 51); - uint32_t incoming_thread_ref = + auto incoming_thread_ref = fuchsia_trace_utils::ReadField(header, 36, 43); - int32_t incoming_priority = - fuchsia_trace_utils::ReadField(header, 52, 59); + + FuchsiaRecord record(std::move(tbv)); + record.set_ticks_per_second(current_provider_->ticks_per_second); int64_t ts; if (!cursor.ReadTimestamp(current_provider_->ticks_per_second, &ts)) { @@ -667,54 +517,29 @@ void FuchsiaTraceTokenizer::ParseRecord(TraceBlobView tbv) { return; } - FuchsiaThreadInfo outgoing_thread_info; if (fuchsia_trace_utils::IsInlineThread(outgoing_thread_ref)) { - if (!cursor.ReadInlineThread(&outgoing_thread_info)) { - context_->storage->IncrementStats(stats::fuchsia_invalid_event); - return; - } + // Skip over inline thread + cursor.ReadInlineThread(nullptr); } else { - outgoing_thread_info = - current_provider_->GetThread(outgoing_thread_ref); + record.InsertThread( + outgoing_thread_ref, + current_provider_->GetThread(outgoing_thread_ref)); } - Thread& outgoing_thread = GetThread(outgoing_thread_info.tid); - FuchsiaThreadInfo incoming_thread_info; if (fuchsia_trace_utils::IsInlineThread(incoming_thread_ref)) { - if (!cursor.ReadInlineThread(&incoming_thread_info)) { - context_->storage->IncrementStats(stats::fuchsia_invalid_event); - return; - } + // Skip over inline thread + cursor.ReadInlineThread(nullptr); } else { - incoming_thread_info = - current_provider_->GetThread(incoming_thread_ref); - } - Thread& incoming_thread = GetThread(incoming_thread_info.tid); - - // Idle threads are identified by pid == 0 and prio == 0. - const bool incoming_is_idle = - incoming_thread.info.pid == 0 && incoming_priority == 0; - const bool outgoing_is_idle = - outgoing_thread.info.pid == 0 && outgoing_priority == 0; - - // Handle switching away from the currently running thread. - if (!outgoing_is_idle) { - SwitchFrom(&outgoing_thread, ts, cpu, outgoing_state); - } - - // Handle switching to the new currently running thread. - if (!incoming_is_idle) { - SwitchTo(&incoming_thread, ts, cpu, incoming_priority); + record.InsertThread( + incoming_thread_ref, + current_provider_->GetThread(incoming_thread_ref)); } + context_->sorter->PushFuchsiaRecord(ts, std::move(record)); break; } case kSchedulerEventContextSwitch: { - const uint32_t argument_count = - fuchsia_trace_utils::ReadField(header, 16, 19); - const uint32_t cpu = - fuchsia_trace_utils::ReadField(header, 20, 35); - const uint32_t outgoing_state = - fuchsia_trace_utils::ReadField(header, 36, 39); + FuchsiaRecord record(std::move(tbv)); + record.set_ticks_per_second(current_provider_->ticks_per_second); int64_t ts; if (!cursor.ReadTimestamp(current_provider_->ticks_per_second, &ts)) { @@ -726,65 +551,27 @@ void FuchsiaTraceTokenizer::ParseRecord(TraceBlobView tbv) { return; } - uint64_t outgoing_tid; - if (!cursor.ReadUint64(&outgoing_tid)) { + // Skip outgoing tid. + if (!cursor.ReadUint64(nullptr)) { context_->storage->IncrementStats(stats::fuchsia_invalid_event); return; } - Thread& outgoing_thread = GetThread(outgoing_tid); - uint64_t incoming_tid; - if (!cursor.ReadUint64(&incoming_tid)) { + // Skip incoming tid. + if (!cursor.ReadUint64(nullptr)) { context_->storage->IncrementStats(stats::fuchsia_invalid_event); return; } - Thread& incoming_thread = GetThread(incoming_tid); - - auto maybe_args = FuchsiaTraceParser::ParseArgs( - cursor, argument_count, intern_string, get_string); - if (!maybe_args.has_value()) { - context_->storage->IncrementStats(stats::fuchsia_invalid_event); - return; - } - - int32_t incoming_weight = 0; - int32_t outgoing_weight = 0; - - for (const auto& arg : *maybe_args) { - if (arg.name == incoming_weight_id_) { - if (arg.value.Type() != ArgValue::ArgType::kInt32) { - context_->storage->IncrementStats(stats::fuchsia_invalid_event); - return; - } - incoming_weight = arg.value.Int32(); - } else if (arg.name == outgoing_weight_id_) { - if (arg.value.Type() != ArgValue::ArgType::kInt32) { - context_->storage->IncrementStats(stats::fuchsia_invalid_event); - return; - } - outgoing_weight = arg.value.Int32(); - } - } - - const bool incoming_is_idle = incoming_weight == kIdleWeight; - const bool outgoing_is_idle = outgoing_weight == kIdleWeight; - // Handle switching away from the currently running thread. - if (!outgoing_is_idle) { - SwitchFrom(&outgoing_thread, ts, cpu, outgoing_state); - } - - // Handle switching to the new currently running thread. - if (!incoming_is_idle) { - SwitchTo(&incoming_thread, ts, cpu, incoming_weight); - } + const auto n_args = + fuchsia_trace_utils::ReadField(header, 16, 19); + insert_args(n_args, cursor, record); + context_->sorter->PushFuchsiaRecord(ts, std::move(record)); break; } case kSchedulerEventThreadWakeup: { - const uint32_t argument_count = - fuchsia_trace_utils::ReadField(header, 16, 19); - const uint32_t cpu = - fuchsia_trace_utils::ReadField(header, 20, 35); + FuchsiaRecord record(std::move(tbv)); + record.set_ticks_per_second(current_provider_->ticks_per_second); int64_t ts; if (!cursor.ReadTimestamp(current_provider_->ticks_per_second, &ts)) { @@ -796,36 +583,10 @@ void FuchsiaTraceTokenizer::ParseRecord(TraceBlobView tbv) { return; } - uint64_t waking_tid; - if (!cursor.ReadUint64(&waking_tid)) { - context_->storage->IncrementStats(stats::fuchsia_invalid_event); - return; - } - Thread& waking_thread = GetThread(waking_tid); - - auto maybe_args = FuchsiaTraceParser::ParseArgs( - cursor, argument_count, intern_string, get_string); - if (!maybe_args.has_value()) { - context_->storage->IncrementStats(stats::fuchsia_invalid_event); - return; - } - - int32_t waking_weight = 0; - - for (const auto& arg : *maybe_args) { - if (arg.name == weight_id_) { - if (arg.value.Type() != ArgValue::ArgType::kInt32) { - context_->storage->IncrementStats(stats::fuchsia_invalid_event); - return; - } - waking_weight = arg.value.Int32(); - } - } - - const bool waking_is_idle = waking_weight == kIdleWeight; - if (!waking_is_idle) { - Wake(&waking_thread, ts, cpu); - } + const auto n_args = + fuchsia_trace_utils::ReadField(header, 16, 19); + insert_args(n_args, cursor, record); + context_->sorter->PushFuchsiaRecord(ts, std::move(record)); break; } default: @@ -854,5 +615,4 @@ base::Status FuchsiaTraceTokenizer::NotifyEndOfFile() { return base::OkStatus(); } -} // namespace trace_processor -} // namespace perfetto +} // namespace perfetto::trace_processor diff --git a/src/trace_processor/importers/fuchsia/fuchsia_trace_tokenizer.h b/src/trace_processor/importers/fuchsia/fuchsia_trace_tokenizer.h index fb669a9223..8ba5fc7fca 100644 --- a/src/trace_processor/importers/fuchsia/fuchsia_trace_tokenizer.h +++ b/src/trace_processor/importers/fuchsia/fuchsia_trace_tokenizer.h @@ -76,36 +76,8 @@ class FuchsiaTraceTokenizer : public ChunkedTraceReader { uint64_t ticks_per_second = 1000000000; }; - // Tracks the state for updating sched slice and thread state tables. - struct Thread { - explicit Thread(uint64_t tid) : info{0, tid} {} - - FuchsiaThreadInfo info; - int64_t last_ts{0}; - std::optional last_slice_row; - std::optional last_state_row; - }; - - void SwitchFrom(Thread* thread, - int64_t ts, - uint32_t cpu, - uint32_t thread_state); - void SwitchTo(Thread* thread, int64_t ts, uint32_t cpu, int32_t weight); - void Wake(Thread* thread, int64_t ts, uint32_t cpu); - - // Allocates or returns an existing Thread instance for the given tid. - Thread& GetThread(uint64_t tid) { - auto search = threads_.find(tid); - if (search != threads_.end()) { - return search->second; - } - auto result = threads_.emplace(tid, tid); - return result.first->second; - } - void ParseRecord(TraceBlobView); void RegisterProvider(uint32_t, std::string); - StringId IdForOutgoingThreadState(uint32_t state); TraceProcessorContext* const context_; std::vector leftover_bytes_; @@ -118,24 +90,8 @@ class FuchsiaTraceTokenizer : public ChunkedTraceReader { std::unordered_map> providers_; ProviderInfo* current_provider_; - // Interned string ids for the relevant thread states. - StringId running_string_id_; - StringId runnable_string_id_; - StringId preempted_string_id_; - StringId waking_string_id_; - StringId blocked_string_id_; - StringId suspended_string_id_; - StringId exit_dying_string_id_; - StringId exit_dead_string_id_; - // Interned string ids for record arguments. - StringId incoming_weight_id_; - StringId outgoing_weight_id_; - StringId weight_id_; StringId process_id_; - - // Map from tid to Thread. - std::unordered_map threads_; }; } // namespace perfetto::trace_processor diff --git a/src/trace_processor/importers/fuchsia/fuchsia_trace_utils.h b/src/trace_processor/importers/fuchsia/fuchsia_trace_utils.h index d4a3efa608..69f3ed43c2 100644 --- a/src/trace_processor/importers/fuchsia/fuchsia_trace_utils.h +++ b/src/trace_processor/importers/fuchsia/fuchsia_trace_utils.h @@ -17,18 +17,16 @@ #ifndef SRC_TRACE_PROCESSOR_IMPORTERS_FUCHSIA_FUCHSIA_TRACE_UTILS_H_ #define SRC_TRACE_PROCESSOR_IMPORTERS_FUCHSIA_FUCHSIA_TRACE_UTILS_H_ -#include -#include -#include +#include +#include +#include "perfetto/base/logging.h" #include "perfetto/ext/base/string_view.h" -#include "perfetto/trace_processor/trace_blob_view.h" #include "src/trace_processor/importers/fuchsia/fuchsia_record.h" #include "src/trace_processor/storage/trace_storage.h" +#include "src/trace_processor/types/variadic.h" -namespace perfetto { -namespace trace_processor { -namespace fuchsia_trace_utils { +namespace perfetto::trace_processor::fuchsia_trace_utils { template T ReadField(uint64_t word, size_t begin, size_t end) { @@ -206,7 +204,7 @@ class ArgValue { class RecordCursor { public: RecordCursor(const uint8_t* begin, size_t length) - : begin_(begin), end_(begin + length), word_index_(0) {} + : begin_(begin), end_(begin + length) {} size_t WordIndex(); void SetWordIndex(size_t index); @@ -226,11 +224,9 @@ class RecordCursor { const uint8_t* begin_; const uint8_t* end_; - size_t word_index_; + size_t word_index_{}; }; -} // namespace fuchsia_trace_utils -} // namespace trace_processor -} // namespace perfetto +} // namespace perfetto::trace_processor::fuchsia_trace_utils #endif // SRC_TRACE_PROCESSOR_IMPORTERS_FUCHSIA_FUCHSIA_TRACE_UTILS_H_