tp: Track process lifecycle from Android framework events In some traces, ftrace only captures an initial process snapshot. Subsequent starts and deaths come from the Android framework via AndroidProcessStartEvent (process_bound) and AndroidBinderDiedEvent (binder_died) track events carrying pid, uid, and process_name. Hook into ProtoToArgsParser::AddParsingOverrideForType to intercept these events during arg parsing. For starts, reuse the existing process if ftrace already created it (filling in uid only), otherwise create one via StartNewProcessWithoutMainThread. For deaths, set uid/name on the process row before marking it ended. Add ProcessTracker::EndProcess() to handle ending processes created both with and without a main thread. Add ProcessTracker::UpidForPid() as a public lookup (previously test-only). Change-Id: I167ed69400f3bbc5f9f753407cdbfae83110fd3f
diff --git a/src/trace_processor/importers/common/process_tracker.cc b/src/trace_processor/importers/common/process_tracker.cc index c811b35..22dea82 100644 --- a/src/trace_processor/importers/common/process_tracker.cc +++ b/src/trace_processor/importers/common/process_tracker.cc
@@ -133,6 +133,24 @@ pids_.Erase(tid); } +void ProcessTracker::EndProcess(int64_t timestamp, int64_t pid) { + // First try ending via the main thread (handles the common case where a + // main thread exists). + EndThread(timestamp, pid); + + // If the process was created without a main thread (e.g. via + // StartNewProcessWithoutMainThread), EndThread won't find the thread. + // Look up the process directly by pid and set end_ts. + auto* upid_ptr = pids_.Find(pid); + if (!upid_ptr) + return; + auto& process_table = *context_->storage->mutable_process_table(); + if (!process_table[*upid_ptr].end_ts().has_value()) { + process_table[*upid_ptr].set_end_ts(timestamp); + } + pids_.Erase(pid); +} + std::optional<UniqueTid> ProcessTracker::GetThreadOrNull(int64_t tid) { return GetThreadOrNull(tid, std::nullopt); }
diff --git a/src/trace_processor/importers/common/process_tracker.h b/src/trace_processor/importers/common/process_tracker.h index 9b8bde4..eeba295 100644 --- a/src/trace_processor/importers/common/process_tracker.h +++ b/src/trace_processor/importers/common/process_tracker.h
@@ -200,12 +200,20 @@ // to the pid. UniquePid GetOrCreateProcessWithoutMainThread(int64_t pid); - // Returns the upid for a given pid. - std::optional<UniquePid> UpidForPidForTesting(uint32_t pid) { + // Returns the upid for a given pid, or nullopt if not tracked. + std::optional<UniquePid> UpidForPid(int64_t pid) { auto* it = pids_.Find(pid); return it ? std::make_optional(*it) : std::nullopt; } + // Ends a process by pid, setting end_ts and removing from the pid map. + // Unlike EndThread, this does not require a main thread to exist. + void EndProcess(int64_t timestamp, int64_t pid); + + std::optional<UniquePid> UpidForPidForTesting(uint32_t pid) { + return UpidForPid(static_cast<int64_t>(pid)); + } + // Returns the bounds of a range that includes all UniqueTids that have the // requested tid. UniqueThreadBounds UtidsForTidForTesting(int64_t tid) {
diff --git a/src/trace_processor/importers/common/process_tracker_unittest.cc b/src/trace_processor/importers/common/process_tracker_unittest.cc index b272a2e..7c5c7bc 100644 --- a/src/trace_processor/importers/common/process_tracker_unittest.cc +++ b/src/trace_processor/importers/common/process_tracker_unittest.cc
@@ -329,6 +329,319 @@ 1029u); } +TEST_F(ProcessTrackerTest, EndProcessWithMainThread) { + context.process_tracker->StartNewProcess( + 1000, std::nullopt, 123, kNullStringId, ThreadNamePriority::kFtrace); + context.process_tracker->EndProcess(2000, 123); + + ASSERT_EQ(context.storage->process_table()[1].end_ts(), 2000); + ASSERT_EQ(context.storage->thread_table()[1].end_ts(), 2000); + + // PID should be freed: creating a new process should yield a new upid. + auto new_upid = context.process_tracker->GetOrCreateProcess(123); + ASSERT_NE(new_upid, UniquePid{1}); +} + +TEST_F(ProcessTrackerTest, EndProcessWithoutMainThread) { + auto upid = context.process_tracker->StartNewProcessWithoutMainThread( + 1000, std::nullopt, 123, kNullStringId, + ThreadNamePriority::kGenericKernelTask); + context.process_tracker->EndProcess(2000, 123); + + ASSERT_EQ(context.storage->process_table()[upid].end_ts(), 2000); + // No main thread should exist. + ASSERT_FALSE(context.process_tracker->GetThreadOrNull(123).has_value()); + + // PID should be freed: creating a new process should yield a new upid. + auto new_upid = context.process_tracker->GetOrCreateProcess(123); + ASSERT_NE(new_upid, upid); +} + +TEST_F(ProcessTrackerTest, EndProcessNonExistent) { + // Ending a process that was never tracked should not crash. + context.process_tracker->EndProcess(1000, 999); +} + +TEST_F(ProcessTrackerTest, EndProcessThenPidReuse) { + auto upid1 = context.process_tracker->StartNewProcessWithoutMainThread( + 1000, std::nullopt, 42, kNullStringId, + ThreadNamePriority::kGenericKernelTask); + context.process_tracker->EndProcess(2000, 42); + + auto upid2 = context.process_tracker->StartNewProcessWithoutMainThread( + 3000, std::nullopt, 42, kNullStringId, + ThreadNamePriority::kGenericKernelTask); + + ASSERT_NE(upid1, upid2); + ASSERT_EQ(context.storage->process_table()[upid1].end_ts(), 2000); + ASSERT_EQ(context.storage->process_table()[upid2].start_ts(), 3000); + ASSERT_FALSE(context.storage->process_table()[upid2].end_ts().has_value()); +} + +// --------------------------------------------------------------------------- +// Tests simulating mixed ftrace and Android framework track event interactions. +// These mirror the ProcessTracker calls made by the parsing overrides in +// track_event_parser.cc for AndroidProcessStartEvent and +// AndroidBinderDiedEvent. +// --------------------------------------------------------------------------- + +// Ftrace creates the process first (task_newtask). Then the Android framework +// start event arrives and finds the existing upid via UpidForPid. It should +// reuse the same upid and only fill in the uid. +TEST_F(ProcessTrackerTest, FtraceStartThenAndroidStart) { + auto ftrace_name = context.storage->InternString("com.app"); + auto upid = context.process_tracker->StartNewProcess( + /*timestamp=*/1000, std::nullopt, /*pid=*/100, ftrace_name, + ThreadNamePriority::kFtrace); + + // Simulate AndroidProcessStartEvent: UpidForPid finds the process. + auto existing = context.process_tracker->UpidForPid(100); + ASSERT_TRUE(existing.has_value()); + ASSERT_EQ(*existing, upid); + + // Set uid (Android framework provides this). + context.process_tracker->SetProcessUid(*existing, 10042); + + // Verify: same upid, ftrace name preserved, uid set. + ASSERT_EQ(context.storage->process_table()[upid].start_ts(), 1000); + ASSERT_EQ(context.storage->GetString( + *context.storage->process_table()[upid].name()), + "com.app"); + ASSERT_EQ(*context.storage->process_table()[upid].uid(), 10042u); + ASSERT_EQ(*context.storage->process_table()[upid].android_appid(), 10042u); + // Main thread should still exist from ftrace. + ASSERT_TRUE(context.process_tracker->GetThreadOrNull(100).has_value()); +} + +// Android framework start event arrives before ftrace task_newtask (e.g. due +// to timestamp ordering). Ftrace should take full precedence: StartNewProcess +// erases the old pid mapping and creates a fresh upid with a main thread. +TEST_F(ProcessTrackerTest, AndroidStartThenFtraceStart) { + auto android_name = context.storage->InternString("com.app.android"); + auto android_upid = context.process_tracker->StartNewProcessWithoutMainThread( + /*timestamp=*/900, std::nullopt, /*pid=*/100, android_name, + ThreadNamePriority::kOther); + context.process_tracker->SetProcessUid(android_upid, 10042); + + // Ftrace task_newtask arrives later with higher-priority name. + auto ftrace_name = context.storage->InternString("com.app.ftrace"); + auto ftrace_upid = context.process_tracker->StartNewProcess( + /*timestamp=*/1000, std::nullopt, /*pid=*/100, ftrace_name, + ThreadNamePriority::kFtrace); + + // Ftrace creates a new upid (StartNewProcess erases old pid mapping). + ASSERT_NE(android_upid, ftrace_upid); + // The current pid mapping points to the ftrace-created process. + auto current = context.process_tracker->UpidForPid(100); + ASSERT_TRUE(current.has_value()); + ASSERT_EQ(*current, ftrace_upid); + // Ftrace process has its own name and timestamp. + ASSERT_EQ(context.storage->GetString( + *context.storage->process_table()[ftrace_upid].name()), + "com.app.ftrace"); + ASSERT_EQ(context.storage->process_table()[ftrace_upid].start_ts(), 1000); + // Main thread exists for ftrace-created process. + ASSERT_TRUE(context.process_tracker->GetThreadOrNull(100).has_value()); +} + +// Ftrace creates the process, then Android BinderDied ends it. Verifies +// that end_ts is set and uid/name from the death event are populated. +TEST_F(ProcessTrackerTest, FtraceStartAndroidDeath) { + auto ftrace_name = context.storage->InternString("com.app"); + auto upid = context.process_tracker->StartNewProcess( + /*timestamp=*/1000, std::nullopt, /*pid=*/100, ftrace_name, + ThreadNamePriority::kFtrace); + + // Simulate AndroidBinderDiedEvent: populate uid/name, then end. + auto opt_upid = context.process_tracker->UpidForPid(100); + ASSERT_TRUE(opt_upid.has_value()); + context.process_tracker->SetProcessUid(*opt_upid, 10042); + // Name update at kOther priority should NOT override ftrace's kSystem name. + auto death_name = context.storage->InternString("com.app.death"); + context.process_tracker->UpdateProcessName(*opt_upid, death_name, + ProcessNamePriority::kOther); + context.process_tracker->EndProcess(2000, 100); + + // Ftrace name preserved (kSystem > kOther). + ASSERT_EQ(context.storage->GetString( + *context.storage->process_table()[upid].name()), + "com.app"); + // end_ts and uid are set. + ASSERT_EQ(context.storage->process_table()[upid].end_ts(), 2000); + ASSERT_EQ(*context.storage->process_table()[upid].uid(), 10042u); + // pid is freed. + ASSERT_FALSE(context.process_tracker->UpidForPid(100).has_value()); +} + +// Ftrace creates and ends the process (sched_process_free). Then Android +// BinderDied arrives for the same pid. The process is already gone from the +// pid map, so BinderDied should be a no-op: no crash, no new process created. +TEST_F(ProcessTrackerTest, FtraceFullLifecycleThenAndroidDeath) { + auto ftrace_name = context.storage->InternString("com.app"); + auto upid = context.process_tracker->StartNewProcess( + /*timestamp=*/1000, std::nullopt, /*pid=*/100, ftrace_name, + ThreadNamePriority::kFtrace); + // Ftrace sched_process_free: EndThread for the main thread ends the process. + context.process_tracker->EndThread(2000, 100); + + ASSERT_EQ(context.storage->process_table()[upid].end_ts(), 2000); + ASSERT_FALSE(context.process_tracker->UpidForPid(100).has_value()); + + // Android BinderDied arrives late. UpidForPid returns nullopt, so uid/name + // update is skipped. EndProcess is a no-op. + auto opt_upid = context.process_tracker->UpidForPid(100); + ASSERT_FALSE(opt_upid.has_value()); + context.process_tracker->EndProcess(2500, 100); + + // Original end_ts unchanged, no new processes created. + ASSERT_EQ(context.storage->process_table()[upid].end_ts(), 2000); + ASSERT_EQ(context.storage->process_table().row_count(), 2u); +} + +// Android-only lifecycle: no ftrace events at all. Start event creates the +// process, BinderDied ends it. Then PID is reused for a second process. +TEST_F(ProcessTrackerTest, AndroidOnlyLifecycleWithPidReuse) { + auto name1 = context.storage->InternString("com.first"); + auto upid1 = context.process_tracker->StartNewProcessWithoutMainThread( + /*timestamp=*/1000, std::nullopt, /*pid=*/200, name1, + ThreadNamePriority::kOther); + context.process_tracker->SetProcessUid(upid1, 10001); + + // BinderDied ends the first process. + context.process_tracker->EndProcess(2000, 200); + ASSERT_EQ(context.storage->process_table()[upid1].end_ts(), 2000); + + // PID 200 is reused for a second process. + auto name2 = context.storage->InternString("com.second"); + auto upid2 = context.process_tracker->StartNewProcessWithoutMainThread( + /*timestamp=*/3000, std::nullopt, /*pid=*/200, name2, + ThreadNamePriority::kOther); + context.process_tracker->SetProcessUid(upid2, 10002); + + // Two distinct upids for the same pid. + ASSERT_NE(upid1, upid2); + ASSERT_EQ(context.storage->GetString( + *context.storage->process_table()[upid1].name()), + "com.first"); + ASSERT_EQ(context.storage->GetString( + *context.storage->process_table()[upid2].name()), + "com.second"); + ASSERT_EQ(*context.storage->process_table()[upid1].uid(), 10001u); + ASSERT_EQ(*context.storage->process_table()[upid2].uid(), 10002u); + // First process ended, second still alive. + ASSERT_EQ(context.storage->process_table()[upid1].end_ts(), 2000); + ASSERT_FALSE(context.storage->process_table()[upid2].end_ts().has_value()); +} + +// Ftrace creates an initial process snapshot. The process dies via Android +// BinderDied. A new process with the same PID starts via Android start event. +// This is the primary use case: ftrace provides the initial snapshot, Android +// framework handles subsequent lifecycle. +TEST_F(ProcessTrackerTest, FtraceInitialSnapshotThenAndroidLifecycle) { + // Phase 1: ftrace captures the initial snapshot. + auto ftrace_name = context.storage->InternString("com.app"); + auto upid1 = context.process_tracker->StartNewProcess( + /*timestamp=*/1000, std::nullopt, /*pid=*/300, ftrace_name, + ThreadNamePriority::kFtrace); + + // Phase 2: Android BinderDied kills the process. + auto opt = context.process_tracker->UpidForPid(300); + ASSERT_TRUE(opt.has_value()); + context.process_tracker->SetProcessUid(*opt, 10050); + context.process_tracker->EndProcess(5000, 300); + + ASSERT_EQ(context.storage->process_table()[upid1].end_ts(), 5000); + ASSERT_EQ(*context.storage->process_table()[upid1].uid(), 10050u); + + // Phase 3: Android ProcessStartEvent starts a new process with same PID. + // UpidForPid returns nullopt (pid was freed), so a new process is created. + ASSERT_FALSE(context.process_tracker->UpidForPid(300).has_value()); + auto new_name = context.storage->InternString("com.app.v2"); + auto upid2 = context.process_tracker->StartNewProcessWithoutMainThread( + /*timestamp=*/6000, std::nullopt, /*pid=*/300, new_name, + ThreadNamePriority::kOther); + context.process_tracker->SetProcessUid(upid2, 10051); + + // Verify two distinct process entries. + ASSERT_NE(upid1, upid2); + ASSERT_EQ(context.storage->GetString( + *context.storage->process_table()[upid1].name()), + "com.app"); + ASSERT_EQ(context.storage->GetString( + *context.storage->process_table()[upid2].name()), + "com.app.v2"); + ASSERT_EQ(context.storage->process_table()[upid2].start_ts(), 6000); + ASSERT_FALSE(context.storage->process_table()[upid2].end_ts().has_value()); +} + +// Ftrace creates a process with a name. Android start event finds it and +// tries to set uid. Verify that the ftrace name (kSystem priority) is never +// overwritten by a lower-priority source. +TEST_F(ProcessTrackerTest, FtraceNameNotOverwrittenByAndroid) { + auto ftrace_name = context.storage->InternString("ftrace_name"); + auto upid = context.process_tracker->StartNewProcess( + /*timestamp=*/1000, std::nullopt, /*pid=*/400, ftrace_name, + ThreadNamePriority::kFtrace); + + // Android start event finds the process and tries to update the name + // at kOther priority (simulating what would happen if code were changed + // to always update name). + auto android_name = context.storage->InternString("android_name"); + context.process_tracker->UpdateProcessName(upid, android_name, + ProcessNamePriority::kOther); + + // Ftrace name survives (kSystem > kOther). + ASSERT_EQ(context.storage->GetString( + *context.storage->process_table()[upid].name()), + "ftrace_name"); + + // Even kTrackDescriptor can't override kSystem. + auto td_name = context.storage->InternString("track_descriptor_name"); + context.process_tracker->UpdateProcessName( + upid, td_name, ProcessNamePriority::kTrackDescriptor); + ASSERT_EQ(context.storage->GetString( + *context.storage->process_table()[upid].name()), + "ftrace_name"); + + // Only kSystem can override kSystem (same priority is allowed). + auto system_name = context.storage->InternString("system_name"); + context.process_tracker->UpdateProcessName(upid, system_name, + ProcessNamePriority::kSystem); + ASSERT_EQ(context.storage->GetString( + *context.storage->process_table()[upid].name()), + "system_name"); +} + +// Both ftrace EndThread and Android EndProcess fire for the same process. +// The second one should be a no-op. Verifies idempotent ending. +TEST_F(ProcessTrackerTest, DoubleEndFtraceThenAndroid) { + auto upid = context.process_tracker->StartNewProcess( + /*timestamp=*/1000, std::nullopt, /*pid=*/500, kNullStringId, + ThreadNamePriority::kFtrace); + + // Ftrace sched_process_free. + context.process_tracker->EndThread(2000, 500); + ASSERT_EQ(context.storage->process_table()[upid].end_ts(), 2000); + + // Android BinderDied arrives later. EndProcess is a no-op. + context.process_tracker->EndProcess(2500, 500); + + // end_ts unchanged from ftrace. + ASSERT_EQ(context.storage->process_table()[upid].end_ts(), 2000); +} + +// Android BinderDied arrives for a process that was never seen by any source. +// EndProcess should be a safe no-op. +TEST_F(ProcessTrackerTest, AndroidDeathForUnknownProcess) { + // UpidForPid returns nullopt. + ASSERT_FALSE(context.process_tracker->UpidForPid(999).has_value()); + + // EndProcess should not crash or create any entries. + auto row_count_before = context.storage->process_table().row_count(); + context.process_tracker->EndProcess(1000, 999); + ASSERT_EQ(context.storage->process_table().row_count(), row_count_before); +} + TEST_F(ProcessTrackerTest, NamespacedThreadMissingProcess) { // Try to update a namespaced thread without first registering the process. // This should fail and return false.
diff --git a/src/trace_processor/importers/proto/track_event_event_importer.h b/src/trace_processor/importers/proto/track_event_event_importer.h index 034d407..5b176e5 100644 --- a/src/trace_processor/importers/proto/track_event_event_importer.h +++ b/src/trace_processor/importers/proto/track_event_event_importer.h
@@ -1262,6 +1262,7 @@ parser_->AddActiveProcess(ts_, *it); } } + if (event_.has_correlation_id()) { base::StackString<512> id_str("tp:#%" PRIu64, event_.correlation_id()); inserter->AddArg(parser_->correlation_id_key_id_,
diff --git a/src/trace_processor/importers/proto/track_event_parser.cc b/src/trace_processor/importers/proto/track_event_parser.cc index 4780e0d..06e0e83 100644 --- a/src/trace_processor/importers/proto/track_event_parser.cc +++ b/src/trace_processor/importers/proto/track_event_parser.cc
@@ -58,6 +58,21 @@ namespace perfetto::trace_processor { +// Field numbers from AndroidProcessStartEvent in android_track_event.proto. +// pbzero isn't generated for extension-only protos; decode with raw field ids. +namespace android_process_start_event { +constexpr uint32_t kUidFieldNumber = 1; +constexpr uint32_t kPidFieldNumber = 2; +constexpr uint32_t kProcessNameFieldNumber = 3; +} // namespace android_process_start_event + +// Field numbers from AndroidBinderDiedEvent in android_track_event.proto. +namespace android_binder_died_event { +constexpr uint32_t kUidFieldNumber = 1; +constexpr uint32_t kPidFieldNumber = 2; +constexpr uint32_t kProcessNameFieldNumber = 3; +} // namespace android_binder_died_event + namespace { using BoundInserter = ArgsTracker::BoundInserter; using protos::pbzero::TrackEvent; @@ -274,6 +289,102 @@ return std::nullopt; }); + // Intercept Android framework process lifecycle events during proto arg + // parsing. This mirrors how ftrace handles task_newtask/sched_process_free: + // decode the proto inline and call process_tracker directly rather than + // scanning slices post-hoc. + args_parser_.AddParsingOverrideForType( + ".perfetto.protos.AndroidProcessStartEvent", + [this](util::ProtoToArgsParser::ScopedNestedKeyContext&, + const protozero::ConstBytes& data, + util::ProtoToArgsParser::Delegate& delegate) + -> std::optional<base::Status> { + protozero::ProtoDecoder evt(data); + auto pid_field = + evt.FindField(android_process_start_event::kPidFieldNumber); + if (!pid_field.valid()) + return std::nullopt; + + int64_t ts = delegate.packet_timestamp(); + int64_t pid = pid_field.as_int32(); + auto* proc_tracker = context_->process_tracker.get(); + // If ftrace already created this process, don't create a duplicate. + // Only fill in data that ftrace didn't provide. + auto existing_upid = proc_tracker->UpidForPid(pid); + UniquePid upid; + if (existing_upid) { + upid = *existing_upid; + } else { + StringId process_name = kNullStringId; + auto name_field = evt.FindField( + android_process_start_event::kProcessNameFieldNumber); + if (name_field.valid()) { + process_name = + context_->storage->InternString(name_field.as_string()); + } + upid = proc_tracker->StartNewProcessWithoutMainThread( + /*timestamp=*/ts, + /*parent_upid=*/std::nullopt, + /*pid=*/pid, + /*process_name=*/process_name, + /*priority=*/ThreadNamePriority::kOther); + } + + auto uid_field = + evt.FindField(android_process_start_event::kUidFieldNumber); + if (uid_field.valid()) { + proc_tracker->SetProcessUid( + upid, static_cast<uint32_t>(uid_field.as_int32())); + } + + // Fallthrough so the parser also writes args to the slice. + return std::nullopt; + }); + + args_parser_.AddParsingOverrideForType( + ".perfetto.protos.AndroidBinderDiedEvent", + [this](util::ProtoToArgsParser::ScopedNestedKeyContext&, + const protozero::ConstBytes& data, + util::ProtoToArgsParser::Delegate& delegate) + -> std::optional<base::Status> { + protozero::ProtoDecoder evt(data); + auto pid_field = + evt.FindField(android_binder_died_event::kPidFieldNumber); + if (!pid_field.valid()) + return std::nullopt; + + int64_t ts = delegate.packet_timestamp(); + int64_t pid = pid_field.as_int32(); + auto* proc_tracker = context_->process_tracker.get(); + + // Populate uid/name before ending, so data is set on the process + // row before it's marked as ended. + auto opt_upid = proc_tracker->UpidForPid(pid); + if (opt_upid) { + auto uid_field = + evt.FindField(android_binder_died_event::kUidFieldNumber); + if (uid_field.valid()) { + proc_tracker->SetProcessUid( + *opt_upid, static_cast<uint32_t>(uid_field.as_int32())); + } + + auto name_field = + evt.FindField(android_binder_died_event::kProcessNameFieldNumber); + if (name_field.valid()) { + proc_tracker->UpdateProcessName( + *opt_upid, + context_->storage->InternString(name_field.as_string()), + ProcessNamePriority::kOther); + } + } + + // End the process (handles both with-main-thread and without cases). + proc_tracker->EndProcess(ts, pid); + + // Fallthrough so the parser also writes args to the slice. + return std::nullopt; + }); + for (uint16_t index : kReflectFields) { reflect_fields_.push_back(index); }