1
2 /*
3 * Copyright (C) 2024 The Android Open Source Project
4 *
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17
18 #include "src/trace_redaction/collect_frame_cookies.h"
19 #include "src/base/test/status_matchers.h"
20 #include "src/trace_redaction/collect_timeline_events.h"
21 #include "test/gtest_and_gmock.h"
22
23 #include "protos/perfetto/trace/android/frame_timeline_event.gen.h"
24 #include "protos/perfetto/trace/android/frame_timeline_event.pbzero.h"
25 #include "protos/perfetto/trace/trace_packet.gen.h"
26 #include "protos/perfetto/trace/trace_packet.pbzero.h"
27
28 namespace perfetto::trace_redaction {
29 namespace {
30
31 constexpr uint64_t kTimeStep = 1000;
32
33 constexpr uint64_t kTimestampA = 0;
34 constexpr uint64_t kTimestampB = kTimeStep;
35 constexpr uint64_t kTimestampC = kTimeStep * 2;
36 constexpr uint64_t kTimestampD = kTimeStep * 3;
37 constexpr uint64_t kTimestampE = kTimeStep * 4;
38
39 constexpr int64_t kCookieA = 1234;
40
41 // Start at 1, amd not zero, because zero hnas special meaning (system uid).
42 constexpr uint64_t kUidA = 1;
43
44 constexpr int32_t kPidNone = 10;
45 constexpr int32_t kPidA = 11;
46
47 } // namespace
48
49 class FrameCookieFixture {
50 protected:
CreateStartEvent(int32_t field_id,uint64_t ts,int32_t pid,int64_t cookie) const51 std::string CreateStartEvent(int32_t field_id,
52 uint64_t ts,
53 int32_t pid,
54 int64_t cookie) const {
55 protos::gen::TracePacket packet;
56 packet.set_timestamp(ts);
57
58 switch (field_id) {
59 case protos::pbzero::FrameTimelineEvent::
60 kExpectedSurfaceFrameStartFieldNumber:
61 CreateExpectedSurfaceFrameStart(pid, cookie,
62 packet.mutable_frame_timeline_event());
63 break;
64
65 case protos::pbzero::FrameTimelineEvent::
66 kActualSurfaceFrameStartFieldNumber:
67 CreateActualSurfaceFrameStart(pid, cookie,
68 packet.mutable_frame_timeline_event());
69 break;
70
71 case protos::pbzero::FrameTimelineEvent::
72 kExpectedDisplayFrameStartFieldNumber:
73 CreateExpectedDisplayFrameStart(pid, cookie,
74 packet.mutable_frame_timeline_event());
75 break;
76
77 case protos::pbzero::FrameTimelineEvent::
78 kActualDisplayFrameStartFieldNumber:
79 CreateActualDisplayFrameStart(pid, cookie,
80 packet.mutable_frame_timeline_event());
81 break;
82
83 default:
84 PERFETTO_FATAL("Invalid field id");
85 break;
86 }
87
88 return packet.SerializeAsString();
89 }
90
CreateFrameEnd(uint64_t ts,int64_t cookie) const91 std::string CreateFrameEnd(uint64_t ts, int64_t cookie) const {
92 protos::gen::TracePacket packet;
93 packet.set_timestamp(ts);
94
95 auto* start = packet.mutable_frame_timeline_event()->mutable_frame_end();
96 start->set_cookie(cookie);
97
98 return packet.SerializeAsString();
99 }
100
CollectEvents(std::initializer_list<ProcessThreadTimeline::Event> events,Context * context) const101 void CollectEvents(std::initializer_list<ProcessThreadTimeline::Event> events,
102 Context* context) const {
103 CollectTimelineEvents collect;
104 ASSERT_OK(collect.Begin(context));
105
106 for (const auto& event : events) {
107 context->timeline->Append(event);
108 }
109
110 ASSERT_OK(collect.End(context));
111 }
112
CollectCookies(std::initializer_list<std::string> packets,Context * context) const113 void CollectCookies(std::initializer_list<std::string> packets,
114 Context* context) const {
115 CollectFrameCookies collect;
116 ASSERT_OK(collect.Begin(context));
117
118 for (const auto& packet : packets) {
119 protos::pbzero::TracePacket::Decoder decoder(packet);
120 ASSERT_OK(collect.Collect(decoder, context));
121 }
122
123 ASSERT_OK(collect.End(context));
124 }
125
126 private:
CreateExpectedSurfaceFrameStart(int32_t pid,int64_t cookie,protos::gen::FrameTimelineEvent * event) const127 void CreateExpectedSurfaceFrameStart(
128 int32_t pid,
129 int64_t cookie,
130 protos::gen::FrameTimelineEvent* event) const {
131 auto* start = event->mutable_expected_surface_frame_start();
132 start->set_cookie(cookie);
133 start->set_pid(pid);
134 }
135
CreateActualSurfaceFrameStart(int32_t pid,int64_t cookie,protos::gen::FrameTimelineEvent * event) const136 void CreateActualSurfaceFrameStart(
137 int32_t pid,
138 int64_t cookie,
139 protos::gen::FrameTimelineEvent* event) const {
140 auto* start = event->mutable_actual_surface_frame_start();
141 start->set_cookie(cookie);
142 start->set_pid(pid);
143 }
144
CreateExpectedDisplayFrameStart(int32_t pid,int64_t cookie,protos::gen::FrameTimelineEvent * event) const145 void CreateExpectedDisplayFrameStart(
146 int32_t pid,
147 int64_t cookie,
148 protos::gen::FrameTimelineEvent* event) const {
149 auto* start = event->mutable_expected_display_frame_start();
150 start->set_cookie(cookie);
151 start->set_pid(pid);
152 }
153
CreateActualDisplayFrameStart(int32_t pid,int64_t cookie,protos::gen::FrameTimelineEvent * event) const154 void CreateActualDisplayFrameStart(
155 int32_t pid,
156 int64_t cookie,
157 protos::gen::FrameTimelineEvent* event) const {
158 auto* start = event->mutable_actual_display_frame_start();
159 start->set_cookie(cookie);
160 start->set_pid(pid);
161 }
162 };
163
164 class CollectFrameCookiesTest : public testing::Test,
165 protected FrameCookieFixture,
166 public testing::WithParamInterface<int32_t> {
167 protected:
168 Context context_;
169 };
170
TEST_P(CollectFrameCookiesTest,ExtractsExpectedSurfaceFrameStart)171 TEST_P(CollectFrameCookiesTest, ExtractsExpectedSurfaceFrameStart) {
172 auto field_id = GetParam();
173
174 auto packet = CreateStartEvent(field_id, kTimestampA, kPidA, kCookieA);
175
176 CollectCookies({packet}, &context_);
177
178 ASSERT_EQ(context_.global_frame_cookies.size(), 1u);
179
180 auto& cookie = context_.global_frame_cookies.back();
181 ASSERT_EQ(cookie.cookie, kCookieA);
182 ASSERT_EQ(cookie.pid, kPidA);
183 ASSERT_EQ(cookie.ts, kTimestampA);
184 }
185
186 INSTANTIATE_TEST_SUITE_P(
187 EveryStartEventType,
188 CollectFrameCookiesTest,
189 testing::Values(
190 protos::pbzero::FrameTimelineEvent::
191 kExpectedSurfaceFrameStartFieldNumber,
192 protos::pbzero::FrameTimelineEvent::kActualSurfaceFrameStartFieldNumber,
193 protos::pbzero::FrameTimelineEvent::
194 kExpectedDisplayFrameStartFieldNumber,
195 protos::pbzero::FrameTimelineEvent::
196 kActualDisplayFrameStartFieldNumber));
197
198 // End events have no influence during the collect phase because they don't have
199 // a direct connection to a process. They're indirectly connected to a pid via a
200 // start event (via a common cookie value).
TEST_F(CollectFrameCookiesTest,IgnoresFrameEnd)201 TEST_F(CollectFrameCookiesTest, IgnoresFrameEnd) {
202 CollectCookies({CreateFrameEnd(kTimestampA, kPidA)}, &context_);
203
204 ASSERT_TRUE(context_.global_frame_cookies.empty());
205 }
206
207 class ReduceFrameCookiesTest : public testing::Test,
208 protected FrameCookieFixture,
209 public testing::WithParamInterface<int32_t> {
210 protected:
SetUp()211 void SetUp() {
212 context_.package_uid = kUidA;
213
214 // Time A +- Time B +- Time C +- Time D +- Time E
215 // | |
216 // +------------ Pid A ---------+
217 //
218 // The pid will be active from time b to time d. Time A will be used for
219 // "before active". Time C will be used for "while active". Time E will be
220 // used for "after active".
221 CollectEvents(
222 {
223 ProcessThreadTimeline::Event::Open(kTimestampB, kPidA, kPidNone,
224 kUidA),
225 ProcessThreadTimeline::Event::Close(kTimestampD, kPidA),
226 },
227 &context_);
228 }
229
230 ReduceFrameCookies reduce_;
231 Context context_;
232 };
233
TEST_P(ReduceFrameCookiesTest,RejectBeforeActive)234 TEST_P(ReduceFrameCookiesTest, RejectBeforeActive) {
235 auto field_id = GetParam();
236
237 // kTimestampA is before pid starts.
238 auto packet = CreateStartEvent(field_id, kTimestampA, kPidA, kCookieA);
239
240 CollectCookies({packet}, &context_);
241
242 ASSERT_OK(reduce_.Build(&context_));
243 ASSERT_FALSE(context_.package_frame_cookies.count(kCookieA));
244 }
245
TEST_P(ReduceFrameCookiesTest,AcceptDuringActive)246 TEST_P(ReduceFrameCookiesTest, AcceptDuringActive) {
247 auto field_id = GetParam();
248
249 // kTimestampC is between pid starts and ends.
250 auto packet = CreateStartEvent(field_id, kTimestampC, kPidA, kCookieA);
251
252 CollectCookies({packet}, &context_);
253
254 ASSERT_OK(reduce_.Build(&context_));
255 ASSERT_TRUE(context_.package_frame_cookies.count(kCookieA));
256 }
257
TEST_P(ReduceFrameCookiesTest,RejectAfterActive)258 TEST_P(ReduceFrameCookiesTest, RejectAfterActive) {
259 auto field_id = GetParam();
260
261 // kTimestampE is after pid ends.
262 auto packet = CreateStartEvent(field_id, kTimestampE, kPidA, kCookieA);
263
264 CollectCookies({packet}, &context_);
265
266 ASSERT_OK(reduce_.Build(&context_));
267 ASSERT_FALSE(context_.package_frame_cookies.count(kCookieA));
268 }
269
270 INSTANTIATE_TEST_SUITE_P(
271 EveryStartEventType,
272 ReduceFrameCookiesTest,
273 testing::Values(
274 protos::pbzero::FrameTimelineEvent::
275 kExpectedSurfaceFrameStartFieldNumber,
276 protos::pbzero::FrameTimelineEvent::kActualSurfaceFrameStartFieldNumber,
277 protos::pbzero::FrameTimelineEvent::
278 kExpectedDisplayFrameStartFieldNumber,
279 protos::pbzero::FrameTimelineEvent::
280 kActualDisplayFrameStartFieldNumber));
281
282 class FilterCookiesFieldsTest : public testing::Test,
283 protected FrameCookieFixture,
284 public testing::WithParamInterface<int32_t> {
285 protected:
ExtractTimelineEvent(const std::string & packet) const286 protozero::Field ExtractTimelineEvent(const std::string& packet) const {
287 protozero::ProtoDecoder packet_decoder(packet);
288
289 // There must be one in order for the test to work, so we assume it's there.
290 return packet_decoder.FindField(
291 protos::pbzero::TracePacket::kFrameTimelineEventFieldNumber);
292 }
293
294 FilterFrameEvents filter_;
295 Context context_;
296 };
297
298 // If the event was within a valid pid's lifespan and was connected to the
299 // package, it should be kept.
TEST_P(FilterCookiesFieldsTest,IncludeIncludedStartCookies)300 TEST_P(FilterCookiesFieldsTest, IncludeIncludedStartCookies) {
301 context_.package_frame_cookies.insert(kCookieA);
302
303 auto field_id = GetParam();
304 auto packet = CreateStartEvent(field_id, kTimestampA, kPidA, kCookieA);
305 auto timeline_field = ExtractTimelineEvent(packet);
306
307 ASSERT_TRUE(filter_.KeepField(context_, timeline_field));
308 }
309
310 // If the event wasn't within a valid pid's lifespans and/or was connected to a
311 // package, it should be removed.
TEST_P(FilterCookiesFieldsTest,ExcludeMissingStartCookies)312 TEST_P(FilterCookiesFieldsTest, ExcludeMissingStartCookies) {
313 auto field_id = GetParam();
314 auto packet = CreateStartEvent(field_id, kTimestampA, kPidA, kCookieA);
315 auto timeline_field = ExtractTimelineEvent(packet);
316
317 ASSERT_FALSE(filter_.KeepField(context_, timeline_field));
318 }
319
320 INSTANTIATE_TEST_SUITE_P(
321 EveryStartEventType,
322 FilterCookiesFieldsTest,
323 testing::Values(
324 protos::pbzero::FrameTimelineEvent::
325 kExpectedSurfaceFrameStartFieldNumber,
326 protos::pbzero::FrameTimelineEvent::kActualSurfaceFrameStartFieldNumber,
327 protos::pbzero::FrameTimelineEvent::
328 kExpectedDisplayFrameStartFieldNumber,
329 protos::pbzero::FrameTimelineEvent::
330 kActualDisplayFrameStartFieldNumber));
331
TEST_F(FilterCookiesFieldsTest,IncludeIncludedEndCookies)332 TEST_F(FilterCookiesFieldsTest, IncludeIncludedEndCookies) {
333 context_.package_frame_cookies.insert(kCookieA);
334
335 auto packet = CreateFrameEnd(kTimestampA, kCookieA);
336 auto timeline_field = ExtractTimelineEvent(packet);
337
338 ASSERT_TRUE(filter_.KeepField(context_, timeline_field));
339 }
340
TEST_F(FilterCookiesFieldsTest,ExcludeMissingEndCookies)341 TEST_F(FilterCookiesFieldsTest, ExcludeMissingEndCookies) {
342 auto packet = CreateFrameEnd(kTimestampA, kCookieA);
343 auto timeline_field = ExtractTimelineEvent(packet);
344
345 ASSERT_FALSE(filter_.KeepField(context_, timeline_field));
346 }
347
348 } // namespace perfetto::trace_redaction
349