Merge "Add ui/BUILD.bazel" into main
diff --git a/src/trace_redaction/collect_timeline_events_unittest.cc b/src/trace_redaction/collect_timeline_events_unittest.cc
index 8b00ce1..96282ba 100644
--- a/src/trace_redaction/collect_timeline_events_unittest.cc
+++ b/src/trace_redaction/collect_timeline_events_unittest.cc
@@ -15,8 +15,6 @@
  * limitations under the License.
  */
 
-#include <string>
-
 #include "src/base/test/status_matchers.h"
 #include "src/trace_redaction/collect_timeline_events.h"
 #include "src/trace_redaction/trace_redaction_framework.h"
@@ -29,35 +27,6 @@
 
 namespace perfetto::trace_redaction {
 
-// Test packet (a small clip of a later trace):
-//
-// packet {
-//  process_tree{
-//    processes {
-//      pid: 1093
-//      ppid: 1
-//      cmdline: "zygote"
-//      uid: 0
-//    }
-//    processes {
-//      pid: 7105
-//      ppid: 1093
-//      cmdline: "com.Unity.com.unity.multiplayer.samples.coop"
-//      uid: 10252
-//    }
-//    threads {
-//      tid: 7127
-//      tgid: 7105
-//    }
-//    collection_end_timestamp: 6702093738547594
-//  }
-//  trusted_uid: 9999
-//  timestamp: 6702093635419927
-//  trusted_packet_sequence_id: 6
-//  incremental_state_cleared: true
-//  previous_packet_dropped: true
-// }
-
 namespace {
 
 constexpr uint64_t kSystemPackage = 0;
@@ -67,180 +36,184 @@
 constexpr int32_t kUnityPid = 7105;
 constexpr int32_t kUnityTid = 7127;
 
+// TODO(vaage): Need a better name and documentation.
+constexpr int32_t kPidWithNoOpen = 32;
+
 constexpr uint64_t kProcessTreeTimestamp = 6702093635419927;
-constexpr uint64_t kThreadFreeTimestamp = 6702094703928940;
 
-class TestParams {
- public:
-  TestParams(uint64_t ts, int32_t pid, uint64_t uid)
-      : ts_(ts), pid_(pid), uid_(uid) {}
-
-  uint64_t ts() const { return ts_; }
-  int32_t pid() const { return pid_; }
-  uint64_t uid() const { return uid_; }
-
- private:
-  uint64_t ts_;
-  int32_t pid_;
-  uint64_t uid_;
-};
+// These two timestamps are used to separate the packet and event times. A
+// packet can have time X, but the time can have time Y. Time Y should be used
+// for the events.
+constexpr uint64_t kThreadFreePacketTimestamp = 6702094703928940;
+constexpr uint64_t kThreadFreeOffset = 100;
 
 }  // namespace
 
-class CollectTimelineEventsFixture {
+// Base class for all collect timeline event tests. Creates a simple trace that
+// contains trace elements that should create timeline events.
+class CollectTimelineEventsTest : public testing::Test {
  protected:
-  std::string CreateProcessTreePacket(uint64_t timestamp) {
-    protos::gen::TracePacket packet;
-    packet.set_trusted_uid(9999);
-    packet.set_timestamp(timestamp);
-    packet.set_trusted_packet_sequence_id(6);
-    packet.set_incremental_state_cleared(true);
-    packet.set_previous_packet_dropped(true);
+  void SetUp() {
+    CollectTimelineEvents collector;
 
-    auto* process_tree = packet.mutable_process_tree();
+    ASSERT_OK(collector.Begin(&context_));
 
-    auto* zygote = process_tree->add_processes();
-    zygote->set_pid(kZygotePid);
-    zygote->set_ppid(1);
-    zygote->add_cmdline("zygote");
-    zygote->set_uid(kSystemPackage);
+    // Minimum ProcessTree information.
+    {
+      auto timestamp = kProcessTreeTimestamp;
 
-    auto* unity = process_tree->add_processes();
-    unity->set_pid(kUnityPid);
-    unity->set_ppid(1093);
-    unity->add_cmdline("com.Unity.com.unity.multiplayer.samples.coop");
-    unity->set_uid(kUnityUid);
+      protos::gen::TracePacket packet;
+      packet.set_timestamp(timestamp);
 
-    auto* thread = process_tree->add_threads();
-    thread->set_tid(kUnityTid);
-    thread->set_tgid(kUnityPid);
+      auto* process_tree = packet.mutable_process_tree();
 
-    process_tree->set_collection_end_timestamp(timestamp);
+      auto* zygote = process_tree->add_processes();
+      zygote->set_pid(kZygotePid);
+      zygote->set_ppid(1);
+      zygote->set_uid(kSystemPackage);
 
-    return packet.SerializeAsString();
+      auto* unity = process_tree->add_processes();
+      unity->set_pid(kUnityPid);
+      unity->set_ppid(1093);
+      unity->set_uid(kUnityUid);
+
+      auto* thread = process_tree->add_threads();
+      thread->set_tid(kUnityTid);
+      thread->set_tgid(kUnityPid);
+
+      process_tree->set_collection_end_timestamp(timestamp);
+
+      auto buffer = packet.SerializeAsString();
+
+      protos::pbzero::TracePacket::Decoder decoder(buffer);
+      ASSERT_OK(collector.Collect(decoder, &context_));
+    }
+
+    // Minimum proc free informations.
+    {
+      auto timestamp = kThreadFreePacketTimestamp;
+
+      protos::gen::TracePacket packet;
+      packet.set_timestamp(timestamp);
+
+      auto* ftrace_event = packet.mutable_ftrace_events()->add_event();
+      ftrace_event->set_timestamp(timestamp + kThreadFreeOffset);
+      ftrace_event->set_pid(10);  // kernel thread - e.g. "rcuop/0"
+
+      auto* process_free = ftrace_event->mutable_sched_process_free();
+      process_free->set_pid(kUnityTid);
+
+      auto buffer = packet.SerializeAsString();
+
+      protos::pbzero::TracePacket::Decoder decoder(buffer);
+      ASSERT_OK(collector.Collect(decoder, &context_));
+    }
+
+    // Free a pid that neve started.
+    {
+      auto timestamp = kThreadFreePacketTimestamp;
+
+      protos::gen::TracePacket packet;
+      packet.set_timestamp(timestamp);
+
+      auto* ftrace_event = packet.mutable_ftrace_events()->add_event();
+      ftrace_event->set_timestamp(timestamp + kThreadFreeOffset);
+      ftrace_event->set_pid(10);  // kernel thread - e.g. "rcuop/0"
+
+      auto* process_free = ftrace_event->mutable_sched_process_free();
+      process_free->set_pid(kPidWithNoOpen);
+
+      auto buffer = packet.SerializeAsString();
+
+      protos::pbzero::TracePacket::Decoder decoder(buffer);
+      ASSERT_OK(collector.Collect(decoder, &context_));
+    }
+
+    ASSERT_OK(collector.End(&context_));
   }
 
-  std::string CreateSchedProcessFreePacket(uint64_t timestamp) {
-    protos::gen::TracePacket packet;
-
-    packet.set_trusted_uid(9999);
-    packet.set_timestamp(timestamp);
-    packet.set_trusted_packet_sequence_id(6);
-    packet.set_incremental_state_cleared(true);
-    packet.set_previous_packet_dropped(true);
-
-    auto* ftrace_events = packet.mutable_ftrace_events();
-    auto* ftrace_event = ftrace_events->add_event();
-    ftrace_event->set_timestamp(timestamp);
-    ftrace_event->set_pid(10);  // kernel thread - e.g. "rcuop/0"
-
-    auto* process_free = ftrace_event->mutable_sched_process_free();
-    process_free->set_comm("UnityMain");
-    process_free->set_pid(kUnityTid);
-    process_free->set_prio(120);
-
-    return packet.SerializeAsString();
-  }
-};
-
-class CollectTimelineEventsWithProcessTree
-    : public testing::Test,
-      public CollectTimelineEventsFixture,
-      public testing::WithParamInterface<TestParams> {
- protected:
   Context context_;
-  CollectTimelineEvents collector_;
 };
 
-TEST_P(CollectTimelineEventsWithProcessTree, FindsOpenSpans) {
-  auto params = GetParam();
+class CollectTimelineFindsOpenEventTest
+    : public CollectTimelineEventsTest,
+      public testing::WithParamInterface<int32_t> {};
 
-  auto packet_str = CreateProcessTreePacket(kProcessTreeTimestamp);
+TEST_P(CollectTimelineFindsOpenEventTest, NoOpenEventBeforeProcessTree) {
+  auto pid = GetParam();
 
-  protos::pbzero::TracePacket::Decoder packet(packet_str);
+  auto event =
+      context_.timeline->FindPreviousEvent(kProcessTreeTimestamp - 1, pid);
+  ASSERT_EQ(event.type, ProcessThreadTimeline::Event::Type::kInvalid);
+}
 
-  auto begin_status = collector_.Begin(&context_);
-  ASSERT_OK(begin_status) << begin_status.message();
+TEST_P(CollectTimelineFindsOpenEventTest, OpenEventOnProcessTree) {
+  auto pid = GetParam();
 
-  auto packet_status = collector_.Collect(packet, &context_);
-  ASSERT_OK(packet_status) << packet_status.message();
+  auto event = context_.timeline->FindPreviousEvent(kProcessTreeTimestamp, pid);
+  ASSERT_EQ(event.type, ProcessThreadTimeline::Event::Type::kOpen);
+  ASSERT_EQ(event.pid, pid);
+}
 
-  auto end_status = collector_.End(&context_);
-  ASSERT_OK(end_status) << end_status.message();
+TEST_P(CollectTimelineFindsOpenEventTest, OpenEventAfterProcessTree) {
+  auto pid = GetParam();
 
-  auto slice = context_.timeline->Search(params.ts(), params.pid());
-  ASSERT_EQ(slice.pid, params.pid());
-  ASSERT_EQ(slice.uid, params.uid());
+  auto event = context_.timeline->FindPreviousEvent(kProcessTreeTimestamp, pid);
+  ASSERT_EQ(event.type, ProcessThreadTimeline::Event::Type::kOpen);
+  ASSERT_EQ(event.pid, pid);
 }
 
 INSTANTIATE_TEST_SUITE_P(
-    AcrossWholeTimeline,
-    CollectTimelineEventsWithProcessTree,
-    testing::Values(
-        // System-level process/thread
-        TestParams(kProcessTreeTimestamp - 1,
-                   kZygotePid,
-                   ProcessThreadTimeline::Event::kUnknownUid),
-        TestParams(kProcessTreeTimestamp, kZygotePid, kSystemPackage),
-        TestParams(kProcessTreeTimestamp + 1, kZygotePid, kSystemPackage),
+    SystemProcess,
+    CollectTimelineFindsOpenEventTest,
+    testing::Values(kZygotePid,  // System-level process/thread
+                    kUnityPid,   // Process
+                    kUnityTid    // Child thread. kUnityPid is the parent.
+                    ));
 
-        // Process
-        TestParams(kProcessTreeTimestamp - 1,
-                   kUnityPid,
-                   ProcessThreadTimeline::Event::kUnknownUid),
-        TestParams(kProcessTreeTimestamp, kUnityPid, kUnityUid),
-        TestParams(kProcessTreeTimestamp + 1, kUnityPid, kUnityUid),
+class CollectTimelineFindsFreeEventTest : public CollectTimelineEventsTest {};
 
-        // Child thread. kUnityPid is the parent.
-        TestParams(kProcessTreeTimestamp - 1,
-                   kUnityTid,
-                   ProcessThreadTimeline::Event::kUnknownUid),
-        TestParams(kProcessTreeTimestamp, kUnityTid, kUnityUid),
-        TestParams(kProcessTreeTimestamp + 1, kUnityTid, kUnityUid)));
+TEST_F(CollectTimelineFindsFreeEventTest, UsesFtraceEventTime) {
+  auto pid = kUnityTid;
 
-class CollectTimelineEventsWithFreeProcess
-    : public testing::Test,
-      public CollectTimelineEventsFixture {
- protected:
-  void SetUp() {
-    std::array<std::string, 2> buffers = {
-        CreateProcessTreePacket(kProcessTreeTimestamp),
-        CreateSchedProcessFreePacket(kThreadFreeTimestamp)};
+  // While this will be a valid event (type != invalid), it won't be the close
+  // event.
+  auto incorrect =
+      context_.timeline->FindPreviousEvent(kThreadFreePacketTimestamp, pid);
+  ASSERT_EQ(incorrect.type, ProcessThreadTimeline::Event::Type::kOpen);
 
-    std::array<protos::pbzero::TracePacket::Decoder, 2> decoders = {
-        protos::pbzero::TracePacket::Decoder(buffers[0]),
-        protos::pbzero::TracePacket::Decoder(buffers[1]),
-    };
-
-    ASSERT_OK(collector_.Begin(&context_));
-    ASSERT_OK(collector_.Collect(decoders[0], &context_));
-    ASSERT_OK(collector_.Collect(decoders[1], &context_));
-    ASSERT_OK(collector_.End(&context_));
-  }
-
-  Context context_;
-  CollectTimelineEvents collector_;
-};
-
-TEST_F(CollectTimelineEventsWithFreeProcess, FindsPackageBeforeFree) {
-  auto slice = context_.timeline->Search(kThreadFreeTimestamp - 1, kUnityTid);
-
-  ASSERT_EQ(slice.pid, kUnityTid);
-  ASSERT_EQ(slice.uid, kUnityUid);
+  auto correct = context_.timeline->FindPreviousEvent(
+      kThreadFreePacketTimestamp + kThreadFreeOffset, pid);
+  ASSERT_EQ(correct.type, ProcessThreadTimeline::Event::Type::kClose);
 }
 
-TEST_F(CollectTimelineEventsWithFreeProcess, NoPackageAtFree) {
-  auto slice = context_.timeline->Search(kThreadFreeTimestamp, kUnityTid);
+TEST_F(CollectTimelineFindsFreeEventTest, NoCloseEventBeforeFree) {
+  auto pid = kUnityTid;
 
-  ASSERT_EQ(slice.pid, kUnityTid);
-  ASSERT_EQ(slice.uid, ProcessThreadTimeline::Event::kUnknownUid);
+  auto event =
+      context_.timeline->FindPreviousEvent(kThreadFreePacketTimestamp - 1, pid);
+  ASSERT_EQ(event.type, ProcessThreadTimeline::Event::Type::kOpen);
+  ASSERT_EQ(event.pid, pid);
 }
 
-TEST_F(CollectTimelineEventsWithFreeProcess, NoPackageAfterFree) {
-  auto slice = context_.timeline->Search(kThreadFreeTimestamp + 1, kUnityTid);
+// Whether or not AddsCloseOnFree and AddsCloseAfterFree are the same close
+// event is an implementation detail.
+TEST_F(CollectTimelineFindsFreeEventTest, AddsCloseOnFree) {
+  auto pid = kUnityTid;
 
-  ASSERT_EQ(slice.pid, kUnityTid);
-  ASSERT_EQ(slice.uid, ProcessThreadTimeline::Event::kUnknownUid);
+  auto event = context_.timeline->FindPreviousEvent(
+      kThreadFreePacketTimestamp + kThreadFreeOffset, pid);
+  ASSERT_EQ(event.type, ProcessThreadTimeline::Event::Type::kClose);
+  ASSERT_EQ(event.pid, pid);
+}
+
+TEST_F(CollectTimelineFindsFreeEventTest, AddsCloseAfterFree) {
+  auto pid = kUnityTid;
+
+  auto event = context_.timeline->FindPreviousEvent(
+      kThreadFreePacketTimestamp + kThreadFreeOffset + 1, pid);
+  ASSERT_EQ(event.type, ProcessThreadTimeline::Event::Type::kClose);
+  ASSERT_EQ(event.pid, pid);
 }
 
 }  // namespace perfetto::trace_redaction
diff --git a/src/trace_redaction/process_thread_timeline.cc b/src/trace_redaction/process_thread_timeline.cc
index e27d46e..c1c3984 100644
--- a/src/trace_redaction/process_thread_timeline.cc
+++ b/src/trace_redaction/process_thread_timeline.cc
@@ -83,12 +83,9 @@
 
   Event fake = Event::Close(ts, pid);
 
-  // Events are in ts-order within each pid-group. See Optimize(), Because each
-  // group is small (the vast majority will have two events [start + event, no
-  // reuse]).
-  //
-  // Find the first process event. Then perform a linear search. There won't be
-  // many events per process.
+  // Events are sorted by pid, creating islands of data. This search is to put
+  // the cursor at the start of pid's island. Each island will be small (a
+  // couple of items), so searching within the islands should be cheap.
   auto at = std::lower_bound(events_.begin(), events_.end(), fake, OrderByPid);
 
   // `pid` was not found in `events_`.
@@ -110,10 +107,13 @@
   // 3. The performance gains are minimal or non-existant because of the small
   //    number of events.
   for (; at != events_.end() && at->pid == pid; ++at) {
+    // This event is after "now" and can safely be ignored.
     if (at->ts > ts) {
-      continue;  // Ignore events in the future.
+      continue;
     }
 
+    // `at` is know to be before now. So it is always safe to accept an event.
+    //
     // All ts values are positive. However, ts_at and ts_best are both less than
     // ts (see early condition), meaning they can be considered negative values.
     //
@@ -126,14 +126,20 @@
     //     -62         -29             0
     //
     // This means that the latest ts value under ts is the closest to ts.
-    if (!best.valid() || at->ts > best.ts) {
+
+    if (best.type == Event::Type::kInvalid || at->ts > best.ts) {
+      best = *at;
+    }
+
+    // This handles the rare edge case where an open and close event occur at
+    // the same time. The close event must get priority. This is done by
+    // allowing close events to use ">=" where as other events can only use ">".
+    if (at->type == Event::Type::kClose && at->ts == best.ts) {
       best = *at;
     }
   }
 
-  Event invalid = {};
-  return best.type == ProcessThreadTimeline::Event::Type::kOpen ? best
-                                                                : invalid;
+  return best;
 }
 
 }  // namespace perfetto::trace_redaction
diff --git a/src/trace_redaction/process_thread_timeline.h b/src/trace_redaction/process_thread_timeline.h
index 62aac05..b176121 100644
--- a/src/trace_redaction/process_thread_timeline.h
+++ b/src/trace_redaction/process_thread_timeline.h
@@ -112,6 +112,9 @@
   // `Sort()` must be called before this.
   Slice Search(uint64_t ts, int32_t pid) const;
 
+  // Finds the pid's last event before ts.
+  Event FindPreviousEvent(uint64_t ts, int32_t pid) const;
+
  private:
   enum class Mode {
     // The timeline can safely be queried. If the timeline is in read mode, and
@@ -124,11 +127,6 @@
     kWrite
   };
 
-  // Effectively this is the same as:
-  //
-  //  events_for(pid).before(ts).sort_by_time().last()
-  Event FindPreviousEvent(uint64_t ts, int32_t pid) const;
-
   std::vector<Event> events_;
 
   Mode mode_ = Mode::kRead;
diff --git a/src/trace_redaction/process_thread_timeline_unittest.cc b/src/trace_redaction/process_thread_timeline_unittest.cc
index 16c0174..23a9727 100644
--- a/src/trace_redaction/process_thread_timeline_unittest.cc
+++ b/src/trace_redaction/process_thread_timeline_unittest.cc
@@ -45,175 +45,182 @@
 constexpr uint64_t kTimeE = 40;
 constexpr uint64_t kTimeF = 50;
 constexpr uint64_t kTimeG = 60;
+constexpr uint64_t kTimeH = 70;
 
 constexpr int32_t kPidA = 1;
 constexpr int32_t kPidB = 2;
+constexpr int32_t kPidC = 3;
+constexpr int32_t kPidD = 4;
 
-constexpr uint64_t kUidA = 98;
-constexpr uint64_t kUidB = 99;
+constexpr uint64_t kUidA = 97;
+constexpr uint64_t kUidC = 99;
 
 }  // namespace
 
-// |--- PID A --- >
-class TimelineEventsOpenTest : public testing::Test {
+// B        C        D   E   F        G        H
+// *        *        *   *   *        *        *
+// |----- PID B -----|   .   |----- PID B -----|
+//          |--------- PID C ---------|
+//          | <- PID D (no duration)
+class ProcessThreadTimelineTest : public testing::Test {
  protected:
   void SetUp() {
-    timeline_.Append(
-        ProcessThreadTimeline::Event::Open(kTimeB, kPidB, kPidA, kUidA));
+    for (auto e : pid_b_events_) {
+      timeline_.Append(e);
+    }
+
+    for (auto e : pid_c_events_) {
+      timeline_.Append(e);
+    }
+
+    for (auto e : pid_d_events_) {
+      timeline_.Append(e);
+    }
+
     timeline_.Sort();
   }
 
+  ProcessThreadTimeline::Event invalid_ = {};
+
+  std::array<ProcessThreadTimeline::Event, 4> pid_b_events_ = {
+      ProcessThreadTimeline::Event::Open(kTimeB, kPidB, kPidA, kUidA),
+      ProcessThreadTimeline::Event::Close(kTimeD, kPidB),
+      ProcessThreadTimeline::Event::Open(kTimeF, kPidB, kPidA, kUidA),
+      ProcessThreadTimeline::Event::Close(kTimeH, kPidB),
+  };
+
+  std::array<ProcessThreadTimeline::Event, 2> pid_c_events_ = {
+      ProcessThreadTimeline::Event::Open(kTimeC, kPidC, kPidA, kUidA),
+      ProcessThreadTimeline::Event::Close(kTimeG, kPidC),
+  };
+
+  // A process with no duration.
+  std::array<ProcessThreadTimeline::Event, 2> pid_d_events_{
+      ProcessThreadTimeline::Event::Open(kTimeC, kPidD, kPidA, kUidA),
+      ProcessThreadTimeline::Event::Close(kTimeC, kPidD),
+  };
+
   ProcessThreadTimeline timeline_;
 };
 
-TEST_F(TimelineEventsOpenTest, ReturnsNothingBeforeStart) {
-  auto slice = timeline_.Search(kTimeA, kPidB);
-  ASSERT_EQ(slice.pid, kPidB);
-  ASSERT_EQ(slice.uid, ProcessThreadTimeline::Event::kUnknownUid);
+TEST_F(ProcessThreadTimelineTest, NoEventBeforeFirstSpan) {
+  auto event = timeline_.FindPreviousEvent(kTimeA, kPidB);
+  ASSERT_EQ(event, invalid_);
 }
 
-TEST_F(TimelineEventsOpenTest, ReturnsSomethingAtStart) {
-  auto slice = timeline_.Search(kTimeB, kPidB);
-  ASSERT_EQ(slice.pid, kPidB);
-  ASSERT_EQ(slice.uid, kUidA);
+TEST_F(ProcessThreadTimelineTest, OpenEventAtStartOfFirstSpan) {
+  auto event = timeline_.FindPreviousEvent(kTimeB, kPidB);
+  ASSERT_EQ(event, pid_b_events_[0]);
 }
 
-TEST_F(TimelineEventsOpenTest, ReturnsSomethingAfterStart) {
-  auto slice = timeline_.Search(kTimeC, kPidB);
-  ASSERT_EQ(slice.pid, kPidB);
-  ASSERT_EQ(slice.uid, kUidA);
+TEST_F(ProcessThreadTimelineTest, OpenEventWithinFirstSpan) {
+  auto event = timeline_.FindPreviousEvent(kTimeC, kPidB);
+  ASSERT_EQ(event, pid_b_events_[0]);
 }
 
-// |--- PID A --- |
-class TimelineEventsCloseTest : public testing::Test {
- protected:
-  void SetUp() {
-    // An open event must exist in order for a close event to exist.
-    timeline_.Append(
-        ProcessThreadTimeline::Event::Open(kTimeB, kPidB, kPidA, kUidA));
-    timeline_.Append(ProcessThreadTimeline::Event::Close(kTimeD, kPidB));
-    timeline_.Sort();
-  }
-
-  ProcessThreadTimeline timeline_;
-};
-
-TEST_F(TimelineEventsCloseTest, ReturnsSomethingBeforeClose) {
-  auto slice = timeline_.Search(kTimeC, kPidB);
-  ASSERT_EQ(slice.pid, kPidB);
-  ASSERT_EQ(slice.uid, kUidA);
+TEST_F(ProcessThreadTimelineTest, CloseEventAtEndOfFirstSpan) {
+  auto event = timeline_.FindPreviousEvent(kTimeD, kPidB);
+  ASSERT_EQ(event, pid_b_events_[1]);
 }
 
-TEST_F(TimelineEventsCloseTest, ReturnsNothingAtClose) {
-  auto slice = timeline_.Search(kTimeD, kPidB);
-  ASSERT_EQ(slice.pid, kPidB);
-  ASSERT_EQ(slice.uid, ProcessThreadTimeline::Event::kUnknownUid);
+TEST_F(ProcessThreadTimelineTest, CloseEventBetweenSpans) {
+  auto event = timeline_.FindPreviousEvent(kTimeE, kPidB);
+  ASSERT_EQ(event, pid_b_events_[1]);
 }
 
-TEST_F(TimelineEventsCloseTest, ReturnsNothingAfterClose) {
-  auto slice = timeline_.Search(kTimeE, kPidB);
-  ASSERT_EQ(slice.pid, kPidB);
-  ASSERT_EQ(slice.uid, ProcessThreadTimeline::Event::kUnknownUid);
+TEST_F(ProcessThreadTimelineTest, OpenEventAtStartOfSecondSpan) {
+  auto event = timeline_.FindPreviousEvent(kTimeF, kPidB);
+  ASSERT_EQ(event, pid_b_events_[2]);
 }
 
-// Two start events can occur (normally with process trees). The timeline is
-// expected to treat this case as if there was a close event between the two
-// open events.
+TEST_F(ProcessThreadTimelineTest, OpenEventWithinSecondSpan) {
+  auto event = timeline_.FindPreviousEvent(kTimeG, kPidB);
+  ASSERT_EQ(event, pid_b_events_[2]);
+}
+
+TEST_F(ProcessThreadTimelineTest, CloseEventAtEndOfSecondSpan) {
+  auto event = timeline_.FindPreviousEvent(kTimeH, kPidB);
+  ASSERT_EQ(event, pid_b_events_[3]);
+}
+
+// Pid B is active. But Pid C is not active. At this point, Pid C should report
+// as invalid event though another pid is active.
+TEST_F(ProcessThreadTimelineTest, InvalidEventWhenAnotherSpanIsActive) {
+  ASSERT_EQ(timeline_.FindPreviousEvent(kTimeB, kPidB), pid_b_events_[0]);
+  ASSERT_EQ(timeline_.FindPreviousEvent(kTimeB, kPidC), invalid_);
+}
+
+// When both pids are active, they should both report as active (using their
+// open events).
+TEST_F(ProcessThreadTimelineTest, ConcurrentSpansBothReportAsActive) {
+  ASSERT_EQ(timeline_.FindPreviousEvent(kTimeC, kPidB), pid_b_events_[0]);
+  ASSERT_EQ(timeline_.FindPreviousEvent(kTimeC, kPidC), pid_c_events_[0]);
+}
+
+// There are three test cases here:
 //
-// |--- PID A --- >
-//                 |--- PID A --- >
-class TimelineEventsOpenAfterOpenTest : public testing::Test {
+// 1. Before open/close
+// 2. At open/close
+// 3. After open/close
+//
+// Normally these would be tree different test cases, but the naming gets
+// complicated, so it is easier to do it in one case.
+TEST_F(ProcessThreadTimelineTest, ZeroDuration) {
+  ASSERT_EQ(timeline_.FindPreviousEvent(kTimeB, kPidD), invalid_);
+  ASSERT_EQ(timeline_.FindPreviousEvent(kTimeC, kPidD), pid_d_events_[1]);
+  ASSERT_EQ(timeline_.FindPreviousEvent(kTimeD, kPidD), pid_d_events_[1]);
+}
+
+// |----- UID A -----| |----- UID C -----|
+//  |---- PID A ----|   |---- PID C ----|
+//    |-- PID B --|
+//
+// NOTE: The notation above does not represent time, it represent relationship.
+// For example, PID B is a child of PID A.
+class ProcessThreadTimelineIsConnectedTest : public testing::Test {
  protected:
   void SetUp() {
-    timeline_.Append(
-        ProcessThreadTimeline::Event::Open(kTimeB, kPidB, kPidA, kUidA));
-    timeline_.Append(
-        ProcessThreadTimeline::Event::Open(kTimeD, kPidB, kPidA, kUidB));
+    timeline_.Append(ProcessThreadTimeline::Event::Open(
+        kTimeB, kPidA, ProcessThreadTimeline::Event::kUnknownPid, kUidA));
+    timeline_.Append(ProcessThreadTimeline::Event::Open(kTimeB, kPidB, kPidA));
+    timeline_.Append(ProcessThreadTimeline::Event::Open(
+        kTimeB, kPidC, ProcessThreadTimeline::Event::kUnknownPid, kUidC));
     timeline_.Sort();
   }
 
   ProcessThreadTimeline timeline_;
 };
 
-TEST_F(TimelineEventsOpenAfterOpenTest, ReturnsFirstBeforeSwitch) {
-  auto slice = timeline_.Search(kTimeC, kPidB);
-  ASSERT_EQ(slice.pid, kPidB);
-  ASSERT_EQ(slice.uid, kUidA);
-}
+// PID A is directly connected to UID A.
+TEST_F(ProcessThreadTimelineIsConnectedTest, DirectPidAndUid) {
+  auto slice = timeline_.Search(kTimeB, kPidA);
 
-TEST_F(TimelineEventsOpenAfterOpenTest, ReturnsSecondAtSwitch) {
-  auto slice = timeline_.Search(kTimeD, kPidB);
-  ASSERT_EQ(slice.pid, kPidB);
-  ASSERT_EQ(slice.uid, kUidB);
-}
-
-TEST_F(TimelineEventsOpenAfterOpenTest, ReturnsSecondAfterSwitch) {
-  auto slice = timeline_.Search(kTimeE, kPidB);
-  ASSERT_EQ(slice.pid, kPidB);
-  ASSERT_EQ(slice.uid, kUidB);
-}
-
-// |----- PID_A -----|
-//          |----- PID_B -----|
-class TimelineEventsOverlappingRangesTest : public testing::Test {
- protected:
-  void SetUp() {
-    timeline_.Append(
-        ProcessThreadTimeline::Event::Open(kTimeA, kPidA, 0, kUidA));
-    timeline_.Append(
-        ProcessThreadTimeline::Event::Open(kTimeC, kPidB, 0, kUidB));
-    timeline_.Append(ProcessThreadTimeline::Event::Close(kTimeE, kPidA));
-    timeline_.Append(ProcessThreadTimeline::Event::Close(kTimeG, kPidB));
-    timeline_.Sort();
-  }
-
-  ProcessThreadTimeline timeline_;
-};
-
-TEST_F(TimelineEventsOverlappingRangesTest, FindProcessADuringOverlap) {
-  auto slice = timeline_.Search(kTimeD, kPidA);
   ASSERT_EQ(slice.pid, kPidA);
   ASSERT_EQ(slice.uid, kUidA);
 }
 
-TEST_F(TimelineEventsOverlappingRangesTest, FindProcessBDuringOverlap) {
-  auto slice = timeline_.Search(kTimeD, kPidB);
-  ASSERT_EQ(slice.pid, kPidB);
-  ASSERT_EQ(slice.uid, kUidB);
-}
-
-// |------------- PID_A ------------->
-//         |----- PID_B -----|
-class TimelineEventsParentChildTest : public testing::Test {
- protected:
-  void SetUp() {
-    // PID A's parent (0) does not exist on the timeline. In production, this is
-    // what happens as the root process (0) doesn't exist.
-    timeline_.Append(
-        ProcessThreadTimeline::Event::Open(kTimeA, kPidA, 0, kUidA));
-    timeline_.Append(ProcessThreadTimeline::Event::Open(kTimeC, kPidB, kPidA));
-    timeline_.Append(ProcessThreadTimeline::Event::Close(kTimeE, kPidB));
-    timeline_.Sort();
-  }
-
-  ProcessThreadTimeline timeline_;
-};
-
-TEST_F(TimelineEventsParentChildTest, InvalidBeforeBStarts) {
+// PID B is indirectly connected to UID A through PID A.
+TEST_F(ProcessThreadTimelineIsConnectedTest, IndirectPidAndUid) {
   auto slice = timeline_.Search(kTimeB, kPidB);
-  ASSERT_EQ(slice.pid, kPidB);
-  ASSERT_EQ(slice.uid, ProcessThreadTimeline::Event::kUnknownUid);
-}
 
-TEST_F(TimelineEventsParentChildTest, ValidAfterBStarts) {
-  auto slice = timeline_.Search(kTimeD, kPidB);
   ASSERT_EQ(slice.pid, kPidB);
   ASSERT_EQ(slice.uid, kUidA);
 }
 
-TEST_F(TimelineEventsParentChildTest, InvalidAfterBEnds) {
-  auto slice = timeline_.Search(kTimeF, kPidB);
-  ASSERT_EQ(slice.pid, kPidB);
+// PID D is not in the timeline, so it shouldn't be connected to anything.
+TEST_F(ProcessThreadTimelineIsConnectedTest, MissingPid) {
+  auto slice = timeline_.Search(kTimeB, kPidD);
+
+  ASSERT_EQ(slice.pid, kPidD);
+  ASSERT_EQ(slice.uid, ProcessThreadTimeline::Event::kUnknownUid);
+}
+
+// Even through there is a connection between PID A and UID A, the query is too
+// soon (events are at TIME B, but the query is at TIME A).
+TEST_F(ProcessThreadTimelineIsConnectedTest, PrematureDirectPidAndUid) {
+  auto slice = timeline_.Search(kTimeA, kPidA);
+
+  ASSERT_EQ(slice.pid, kPidA);
   ASSERT_EQ(slice.uid, ProcessThreadTimeline::Event::kUnknownUid);
 }
 
diff --git a/tools/gen_tp_table_headers.py b/tools/gen_tp_table_headers.py
index fd44c19..6d7e044 100755
--- a/tools/gen_tp_table_headers.py
+++ b/tools/gen_tp_table_headers.py
@@ -67,10 +67,10 @@
     return os.path.join(args.import_prefix, get_relout_path(in_path))
 
   def get_relin_path_from_module_path(module_path: str):
-    return module_path[module_path.rfind('/src') + 1:]
+    return module_path[module_path.rfind(os.sep + 'src') + 1:]
 
   modules = [
-      os.path.splitext(get_relin_path(i).replace(os.sep, '.'))[0]
+      os.path.splitext(get_relin_path(i).replace('/', '.'))[0]
       for i in args.inputs
   ]
   headers: Dict[str, Header] = {}
diff --git a/ui/src/common/plugins.ts b/ui/src/common/plugins.ts
index 0750e2e..0f2b2b7 100644
--- a/ui/src/common/plugins.ts
+++ b/ui/src/common/plugins.ts
@@ -394,18 +394,24 @@
 
   // Must only be called once on startup
   async initialize(): Promise<void> {
-    for (const plugin of pluginRegistry.values()) {
-      const id = `plugin_${plugin.pluginId}`;
-      const name = `Plugin: ${plugin.pluginId}`;
+    // Shuffle the order of plugins to weed out any implicit inter-plugin
+    // dependencies.
+    const pluginsShuffled = Array.from(pluginRegistry.values())
+      .map(({pluginId}) => ({pluginId, sort: Math.random()}))
+      .sort((a, b) => a.sort - b.sort);
+
+    for (const {pluginId} of pluginsShuffled) {
+      const flagId = `plugin_${pluginId}`;
+      const name = `Plugin: ${pluginId}`;
       const flag = featureFlags.register({
-        id,
+        id: flagId,
         name,
-        description: `Overrides '${id}' plugin.`,
-        defaultValue: defaultPlugins.includes(plugin.pluginId),
+        description: `Overrides '${pluginId}' plugin.`,
+        defaultValue: defaultPlugins.includes(pluginId),
       });
-      this.flags.set(plugin.pluginId, flag);
+      this.flags.set(pluginId, flag);
       if (flag.get()) {
-        await this.activatePlugin(plugin.pluginId);
+        await this.activatePlugin(pluginId);
       }
     }
   }
@@ -526,15 +532,21 @@
     beforeEach?: (id: string) => void,
   ): Promise<void> {
     this.engine = engine;
-    const plugins = Array.from(this._plugins.entries());
+
+    // Shuffle the order of plugins to weed out any implicit inter-plugin
+    // dependencies.
+    const pluginsShuffled = Array.from(this._plugins.entries())
+      .map(([id, plugin]) => ({id, plugin, sort: Math.random()}))
+      .sort((a, b) => a.sort - b.sort);
+
     // Awaiting all plugins in parallel will skew timing data as later plugins
     // will spend most of their time waiting for earlier plugins to load.
     // Running in parallel will have very little performance benefit assuming
     // most plugins use the same engine, which can only process one query at a
     // time.
-    for (const [id, pluginDetails] of plugins) {
+    for (const {id, plugin} of pluginsShuffled) {
       beforeEach?.(id);
-      await doPluginTraceLoad(pluginDetails, engine, id);
+      await doPluginTraceLoad(plugin, engine, id);
     }
   }
 
diff --git a/ui/src/core_plugins/process_summary/index.ts b/ui/src/core_plugins/process_summary/index.ts
index ea6845f..f2a7475 100644
--- a/ui/src/core_plugins/process_summary/index.ts
+++ b/ui/src/core_plugins/process_summary/index.ts
@@ -35,6 +35,8 @@
 
   private async addProcessTrackGroups(ctx: PluginContextTrace): Promise<void> {
     const result = await ctx.engine.query(`
+      INCLUDE PERFETTO MODULE android.process_metadata;
+
       select *
       from (
         select