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