trace_processor: Add cpuidle state residences
Introduce cpuidle states cpu_counter_tracks. Counter tracks are named
cpuidle.{state}, with the state name determined by
/sys/devices/system/cpu/cpu*/cpuidle/state*/name.
Bug: 337135369
Test: build trace_processor locally, run a trace with `cpuidle_period_ms` enabled and check counters/cpu_counter_tracks
Test: diff tests
Change-Id: Ia12f5578d6a754e6ebe7d4fea1d70ab56fddb07a
diff --git a/src/trace_processor/importers/proto/proto_trace_parser_impl_unittest.cc b/src/trace_processor/importers/proto/proto_trace_parser_impl_unittest.cc
index e549fa5..efa53c9 100644
--- a/src/trace_processor/importers/proto/proto_trace_parser_impl_unittest.cc
+++ b/src/trace_processor/importers/proto/proto_trace_parser_impl_unittest.cc
@@ -649,6 +649,24 @@
EXPECT_EQ(row->ucpu().value, 1u);
}
+TEST_F(ProtoTraceParserTest, LoadCpuIdleStats) {
+ auto* packet = trace_->add_packet();
+ uint64_t ts = 1000;
+ packet->set_timestamp(ts);
+ auto* bundle = packet->set_sys_stats();
+ auto* cpuidle_state = bundle->add_cpuidle_state();
+ cpuidle_state->set_cpu_id(0);
+ auto* cpuidle_state_entry = cpuidle_state->add_cpuidle_state_entry();
+ cpuidle_state_entry->set_state("mock_state0");
+ cpuidle_state_entry->set_duration_us(20000);
+ EXPECT_CALL(*event_, PushCounter(static_cast<int64_t>(ts),
+ static_cast<double>(20000), TrackId{0u}));
+ Tokenize();
+ context_.sorter->ExtractEventsForced();
+
+ EXPECT_EQ(context_.storage->track_table().row_count(), 1u);
+}
+
TEST_F(ProtoTraceParserTest, LoadMemInfo) {
auto* packet = trace_->add_packet();
uint64_t ts = 1000;
diff --git a/src/trace_processor/importers/proto/system_probes_parser.cc b/src/trace_processor/importers/proto/system_probes_parser.cc
index 3cfe982..162aa1b 100644
--- a/src/trace_processor/importers/proto/system_probes_parser.cc
+++ b/src/trace_processor/importers/proto/system_probes_parser.cc
@@ -486,6 +486,27 @@
context_->event_tracker->PushCounter(
ts, static_cast<double>(thermal.temp()), track);
}
+
+ for (auto it = sys_stats.cpuidle_state(); it; ++it) {
+ ParseCpuIdleStats(ts, *it);
+ }
+}
+
+void SystemProbesParser::ParseCpuIdleStats(int64_t ts, ConstBytes blob) {
+ protos::pbzero::SysStats::CpuIdleState::Decoder cpuidle_state(blob);
+ uint32_t cpu_id = cpuidle_state.cpu_id();
+ for (auto cpuidle_field = cpuidle_state.cpuidle_state_entry(); cpuidle_field;
+ ++cpuidle_field) {
+ protos::pbzero::SysStats::CpuIdleStateEntry::Decoder idle(*cpuidle_field);
+ std::string state = idle.state().ToStdString();
+ uint64_t time = idle.duration_us();
+
+ std::string track_name = "cpuidle." + state;
+ StringId string_id = context_->storage->InternString(track_name.c_str());
+ TrackId track =
+ context_->track_tracker->InternCpuCounterTrack(string_id, cpu_id);
+ context_->event_tracker->PushCounter(ts, static_cast<double>(time), track);
+ }
}
void SystemProbesParser::ParseProcessTree(ConstBytes blob) {
diff --git a/src/trace_processor/importers/proto/system_probes_parser.h b/src/trace_processor/importers/proto/system_probes_parser.h
index 124c003..c888400 100644
--- a/src/trace_processor/importers/proto/system_probes_parser.h
+++ b/src/trace_processor/importers/proto/system_probes_parser.h
@@ -46,6 +46,7 @@
void ParseThreadStats(int64_t timestamp, uint32_t pid, ConstBytes);
void ParseDiskStats(int64_t ts, ConstBytes blob);
void ParseProcessFds(int64_t ts, uint32_t pid, ConstBytes);
+ void ParseCpuIdleStats(int64_t ts, ConstBytes);
TraceProcessorContext* const context_;
diff --git a/test/trace_processor/diff_tests/include_index.py b/test/trace_processor/diff_tests/include_index.py
index d6a1960..5840800 100644
--- a/test/trace_processor/diff_tests/include_index.py
+++ b/test/trace_processor/diff_tests/include_index.py
@@ -79,6 +79,7 @@
from diff_tests.parser.parsing.tests_debug_annotation import ParsingDebugAnnotation
from diff_tests.parser.parsing.tests_memory_counters import ParsingMemoryCounters
from diff_tests.parser.parsing.tests_rss_stats import ParsingRssStats
+from diff_tests.parser.parsing.tests_sys_stats import ParsingSysStats
from diff_tests.parser.parsing.tests_traced_stats import ParsingTracedStats
from diff_tests.parser.power.tests_energy_breakdown import PowerEnergyBreakdown
from diff_tests.parser.power.tests_entity_state_residency import EntityStateResidency
@@ -228,6 +229,7 @@
*ParsingDebugAnnotation(index_path, 'parser/parsing',
'ParsingDebugAnnotation').fetch(),
*ParsingRssStats(index_path, 'parser/parsing', 'ParsingRssStats').fetch(),
+ *ParsingSysStats(index_path, 'parser/parsing', 'ParsingSysStats').fetch(),
*ParsingMemoryCounters(index_path, 'parser/parsing',
'ParsingMemoryCounters').fetch(),
*FtraceCrop(index_path, 'parser/ftrace', 'FtraceCrop').fetch(),
diff --git a/test/trace_processor/diff_tests/parser/parsing/tests_sys_stats.py b/test/trace_processor/diff_tests/parser/parsing/tests_sys_stats.py
new file mode 100644
index 0000000..8e00929
--- /dev/null
+++ b/test/trace_processor/diff_tests/parser/parsing/tests_sys_stats.py
@@ -0,0 +1,103 @@
+#!/usr/bin/env python3
+# Copyright (C) 2024 The Android Open Source Project
+#
+# 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 a
+#
+# http://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.
+
+from python.generators.diff_tests.testing import Path, DataPath, Metric
+from python.generators.diff_tests.testing import Csv, Json, TextProto
+from python.generators.diff_tests.testing import DiffTestBlueprint
+from python.generators.diff_tests.testing import TestSuite
+
+
+class ParsingSysStats(TestSuite):
+
+ def test_cpuidle_stats(self):
+ return DiffTestBlueprint(
+ trace=TextProto(r"""
+ packet {
+ sys_stats {
+ cpuidle_state {
+ cpu_id: 0
+ cpuidle_state_entry {
+ state: "C8"
+ duration_us: 486626084
+ }
+ }
+ }
+ timestamp: 71625871363623
+ trusted_packet_sequence_id: 2
+ }
+ packet {
+ sys_stats {
+ cpuidle_state {
+ cpu_id: 0
+ cpuidle_state_entry {
+ state: "C8"
+ duration_us: 486636254
+ }
+ }
+ }
+ timestamp: 71626000387166
+ trusted_packet_sequence_id: 2
+ }
+ """),
+ query="""
+ SELECT ts, cct.name, value, cct.cpu
+ FROM counter c
+ JOIN cpu_counter_track cct on c.track_id = cct.id
+ ORDER BY ts;
+ """,
+ out=Csv("""
+ "ts","name","value","cpu"
+ 71625871363623,"cpuidle.C8",486626084.000000,0
+ 71626000387166,"cpuidle.C8",486636254.000000,0
+ """))
+
+ def test_thermal_zones(self):
+ return DiffTestBlueprint(
+ trace=TextProto(r"""
+ packet {
+ sys_stats {
+ thermal_zone {
+ name: "thermal_zone0"
+ temp: 29
+ type: "x86_pkg_temp"
+ }
+ }
+ timestamp: 71625871363623
+ trusted_packet_sequence_id: 2
+ }
+ packet {
+ sys_stats {
+ thermal_zone {
+ name: "thermal_zone0"
+ temp: 31
+ type: "x86_pkg_temp"
+ }
+ }
+ timestamp: 71626000387166
+ trusted_packet_sequence_id: 2
+ }
+ """),
+ query="""
+ SELECT c.ts,
+ t.name,
+ c.value
+ FROM counter_track t
+ JOIN counter c ON t.id = c.track_id
+ """,
+ out=Csv("""
+ "ts","name","value"
+ 71625871363623,"x86_pkg_temp",29.000000
+ 71626000387166,"x86_pkg_temp",31.000000
+ """))