diff --git a/src/trace_processor/importers/fuchsia/fuchsia_trace_parser.cc b/src/trace_processor/importers/fuchsia/fuchsia_trace_parser.cc index 5df25c2a57a..bc5ce4c6f10 100644 --- a/src/trace_processor/importers/fuchsia/fuchsia_trace_parser.cc +++ b/src/trace_processor/importers/fuchsia/fuchsia_trace_parser.cc @@ -16,6 +16,7 @@ #include "src/trace_processor/importers/fuchsia/fuchsia_trace_parser.h" +#include #include #include #include @@ -91,6 +92,21 @@ constexpr uint32_t kThreadDead = 5; constexpr int32_t kIdleWeight = std::numeric_limits::min(); +// CPU idle states (matching Linux/Android conventions: 0 is idle, 0xFFFFFFFF is +// active/exit idle) +constexpr double kCpuIdleState = 0.0; +constexpr double kCpuActiveState = + static_cast(std::numeric_limits::max()); + +// Trace contract for CPU frequency counter events emitted by Fuchsia CPU +// drivers, aligned with the Linux ftrace "power/cpu_frequency" event: the +// counter record carries the logical core index in the "cpu" argument and the +// frequency in kHz in the "value" argument. +constexpr char kCpuFrequencyCategory[] = "power"; +constexpr char kCpuFrequencyEventName[] = "cpu_frequency"; +constexpr char kCpuFrequencyCpuArg[] = "cpu"; +constexpr char kCpuFrequencyValueArg[] = "value"; + constexpr auto kCounterBlueprint = tracks::CounterBlueprint( "fuchsia_counter", tracks::UnknownUnitBlueprint(), @@ -98,6 +114,65 @@ constexpr auto kCounterBlueprint = tracks::CounterBlueprint( tracks::kNameFromTraceDimensionBlueprint), tracks::DynamicNameBlueprint()); +// Returns the numeric value carried by |value| as a double, or std::nullopt if +// the argument does not hold a numeric type. Range and finiteness checks are +// left to the caller. +std::optional ArgValueToDouble( + const fuchsia_trace_utils::ArgValue& value) { + switch (value.Type()) { + case fuchsia_trace_utils::ArgValue::kInt32: + return static_cast(value.Int32()); + case fuchsia_trace_utils::ArgValue::kUint32: + return static_cast(value.Uint32()); + case fuchsia_trace_utils::ArgValue::kInt64: + return static_cast(value.Int64()); + case fuchsia_trace_utils::ArgValue::kUint64: + return static_cast(value.Uint64()); + case fuchsia_trace_utils::ArgValue::kDouble: + return value.Double(); + case fuchsia_trace_utils::ArgValue::kNull: + case fuchsia_trace_utils::ArgValue::kString: + case fuchsia_trace_utils::ArgValue::kPointer: + case fuchsia_trace_utils::ArgValue::kKoid: + case fuchsia_trace_utils::ArgValue::kBool: + case fuchsia_trace_utils::ArgValue::kUnknown: + return std::nullopt; + } + PERFETTO_FATAL("For GCC"); +} + +// Returns the logical CPU core index carried by |value|, or std::nullopt if the +// value is not a valid core index. Negative and out of range indices +// (>= CpuTracker::kMaxCpusPerMachine) are rejected, as are non-integer and +// non-numeric argument types. +std::optional ExtractCpuCoreIndex( + const fuchsia_trace_utils::ArgValue& value) { + if (value.Type() == fuchsia_trace_utils::ArgValue::kDouble) { + return std::nullopt; + } + // Integers up to 2^53 round-trip exactly through a double, so every value in + // the valid core index range is compared and converted without loss below. + std::optional core_index = ArgValueToDouble(value); + if (!core_index.has_value() || *core_index < 0 || + *core_index >= CpuTracker::kMaxCpusPerMachine) { + return std::nullopt; + } + return static_cast(*core_index); +} + +// Returns the frequency in kHz carried by |value|, or std::nullopt if the +// value is not a valid frequency. Negative and non-finite (NaN, infinity) +// frequencies are rejected, as are non-numeric argument types. +std::optional ExtractFrequencyKhz( + const fuchsia_trace_utils::ArgValue& value) { + std::optional frequency_khz = ArgValueToDouble(value); + if (!frequency_khz.has_value() || !std::isfinite(*frequency_khz) || + *frequency_khz < 0) { + return std::nullopt; + } + return frequency_khz; +} + } // namespace FuchsiaTraceParser::FuchsiaTraceParser(TraceProcessorContext* context) @@ -106,6 +181,14 @@ FuchsiaTraceParser::FuchsiaTraceParser(TraceProcessorContext* context) waker_id_(context->storage->InternString("waker")), incoming_weight_id_(context->storage->InternString("incoming_weight")), outgoing_weight_id_(context->storage->InternString("outgoing_weight")), + cpu_frequency_category_id_( + context->storage->InternString(kCpuFrequencyCategory)), + cpu_frequency_name_id_( + context->storage->InternString(kCpuFrequencyEventName)), + cpu_frequency_cpu_arg_id_( + context->storage->InternString(kCpuFrequencyCpuArg)), + cpu_frequency_value_arg_id_( + context->storage->InternString(kCpuFrequencyValueArg)), running_string_id_(context->storage->InternString("Running")), runnable_string_id_(context->storage->InternString("R")), waking_string_id_(context->storage->InternString("W")), @@ -329,69 +412,50 @@ void FuchsiaTraceParser::Parse(int64_t, FuchsiaRecord fr) { break; } case kCounter: { - UniquePid upid = - procs->GetOrCreateProcess(static_cast(tinfo.pid)); - std::string name_str = - context_->storage->GetString(name).ToStdString(); uint64_t counter_id; if (!cursor.ReadUint64(&counter_id)) { context_->stats_tracker->IncrementStats( stats::fuchsia_record_read_error); return; } + + // CPU frequency counters go to the standard per-core cpufreq tracks + // instead of to a generic process counter track. + if (cat == cpu_frequency_category_id_ && + name == cpu_frequency_name_id_) { + ParseCpuFrequencyCounter(ts, *maybe_args); + break; + } + + UniquePid upid = + procs->GetOrCreateProcess(static_cast(tinfo.pid)); + std::string name_str = + context_->storage->GetString(name).ToStdString(); + // Note: In the Fuchsia trace format, counter values are stored // in the arguments for the record, with the data series defined // by both the record name and the argument name. In Perfetto, // counters only have one name, so we combine both names into // one here. for (const Arg& arg : *maybe_args) { + std::optional counter_value = ArgValueToDouble(arg.value); + if (!counter_value.has_value()) { + context_->stats_tracker->IncrementStats( + stats::fuchsia_non_numeric_counters); + continue; + } std::string counter_name_str = name_str + ":"; counter_name_str += context_->storage->GetString(arg.name).ToStdString(); counter_name_str += ":" + std::to_string(counter_id); - bool is_valid_value = false; - double counter_value = -1; - switch (arg.value.Type()) { - case fuchsia_trace_utils::ArgValue::kInt32: - is_valid_value = true; - counter_value = static_cast(arg.value.Int32()); - break; - case fuchsia_trace_utils::ArgValue::kUint32: - is_valid_value = true; - counter_value = static_cast(arg.value.Uint32()); - break; - case fuchsia_trace_utils::ArgValue::kInt64: - is_valid_value = true; - counter_value = static_cast(arg.value.Int64()); - break; - case fuchsia_trace_utils::ArgValue::kUint64: - is_valid_value = true; - counter_value = static_cast(arg.value.Uint64()); - break; - case fuchsia_trace_utils::ArgValue::kDouble: - is_valid_value = true; - counter_value = arg.value.Double(); - break; - case fuchsia_trace_utils::ArgValue::kNull: - case fuchsia_trace_utils::ArgValue::kString: - case fuchsia_trace_utils::ArgValue::kPointer: - case fuchsia_trace_utils::ArgValue::kKoid: - case fuchsia_trace_utils::ArgValue::kBool: - case fuchsia_trace_utils::ArgValue::kUnknown: - context_->stats_tracker->IncrementStats( - stats::fuchsia_non_numeric_counters); - break; - } - if (is_valid_value) { - base::StringView counter_name_str_view(counter_name_str); - StringId counter_name_id = - context_->storage->InternString(counter_name_str_view); - TrackId track = context_->track_tracker->InternTrack( - kCounterBlueprint, - tracks::Dimensions(upid, counter_name_str_view), - tracks::DynamicName(counter_name_id)); - context_->event_tracker->PushCounter(ts, counter_value, track); - } + base::StringView counter_name_str_view(counter_name_str); + StringId counter_name_id = + context_->storage->InternString(counter_name_str_view); + TrackId track = context_->track_tracker->InternTrack( + kCounterBlueprint, + tracks::Dimensions(upid, counter_name_str_view), + tracks::DynamicName(counter_name_id)); + context_->event_tracker->PushCounter(ts, *counter_value, track); } break; } @@ -614,6 +678,9 @@ void FuchsiaTraceParser::Parse(int64_t, FuchsiaRecord fr) { if (!incoming_is_idle) { SwitchTo(&incoming_thread, ts, cpu, incoming_priority); } + + MaybePushCpuIdleTransition(ts, cpu, outgoing_is_idle, + incoming_is_idle); break; } case kSchedulerEventContextSwitch: { @@ -695,6 +762,9 @@ void FuchsiaTraceParser::Parse(int64_t, FuchsiaRecord fr) { if (!incoming_is_idle) { SwitchTo(&incoming_thread, ts, cpu, incoming_weight); } + + MaybePushCpuIdleTransition(ts, cpu, outgoing_is_idle, + incoming_is_idle); break; } case kSchedulerEventThreadWakeup: { @@ -922,6 +992,51 @@ void FuchsiaTraceParser::Wake(Thread* thread, thread->last_state_row = state_row_number; } +void FuchsiaTraceParser::ParseCpuFrequencyCounter( + int64_t ts, + const std::vector& args) { + std::optional cpu; + std::optional frequency_khz; + // A record which repeats "cpu" or "value", or which carries an unusable core + // index or frequency, does not honour the contract and is dropped whole. + bool is_valid_event = true; + for (const Arg& arg : args) { + if (arg.name == cpu_frequency_cpu_arg_id_) { + is_valid_event = is_valid_event && !cpu.has_value(); + cpu = ExtractCpuCoreIndex(arg.value); + is_valid_event = is_valid_event && cpu.has_value(); + } else if (arg.name == cpu_frequency_value_arg_id_) { + is_valid_event = is_valid_event && !frequency_khz.has_value(); + frequency_khz = ExtractFrequencyKhz(arg.value); + is_valid_event = is_valid_event && frequency_khz.has_value(); + } + } + + if (!is_valid_event || !cpu.has_value() || !frequency_khz.has_value()) { + context_->stats_tracker->IncrementStats(stats::fuchsia_invalid_event); + return; + } + + context_->cpu_tracker->GetOrCreateCpu(*cpu); + TrackId track = context_->track_tracker->InternTrack( + tracks::kCpuFrequencyBlueprint, tracks::Dimensions(*cpu)); + context_->event_tracker->PushCounter(ts, *frequency_khz, track); +} + +void FuchsiaTraceParser::MaybePushCpuIdleTransition(int64_t ts, + uint32_t cpu, + bool outgoing_is_idle, + bool incoming_is_idle) { + if (outgoing_is_idle == incoming_is_idle) { + return; + } + context_->cpu_tracker->GetOrCreateCpu(cpu); + TrackId idle_track = context_->track_tracker->InternTrack( + tracks::kCpuIdleBlueprint, tracks::Dimensions(cpu)); + double value = incoming_is_idle ? kCpuIdleState : kCpuActiveState; + context_->event_tracker->PushCounter(ts, value, idle_track); +} + StringId FuchsiaTraceParser::IdForOutgoingThreadState(uint32_t state) { switch (state) { case kThreadNew: diff --git a/src/trace_processor/importers/fuchsia/fuchsia_trace_parser.h b/src/trace_processor/importers/fuchsia/fuchsia_trace_parser.h index f75241ff90c..84d93aa7090 100644 --- a/src/trace_processor/importers/fuchsia/fuchsia_trace_parser.h +++ b/src/trace_processor/importers/fuchsia/fuchsia_trace_parser.h @@ -91,6 +91,19 @@ class FuchsiaTraceParser uint32_t cpu, std::optional waker_utid = std::nullopt, std::optional waker_tid = std::nullopt); + // Routes a "power" / "cpu_frequency" counter record to the per-core cpufreq + // track named by its "cpu" argument, using the kHz frequency carried in its + // "value" argument. Records violating that contract are dropped and counted + // in stats::fuchsia_invalid_event. + void ParseCpuFrequencyCounter(int64_t ts, const std::vector& args); + // Synthesizes a CPU idle counter event for the given CPU core matching + // Linux/Android cpuidle conventions: entering idle pushes the idle state, + // leaving idle pushes the exit-idle active state. Context switches which do + // not change the idle state of the core push nothing. + void MaybePushCpuIdleTransition(int64_t ts, + uint32_t cpu, + bool outgoing_is_idle, + bool incoming_is_idle); StringId IdForOutgoingThreadState(uint32_t state); @@ -102,6 +115,12 @@ class FuchsiaTraceParser const StringId incoming_weight_id_; const StringId outgoing_weight_id_; + // Interned string ids for the CPU frequency counter trace contract. + const StringId cpu_frequency_category_id_; + const StringId cpu_frequency_name_id_; + const StringId cpu_frequency_cpu_arg_id_; + const StringId cpu_frequency_value_arg_id_; + // Interned string ids for the relevant thread states. const StringId running_string_id_; const StringId runnable_string_id_; diff --git a/test/trace_processor/diff_tests/parser/fuchsia/fuchsia_cpu_frequency.py b/test/trace_processor/diff_tests/parser/fuchsia/fuchsia_cpu_frequency.py new file mode 100755 index 00000000000..00e0b76ed72 --- /dev/null +++ b/test/trace_processor/diff_tests/parser/fuchsia/fuchsia_cpu_frequency.py @@ -0,0 +1,145 @@ +#!/usr/bin/env python3 +# Copyright 2026 The Fuchsia Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +# Emits a Fuchsia trace holding "power" / "cpu_frequency" counter records, both +# well formed ones and ones violating the trace contract. + +import math +import struct +import sys + +# String indices interned below. +POWER = 1 +CPU_FREQUENCY = 2 +CPU = 3 +VALUE = 4 +CLUSTER = 5 +NOT_A_NUMBER = 6 + + +def push_word(w): + sys.stdout.buffer.write(struct.pack('