tp: parse process name, start seq id, and remap process start time Adds support for process_name, start_seq_id, and start_time_ms to the android_process_state plugin. Specifically: - Adds process_name and start_seq_id columns to __intrinsic_android_process_state. - Overrides TokenizePacket on AndroidProcessStateModule to convert start_time_ms to boottime/monotonically-synchronized trace time and synthesize chronologically sorted single-record process state dump packets before normal packet ingestion. - Updates main process table name and start_ts from AndroidProcessState snapshot records.
diff --git a/src/trace_processor/plugins/android_process_state/android_process_state_module.cc b/src/trace_processor/plugins/android_process_state/android_process_state_module.cc index 434bff6..560648c 100644 --- a/src/trace_processor/plugins/android_process_state/android_process_state_module.cc +++ b/src/trace_processor/plugins/android_process_state/android_process_state_module.cc
@@ -21,8 +21,12 @@ #include "perfetto/base/compiler.h" #include "protos/third_party/android/frameworks/base/proto/tracing/frameworks_base_trace_packet.pbzero.h" #include "protos/third_party/android/frameworks/base/proto/tracing/frameworks_base_track_event.pbzero.h" +#include "src/trace_processor/importers/common/clock_tracker.h" #include "src/trace_processor/importers/common/process_tracker.h" +#include "src/trace_processor/importers/proto/blob_packet_writer.h" +#include "src/trace_processor/importers/proto/packet_sequence_state_generation.h" #include "src/trace_processor/plugins/android_process_state/android_process_state_tracker.h" +#include "src/trace_processor/sorter/trace_sorter.h" #include "src/trace_processor/storage/trace_storage.h" namespace perfetto::trace_processor::android_process_state { @@ -46,6 +50,68 @@ AndroidProcessStateModule::~AndroidProcessStateModule() = default; +ModuleResult AndroidProcessStateModule::TokenizePacket( + const TokenizePacketArgs& args) { + if (args.field.id() != + fb::FrameworksBaseTracePacket::kAndroidProcessStateFieldNumber) { + return ModuleResult::Ignored(); + } + + fb::AndroidProcessStateSnapshot::Decoder dump( + args.field.Cast<fb::FrameworksBaseTracePacket::kAndroidProcessState>()); + for (auto it = dump.record(); it; ++it) { + fb::AndroidProcessStateSnapshot::Record::Decoder rec(*it); + int64_t real_ts = args.ts; + if (rec.has_start_time_ms()) { + std::optional<int64_t> start_ts = context_->clock_tracker->ToTraceTime( + ClockTracker::ClockId::Machine( + protos::pbzero::BUILTIN_CLOCK_BOOTTIME), + static_cast<int64_t>(rec.start_time_ms()) * 1000000LL); + if (start_ts.has_value()) { + real_ts = *start_ts; + } + } + + TraceBlobView tbv = context_->blob_packet_writer->WritePacket( + [&](protos::pbzero::TracePacket* pkt) { + pkt->set_timestamp(static_cast<uint64_t>(real_ts)); + auto* snap = pkt->BeginNestedMessage<fb::AndroidProcessStateSnapshot>( + fb::FrameworksBaseTracePacket::kAndroidProcessStateFieldNumber); + auto* r = snap->add_record(); + r->set_pid(rec.pid()); + if (rec.has_uid()) { + r->set_uid(rec.uid()); + } + if (rec.has_proc_state()) { + r->set_proc_state(rec.proc_state()); + } + if (rec.has_oom_score()) { + r->set_oom_score(rec.oom_score()); + } + if (rec.has_capability_flags()) { + r->set_capability_flags(rec.capability_flags()); + } + if (rec.has_process_name()) { + r->set_process_name(rec.process_name()); + } + if (rec.has_start_seq_id()) { + r->set_start_seq_id(rec.start_seq_id()); + } + if (rec.has_start_time_ms()) { + r->set_start_time_ms(rec.start_time_ms()); + } + }); + + RefPtr<PacketSequenceStateGeneration> state = + args.state ? args.state + : PacketSequenceStateGeneration::CreateFirst(context_); + module_context_->trace_packet_stream->Push( + real_ts, TracePacketData{std::move(tbv), std::move(state)}); + } + + return ModuleResult::Handled(); +} + void AndroidProcessStateModule::ParseField(const ParseFieldArgs& args) { switch (args.field.id()) { case fb::FrameworksBaseTracePacket::kAndroidProcessStateFieldNumber:
diff --git a/src/trace_processor/plugins/android_process_state/android_process_state_module.h b/src/trace_processor/plugins/android_process_state/android_process_state_module.h index 0c2eefe..de14da3 100644 --- a/src/trace_processor/plugins/android_process_state/android_process_state_module.h +++ b/src/trace_processor/plugins/android_process_state/android_process_state_module.h
@@ -42,6 +42,7 @@ tables::AndroidFreezerStateTable* freezer_state_table); ~AndroidProcessStateModule() override; + ModuleResult TokenizePacket(const TokenizePacketArgs& args) override; void ParseField(const ParseFieldArgs& args) override; void OnEventsFullyExtracted() override;
diff --git a/src/trace_processor/plugins/android_process_state/android_process_state_module_unittest.cc b/src/trace_processor/plugins/android_process_state/android_process_state_module_unittest.cc index bc1ca6a..15730e1 100644 --- a/src/trace_processor/plugins/android_process_state/android_process_state_module_unittest.cc +++ b/src/trace_processor/plugins/android_process_state/android_process_state_module_unittest.cc
@@ -56,6 +56,18 @@ using ::com::android::internal::pbzero::FrameworksBaseTracePacket; using ::perfetto::protos::pbzero::TracePacket; +class CaptureSink : public TraceSorter::Sink<TracePacketData, CaptureSink> { + public: + struct Packet { + int64_t ts; + TracePacketData data; + }; + void Parse(int64_t ts, TracePacketData data) { + packets.push_back({ts, std::move(data)}); + } + std::vector<Packet> packets; +}; + class AndroidProcessStateModuleTest : public testing::Test { public: AndroidProcessStateModuleTest() { @@ -74,6 +86,21 @@ context_.process_tracker = std::make_unique<ProcessTracker>(&context_); context_.global_args_tracker = std::make_unique<GlobalArgsTracker>(context_.storage.get()); + context_.blob_packet_writer = std::make_unique<BlobPacketWriter>(); + context_.trace_time_state = std::make_unique<TraceTimeState>( + ClockTracker::ClockId::Machine(protos::pbzero::BUILTIN_CLOCK_BOOTTIME)); + primary_sync_ = std::make_unique<ClockSynchronizer>( + context_.trace_time_state.get(), + std::make_unique<ClockSynchronizerListenerImpl>(&context_)); + context_.clock_tracker = std::make_unique<ClockTracker>( + &context_, primary_sync_.get(), /*is_primary=*/true); + + context_.sorter = std::make_unique<TraceSorter>( + &context_, TraceSorter::SortingMode::kFullSort); + auto sink = std::make_unique<CaptureSink>(); + capture_sink_ = sink.get(); + module_context_.trace_packet_stream = + context_.sorter->CreateStream(std::move(sink)); process_state_table_ = std::make_unique<tables::AndroidProcessStateTable>( context_.storage->mutable_string_pool()); @@ -122,13 +149,75 @@ protected: TraceProcessorContext context_; ProtoImporterModuleContext module_context_; + std::unique_ptr<ClockSynchronizer> primary_sync_; + CaptureSink* capture_sink_ = nullptr; std::unique_ptr<tables::AndroidProcessStateTable> process_state_table_; std::unique_ptr<tables::AndroidFreezerStateTable> freezer_state_table_; std::unique_ptr<AndroidProcessStateTracker> tracker_; std::unique_ptr<AndroidProcessStateModule> module_; }; +TEST_F(AndroidProcessStateModuleTest, TokenizePacketSplitsAndRemapsTimestamps) { + // Sync boottime clock with trace time + context_.clock_tracker->AddSnapshot({ + {ClockTracker::ClockId::Machine(protos::pbzero::BUILTIN_CLOCK_BOOTTIME), + 1000000000}, + {ClockTracker::ClockId::Machine(protos::pbzero::BUILTIN_CLOCK_MONOTONIC), + 1000000000}, + }); + + protozero::HeapBuffered<AndroidProcessStateSnapshot> snapshot; + { + auto* rec = snapshot->add_record(); + rec->set_pid(1001); + rec->set_uid(10001); + rec->set_process_name("com.test.app1"); + rec->set_start_seq_id(42); + rec->set_start_time_ms(5000); // 5000ms boottime + } + { + auto* rec = snapshot->add_record(); + rec->set_pid(1002); + rec->set_uid(10002); + rec->set_process_name("com.test.app2"); + rec->set_start_seq_id(43); + // no start_time_ms -> should use packet timestamp + } + std::vector<uint8_t> snap_bytes = snapshot.SerializeAsArray(); + + protozero::HeapBuffered<TracePacket> packet; + packet->AppendBytes( + FrameworksBaseTracePacket::kAndroidProcessStateFieldNumber, + snap_bytes.data(), snap_bytes.size()); + std::vector<uint8_t> bytes = packet.SerializeAsArray(); + + TraceBlobView tbv(TraceBlob::CopyFrom(bytes.data(), bytes.size())); + SelectiveTracePacketDecoder decoder(tbv.data(), tbv.length()); + TracePacketField field = decoder.FindUnknownField( + FrameworksBaseTracePacket::kAndroidProcessStateFieldNumber); + ASSERT_TRUE(field.valid()); + + ModuleResult res = module_->TokenizePacket( + {decoder, &tbv, 99999999, + PacketSequenceStateGeneration::CreateFirst(&context_), field}); + EXPECT_FALSE(res.ignored()); + + context_.sorter->ExtractEventsForced(); + ASSERT_EQ(capture_sink_->packets.size(), 2u); + + EXPECT_EQ(capture_sink_->packets[0].ts, 99999999LL); + EXPECT_EQ(capture_sink_->packets[1].ts, 5000000000LL); +} + TEST_F(AndroidProcessStateModuleTest, ParseDumpPopulatesProcessTable) { + // Sync boottime clock + context_.clock_tracker->AddSnapshot({ + {ClockTracker::ClockId::Machine(protos::pbzero::BUILTIN_CLOCK_BOOTTIME), + 1000000000}, + {ClockTracker::ClockId::Machine(protos::pbzero::BUILTIN_CLOCK_MONOTONIC), + 1000000000}, + }); + protozero::HeapBuffered<AndroidProcessStateSnapshot> snapshot; { auto* rec = snapshot->add_record(); @@ -137,6 +226,9 @@ rec->set_proc_state(2); // PROCESS_STATE_PERSISTENT rec->set_oom_score(-900); rec->set_capability_flags(1); + rec->set_process_name("com.android.systemui"); + rec->set_start_seq_id(10); + rec->set_start_time_ms(5000); } { auto* rec = snapshot->add_record(); @@ -145,6 +237,9 @@ rec->set_proc_state(19); // PROCESS_STATE_CACHED_EMPTY rec->set_oom_score(905); rec->set_capability_flags(0); + rec->set_process_name("com.example.cached"); + rec->set_start_seq_id(11); + rec->set_start_time_ms(8000); } std::vector<uint8_t> snap_bytes = snapshot.SerializeAsArray(); @@ -153,6 +248,11 @@ // Verify process_state table EXPECT_EQ(process_state_table_->row_count(), 2u); + auto rr = (*process_state_table_)[0]; + ASSERT_TRUE(rr.process_name().has_value()); + EXPECT_STREQ(context_.storage->GetString(*rr.process_name()).c_str(), + "com.android.systemui"); + EXPECT_EQ(*rr.start_seq_id(), 10LL); // Verify main process table (row 0 is reserved swapper/idle process) const auto& pt = context_.storage->process_table(); @@ -161,10 +261,20 @@ auto prr0 = pt[1]; EXPECT_EQ(prr0.pid(), 2001); EXPECT_EQ(prr0.uid(), 10002u); + ASSERT_TRUE(prr0.name().has_value()); + EXPECT_STREQ(context_.storage->GetString(*prr0.name()).c_str(), + "com.android.systemui"); + ASSERT_TRUE(prr0.start_ts().has_value()); + EXPECT_EQ(*prr0.start_ts(), 5000000000LL); auto prr1 = pt[2]; EXPECT_EQ(prr1.pid(), 2002); EXPECT_EQ(prr1.uid(), 10003u); + ASSERT_TRUE(prr1.name().has_value()); + EXPECT_STREQ(context_.storage->GetString(*prr1.name()).c_str(), + "com.example.cached"); + ASSERT_TRUE(prr1.start_ts().has_value()); + EXPECT_EQ(*prr1.start_ts(), 8000000000LL); } TEST_F(AndroidProcessStateModuleTest, ParseChangeAndDumpRewind) {
diff --git a/src/trace_processor/plugins/android_process_state/android_process_state_tracker.cc b/src/trace_processor/plugins/android_process_state/android_process_state_tracker.cc index c3bf5d2..f9f64eb 100644 --- a/src/trace_processor/plugins/android_process_state/android_process_state_tracker.cc +++ b/src/trace_processor/plugins/android_process_state/android_process_state_tracker.cc
@@ -19,8 +19,10 @@ #include <string> #include "perfetto/ext/base/string_view.h" +#include "protos/perfetto/common/builtin_clock.pbzero.h" #include "protos/third_party/android/frameworks/base/proto/tracing/frameworks_base_trace_packet.pbzero.h" #include "protos/third_party/android/frameworks/base/proto/tracing/frameworks_base_track_event.pbzero.h" +#include "src/trace_processor/importers/common/clock_tracker.h" #include "src/trace_processor/importers/common/process_tracker.h" #include "src/trace_processor/storage/trace_storage.h" #include "src/trace_processor/types/trace_processor_context.h" @@ -124,6 +126,25 @@ v.upid, static_cast<uint32_t>(rec.uid())); } + if (rec.has_process_name()) { + v.process_name = context_->storage->InternString(rec.process_name()); + context_->process_tracker->UpdateProcessName( + v.upid, *v.process_name, ProcessNamePriority::kSystem); + } + if (rec.has_start_seq_id()) { + v.start_seq_id = rec.start_seq_id(); + } + if (rec.has_start_time_ms()) { + v.start_time_ms = rec.start_time_ms(); + std::optional<int64_t> start_ts = context_->clock_tracker->ToTraceTime( + ClockTracker::ClockId::Machine( + protos::pbzero::BUILTIN_CLOCK_BOOTTIME), + static_cast<int64_t>(rec.start_time_ms()) * 1000000LL); + if (start_ts.has_value()) { + context_->process_tracker->SetStartTsIfUnset(v.upid, *start_ts); + } + } + // Note: android.util.proto.ProtoOutputStream ignores/omits 0 data points // during serialization on Android, so unset fields in the dump snapshot // represent 0. @@ -158,6 +179,15 @@ if (earliest.values.capability_flags.has_value()) { v.capability_flags = earliest.values.capability_flags; } + if (earliest.values.process_name.has_value()) { + v.process_name = earliest.values.process_name; + } + if (earliest.values.start_seq_id.has_value()) { + v.start_seq_id = earliest.values.start_seq_id; + } + if (earliest.values.start_time_ms.has_value()) { + v.start_time_ms = earliest.values.start_time_ms; + } } return initial; @@ -185,6 +215,12 @@ if (v.capability_flags.has_value()) { row.capability_flags = *v.capability_flags; } + if (v.process_name.has_value()) { + row.process_name = *v.process_name; + } + if (v.start_seq_id.has_value()) { + row.start_seq_id = *v.start_seq_id; + } process_state_table_->Insert(row); }
diff --git a/src/trace_processor/plugins/android_process_state/android_process_state_tracker.h b/src/trace_processor/plugins/android_process_state/android_process_state_tracker.h index 25524c6..07be436 100644 --- a/src/trace_processor/plugins/android_process_state/android_process_state_tracker.h +++ b/src/trace_processor/plugins/android_process_state/android_process_state_tracker.h
@@ -66,6 +66,9 @@ std::optional<int32_t> oom_score; std::optional<int32_t> proc_state; std::optional<int32_t> capability_flags; + std::optional<StringId> process_name; + std::optional<int64_t> start_seq_id; + std::optional<int64_t> start_time_ms; }; // Tracks the state before the first observed change (prev_*) for a process,
diff --git a/src/trace_processor/plugins/android_process_state/tables.py b/src/trace_processor/plugins/android_process_state/tables.py index 315618c..a9f1826 100644 --- a/src/trace_processor/plugins/android_process_state/tables.py +++ b/src/trace_processor/plugins/android_process_state/tables.py
@@ -35,11 +35,15 @@ cpp_access=CppAccess.READ, cpp_access_duration=CppAccessDuration.POST_FINALIZATION), C('upid', CppTableId(PROCESS_TABLE), cpp_access=CppAccess.READ), - C('proc_state', CppOptional(CppString())), - C('oom_score', CppOptional(CppInt32())), - C('capability_flags', CppOptional(CppInt32())), - C('reason', CppOptional(CppString())), - C('seq_id', CppOptional(CppInt64())), + C('proc_state', CppOptional(CppString()), cpp_access=CppAccess.READ), + C('oom_score', CppOptional(CppInt32()), cpp_access=CppAccess.READ), + C('capability_flags', + CppOptional(CppInt32()), + cpp_access=CppAccess.READ), + C('reason', CppOptional(CppString()), cpp_access=CppAccess.READ), + C('seq_id', CppOptional(CppInt64()), cpp_access=CppAccess.READ), + C('process_name', CppOptional(CppString()), cpp_access=CppAccess.READ), + C('start_seq_id', CppOptional(CppInt64()), cpp_access=CppAccess.READ), C('is_initial', CppUint32(), cpp_access=CppAccess.READ), ], tabledoc=TableDoc( @@ -60,6 +64,10 @@ 'Reason for state change (if from track event).', 'seq_id': 'Sequence ID for OOM adjustment iterations.', + 'process_name': + 'Process name from dump snapshot.', + 'start_seq_id': + 'Monotonically increasing process launch sequence ID.', 'is_initial': '1 for synthesized initial state row, 0 for change event.', },
diff --git a/test/trace_processor/diff_tests/parser/android/android_process_state.textproto b/test/trace_processor/diff_tests/parser/android/android_process_state.textproto index b014163..c773b1d 100644 --- a/test/trace_processor/diff_tests/parser/android/android_process_state.textproto +++ b/test/trace_processor/diff_tests/parser/android/android_process_state.textproto
@@ -131,6 +131,8 @@ proc_state: 1002 oom_score: 0 capability_flags: 1 + process_name: "com.android.systemui" + start_seq_id: 10 } record { pid: 200 @@ -138,6 +140,8 @@ proc_state: 1015 oom_score: 900 capability_flags: 0 + process_name: "com.example.app" + start_seq_id: 11 } # PID 300: Dump-only process with non-zero values. record {
diff --git a/test/trace_processor/diff_tests/parser/android/tests.py b/test/trace_processor/diff_tests/parser/android/tests.py index c06ff31..f3a27b4 100644 --- a/test/trace_processor/diff_tests/parser/android/tests.py +++ b/test/trace_processor/diff_tests/parser/android/tests.py
@@ -374,7 +374,10 @@ SELECT t.ts, p.pid, + p.name AS p_name, p.uid, + t.process_name, + t.start_seq_id, t.proc_state, t.oom_score, t.capability_flags, @@ -386,16 +389,16 @@ ORDER BY p.pid, t.ts, t.seq_id; """, out=Csv(""" - "ts","pid","uid","proc_state","oom_score","capability_flags","reason","seq_id","is_initial" - "[NULL]",100,10001,"PROCESS_STATE_TOP",0,1,"[NULL]","[NULL]",1 - "[NULL]",200,10002,"PROCESS_STATE_TOP",0,1,"[NULL]","[NULL]",1 - 2000,200,10002,"PROCESS_STATE_IMPORTANT_FOREGROUND",200,0,"OOM_ADJ_REASON_START_RECEIVER",10,0 - 4000,200,10002,"PROCESS_STATE_CACHED_ACTIVITY",900,0,"OOM_ADJ_REASON_BIND_SERVICE",11,0 - "[NULL]",300,10003,"PROCESS_STATE_PERSISTENT",-1000,1,"[NULL]","[NULL]",1 - "[NULL]",400,10004,"PROCESS_STATE_FOREGROUND_SERVICE",0,0,"[NULL]","[NULL]",1 - "[NULL]",500,"[NULL]","PROCESS_STATE_TOP",0,1,"[NULL]","[NULL]",1 - 2000,500,"[NULL]","PROCESS_STATE_BOUND_FOREGROUND_SERVICE",250,0,"OOM_ADJ_REASON_START_RECEIVER",20,0 - 2000,500,"[NULL]","PROCESS_STATE_IMPORTANT_FOREGROUND",300,0,"OOM_ADJ_REASON_BIND_SERVICE",21,0 + "ts","pid","p_name","uid","process_name","start_seq_id","proc_state","oom_score","capability_flags","reason","seq_id","is_initial" + "[NULL]",100,"com.android.systemui",10001,"com.android.systemui",10,"PROCESS_STATE_TOP",0,1,"[NULL]","[NULL]",1 + "[NULL]",200,"com.example.app",10002,"com.example.app",11,"PROCESS_STATE_TOP",0,1,"[NULL]","[NULL]",1 + 2000,200,"com.example.app",10002,"[NULL]","[NULL]","PROCESS_STATE_IMPORTANT_FOREGROUND",200,0,"OOM_ADJ_REASON_START_RECEIVER",10,0 + 4000,200,"com.example.app",10002,"[NULL]","[NULL]","PROCESS_STATE_CACHED_ACTIVITY",900,0,"OOM_ADJ_REASON_BIND_SERVICE",11,0 + "[NULL]",300,"[NULL]",10003,"[NULL]","[NULL]","PROCESS_STATE_PERSISTENT",-1000,1,"[NULL]","[NULL]",1 + "[NULL]",400,"[NULL]",10004,"[NULL]","[NULL]","PROCESS_STATE_FOREGROUND_SERVICE",0,0,"[NULL]","[NULL]",1 + "[NULL]",500,"[NULL]","[NULL]","[NULL]","[NULL]","PROCESS_STATE_TOP",0,1,"[NULL]","[NULL]",1 + 2000,500,"[NULL]","[NULL]","[NULL]","[NULL]","PROCESS_STATE_BOUND_FOREGROUND_SERVICE",250,0,"OOM_ADJ_REASON_START_RECEIVER",20,0 + 2000,500,"[NULL]","[NULL]","[NULL]","[NULL]","PROCESS_STATE_IMPORTANT_FOREGROUND",300,0,"OOM_ADJ_REASON_BIND_SERVICE",21,0 """)) def test_android_freezer_state(self):