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 "perfetto/ext/base/status_or.h"
20 #include "src/base/test/status_matchers.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/trace_packet.gen.h"
25 #include "protos/perfetto/trace/trace_packet.pbzero.h"
26
27 namespace perfetto::trace_redaction {
28 namespace {
29
30 constexpr uint64_t kTimeStep = 1000;
31
32 constexpr uint64_t kTimestampA = 0;
33 constexpr uint64_t kTimestampB = kTimeStep;
34 constexpr uint64_t kTimestampC = kTimeStep * 2;
35 constexpr uint64_t kTimestampD = kTimeStep * 3;
36 constexpr uint64_t kTimestampE = kTimeStep * 4;
37
38 constexpr int64_t kCookieA = 1234;
39 constexpr int64_t kCookieB = 2345;
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 enum class FrameCookieType {
48 ExpectedSurface,
49 ExpectedDisplay,
50 ActualSurface,
51 ActualDisplay,
52 };
53
CreateExpectedSurfaceFrameStart(uint64_t ts,int32_t pid,int64_t cookie)54 protos::gen::TracePacket CreateExpectedSurfaceFrameStart(uint64_t ts,
55 int32_t pid,
56 int64_t cookie) {
57 protos::gen::TracePacket packet;
58 packet.set_timestamp(ts);
59 auto* start = packet.mutable_frame_timeline_event()
60 ->mutable_expected_surface_frame_start();
61 start->set_cookie(cookie);
62 start->set_pid(pid);
63 return packet;
64 }
65
CreateActualSurfaceFrameStart(uint64_t ts,int32_t pid,int64_t cookie)66 protos::gen::TracePacket CreateActualSurfaceFrameStart(uint64_t ts,
67 int32_t pid,
68 int64_t cookie) {
69 protos::gen::TracePacket packet;
70 packet.set_timestamp(ts);
71 auto* start = packet.mutable_frame_timeline_event()
72 ->mutable_actual_surface_frame_start();
73 start->set_cookie(cookie);
74 start->set_pid(pid);
75
76 return packet;
77 }
78
CreateExpectedDisplayFrameStart(uint64_t ts,int32_t pid,int64_t cookie)79 protos::gen::TracePacket CreateExpectedDisplayFrameStart(uint64_t ts,
80 int32_t pid,
81 int64_t cookie) {
82 protos::gen::TracePacket packet;
83 packet.set_timestamp(ts);
84 auto* start = packet.mutable_frame_timeline_event()
85 ->mutable_expected_display_frame_start();
86 start->set_cookie(cookie);
87 start->set_pid(pid);
88
89 return packet;
90 }
91
CreateActualDisplayFrameStart(uint64_t ts,int32_t pid,int64_t cookie)92 protos::gen::TracePacket CreateActualDisplayFrameStart(uint64_t ts,
93 int32_t pid,
94 int64_t cookie) {
95 protos::gen::TracePacket packet;
96 packet.set_timestamp(ts);
97 auto* start = packet.mutable_frame_timeline_event()
98 ->mutable_actual_display_frame_start();
99 start->set_cookie(cookie);
100 start->set_pid(pid);
101
102 return packet;
103 }
104
CreateFrameEnd(uint64_t ts,int64_t cookie)105 protos::gen::TracePacket CreateFrameEnd(uint64_t ts, int64_t cookie) {
106 protos::gen::TracePacket packet;
107 packet.set_timestamp(ts);
108
109 auto* start = packet.mutable_frame_timeline_event()->mutable_frame_end();
110 start->set_cookie(cookie);
111
112 return packet;
113 }
114
CreateStartPacket(FrameCookieType type,uint64_t ts,int32_t pid,int64_t cookie)115 protos::gen::TracePacket CreateStartPacket(FrameCookieType type,
116 uint64_t ts,
117 int32_t pid,
118 int64_t cookie) {
119 switch (type) {
120 case FrameCookieType::ExpectedSurface:
121 return CreateExpectedSurfaceFrameStart(ts, pid, cookie);
122 case FrameCookieType::ExpectedDisplay:
123 return CreateExpectedDisplayFrameStart(ts, pid, cookie);
124 case FrameCookieType::ActualSurface:
125 return CreateActualSurfaceFrameStart(ts, pid, cookie);
126 case FrameCookieType::ActualDisplay:
127 return CreateActualDisplayFrameStart(ts, pid, cookie);
128 }
129
130 PERFETTO_FATAL("Unhandled case. This should never happen.");
131 }
132
CollectCookies(const std::string & packet,Context * context)133 void CollectCookies(const std::string& packet, Context* context) {
134 protos::pbzero::TracePacket::Decoder decoder(packet);
135
136 CollectFrameCookies collect_;
137 ASSERT_OK(collect_.Begin(context));
138 ASSERT_OK(collect_.Collect(decoder, context));
139 ASSERT_OK(collect_.End(context));
140 }
141
142 class FrameCookieTest : public testing::Test {
143 protected:
144 CollectFrameCookies collect_;
145 Context context_;
146 };
147
TEST_F(FrameCookieTest,ExtractsExpectedSurfaceFrameStart)148 TEST_F(FrameCookieTest, ExtractsExpectedSurfaceFrameStart) {
149 auto packet = CreateExpectedSurfaceFrameStart(kTimestampA, kPidA, kCookieA);
150 auto bytes = packet.SerializeAsString();
151 CollectCookies(bytes, &context_);
152
153 ASSERT_EQ(context_.global_frame_cookies.size(), 1u);
154
155 const auto& cookie = context_.global_frame_cookies.back();
156 ASSERT_EQ(cookie.cookie, kCookieA);
157 ASSERT_EQ(cookie.pid, kPidA);
158 ASSERT_EQ(cookie.ts, kTimestampA);
159 }
160
TEST_F(FrameCookieTest,ExtractsActualSurfaceFrameStart)161 TEST_F(FrameCookieTest, ExtractsActualSurfaceFrameStart) {
162 auto packet = CreateActualSurfaceFrameStart(kTimestampA, kPidA, kCookieA);
163 auto bytes = packet.SerializeAsString();
164 CollectCookies(bytes, &context_);
165
166 ASSERT_EQ(context_.global_frame_cookies.size(), 1u);
167
168 const auto& cookie = context_.global_frame_cookies.back();
169 ASSERT_EQ(cookie.cookie, kCookieA);
170 ASSERT_EQ(cookie.pid, kPidA);
171 ASSERT_EQ(cookie.ts, kTimestampA);
172 }
173
TEST_F(FrameCookieTest,ExtractsExpectedDisplayFrameStart)174 TEST_F(FrameCookieTest, ExtractsExpectedDisplayFrameStart) {
175 auto packet = CreateExpectedDisplayFrameStart(kTimestampA, kPidA, kCookieA);
176 auto bytes = packet.SerializeAsString();
177 CollectCookies(bytes, &context_);
178
179 ASSERT_EQ(context_.global_frame_cookies.size(), 1u);
180
181 const auto& cookie = context_.global_frame_cookies.back();
182 ASSERT_EQ(cookie.cookie, kCookieA);
183 ASSERT_EQ(cookie.pid, kPidA);
184 ASSERT_EQ(cookie.ts, kTimestampA);
185 }
186
TEST_F(FrameCookieTest,ExtractsActualDisplayFrameStart)187 TEST_F(FrameCookieTest, ExtractsActualDisplayFrameStart) {
188 auto packet = CreateActualDisplayFrameStart(kTimestampA, kPidA, kCookieA);
189 auto bytes = packet.SerializeAsString();
190 CollectCookies(bytes, &context_);
191
192 ASSERT_EQ(context_.global_frame_cookies.size(), 1u);
193
194 const auto& cookie = context_.global_frame_cookies.back();
195 ASSERT_EQ(cookie.cookie, kCookieA);
196 ASSERT_EQ(cookie.pid, kPidA);
197 ASSERT_EQ(cookie.ts, kTimestampA);
198 }
199
200 // End events have no influence during the collect phase because they don't have
201 // a direct connection to a process. They're indirectly connected to a pid via a
202 // start event (via a common cookie value).
TEST_F(FrameCookieTest,IgnoresFrameEnd)203 TEST_F(FrameCookieTest, IgnoresFrameEnd) {
204 auto packet = CreateFrameEnd(kTimestampA, kCookieA);
205 auto bytes = packet.SerializeAsString();
206 CollectCookies(bytes, &context_);
207
208 ASSERT_TRUE(context_.global_frame_cookies.empty());
209 }
210
211 class ReduceFrameCookiesTest
212 : public testing::Test,
213 public testing::WithParamInterface<FrameCookieType> {
214 protected:
SetUp()215 void SetUp() {
216 context_.package_uid = kUidA;
217
218 // Time A +- Time B +- Time C +- Time D +- Time E
219 // | |
220 // +------------ Pid A ---------+
221 //
222 // The pid will be active from time b to time d. Time A will be used for
223 // "before active". Time C will be used for "while active". Time E will be
224 // used for "after active".
225 context_.timeline = std::make_unique<ProcessThreadTimeline>();
226 context_.timeline->Append(ProcessThreadTimeline::Event::Open(
227 kTimestampB, kPidA, kPidNone, kUidA));
228 context_.timeline->Append(
229 ProcessThreadTimeline::Event::Close(kTimestampD, kPidA));
230 context_.timeline->Sort();
231 }
232
233 ReduceFrameCookies reduce_;
234 Context context_;
235 };
236
TEST_P(ReduceFrameCookiesTest,RejectBeforeStart)237 TEST_P(ReduceFrameCookiesTest, RejectBeforeStart) {
238 auto packet = CreateStartPacket(GetParam(), kTimestampA, kPidA, kCookieA);
239 auto bytes = packet.SerializeAsString();
240 CollectCookies(bytes, &context_);
241
242 ASSERT_OK(reduce_.Build(&context_));
243 ASSERT_FALSE(context_.package_frame_cookies.count(kCookieA));
244 }
245
TEST_P(ReduceFrameCookiesTest,AcceptAtStart)246 TEST_P(ReduceFrameCookiesTest, AcceptAtStart) {
247 auto packet = CreateStartPacket(GetParam(), kTimestampB, kPidA, kCookieA);
248 auto bytes = packet.SerializeAsString();
249 CollectCookies(bytes, &context_);
250
251 ASSERT_OK(reduce_.Build(&context_));
252 ASSERT_TRUE(context_.package_frame_cookies.count(kCookieA));
253 }
254
TEST_P(ReduceFrameCookiesTest,AcceptBetweenStartAndEnd)255 TEST_P(ReduceFrameCookiesTest, AcceptBetweenStartAndEnd) {
256 auto packet = CreateStartPacket(GetParam(), kTimestampC, kPidA, kCookieA);
257 auto bytes = packet.SerializeAsString();
258 CollectCookies(bytes, &context_);
259
260 ASSERT_OK(reduce_.Build(&context_));
261 ASSERT_TRUE(context_.package_frame_cookies.count(kCookieA));
262 }
263
TEST_P(ReduceFrameCookiesTest,AcceptAtEnd)264 TEST_P(ReduceFrameCookiesTest, AcceptAtEnd) {
265 auto packet = CreateStartPacket(GetParam(), kTimestampD, kPidA, kCookieA);
266 auto bytes = packet.SerializeAsString();
267 CollectCookies(bytes, &context_);
268
269 ASSERT_OK(reduce_.Build(&context_));
270 ASSERT_TRUE(context_.package_frame_cookies.count(kCookieA));
271 }
272
TEST_P(ReduceFrameCookiesTest,RejectAfterEnd)273 TEST_P(ReduceFrameCookiesTest, RejectAfterEnd) {
274 auto packet = CreateStartPacket(GetParam(), kTimestampE, kPidA, kCookieA);
275 auto bytes = packet.SerializeAsString();
276 CollectCookies(bytes, &context_);
277
278 ASSERT_OK(reduce_.Build(&context_));
279 ASSERT_FALSE(context_.package_frame_cookies.count(kCookieA));
280 }
281
282 INSTANTIATE_TEST_SUITE_P(Default,
283 ReduceFrameCookiesTest,
284 testing::Values(FrameCookieType::ExpectedSurface,
285 FrameCookieType::ExpectedDisplay,
286 FrameCookieType::ActualSurface,
287 FrameCookieType::ActualDisplay));
288
289 class TransformStartCookiesTest
290 : public testing::Test,
291 public testing::WithParamInterface<FrameCookieType> {
292 protected:
SetUp()293 void SetUp() { context_.package_frame_cookies.insert(kCookieA); }
294
295 FilterFrameEvents filter_;
296 Context context_;
297 };
298
TEST_P(TransformStartCookiesTest,RetainStartEvent)299 TEST_P(TransformStartCookiesTest, RetainStartEvent) {
300 auto packet = CreateStartPacket(GetParam(), kTimestampE, kPidA, kCookieA);
301 auto bytes = packet.SerializeAsString();
302
303 ASSERT_OK(filter_.Transform(context_, &bytes));
304
305 protos::gen::TracePacket redacted;
306 ASSERT_TRUE(redacted.ParseFromString(bytes));
307
308 ASSERT_TRUE(redacted.has_frame_timeline_event());
309 const auto& timeline = redacted.frame_timeline_event();
310
311 int64_t cookie;
312
313 // Find the cookie from the packet.
314 switch (GetParam()) {
315 case FrameCookieType::ExpectedSurface: {
316 ASSERT_TRUE(timeline.has_expected_surface_frame_start());
317 const auto& start = timeline.expected_surface_frame_start();
318 ASSERT_TRUE(start.has_cookie());
319 cookie = start.cookie();
320
321 break;
322 }
323
324 case FrameCookieType::ExpectedDisplay: {
325 ASSERT_TRUE(timeline.has_expected_display_frame_start());
326 const auto& start = timeline.expected_display_frame_start();
327 ASSERT_TRUE(start.has_cookie());
328 cookie = start.cookie();
329
330 break;
331 }
332
333 case FrameCookieType::ActualSurface: {
334 ASSERT_TRUE(timeline.has_actual_surface_frame_start());
335 const auto& start = timeline.actual_surface_frame_start();
336 ASSERT_TRUE(start.has_cookie());
337 cookie = start.cookie();
338
339 break;
340 }
341 case FrameCookieType::ActualDisplay: {
342 ASSERT_TRUE(timeline.has_actual_display_frame_start());
343 const auto& start = timeline.actual_display_frame_start();
344 ASSERT_TRUE(start.has_cookie());
345 cookie = start.cookie();
346
347 break;
348 }
349 }
350
351 ASSERT_EQ(cookie, kCookieA);
352 }
353
TEST_P(TransformStartCookiesTest,DropStartEvent)354 TEST_P(TransformStartCookiesTest, DropStartEvent) {
355 // Even those this packet is using PidA, because CookieA is not in the package
356 // coookie pool, the event should be dropped.
357 auto packet = CreateStartPacket(GetParam(), kTimestampE, kPidA, kCookieB);
358 auto bytes = packet.SerializeAsString();
359
360 ASSERT_OK(filter_.Transform(context_, &bytes));
361
362 protos::gen::TracePacket redacted;
363 ASSERT_TRUE(redacted.ParseFromString(bytes));
364
365 ASSERT_FALSE(redacted.has_frame_timeline_event());
366 }
367
368 INSTANTIATE_TEST_SUITE_P(Default,
369 TransformStartCookiesTest,
370 testing::Values(FrameCookieType::ExpectedSurface,
371 FrameCookieType::ExpectedDisplay,
372 FrameCookieType::ActualSurface,
373 FrameCookieType::ActualDisplay));
374
375 class TransformEndFrameCookiesTest : public testing::Test {
376 protected:
SetUp()377 void SetUp() { context_.package_frame_cookies.insert(kCookieA); }
378
379 FilterFrameEvents filter_;
380 Context context_;
381 };
382
383 // An end event has no pid. The cookie connects it to a pid. If the start event
384 // cookie moved from the global pool into the package pool, then end the end
385 // event should be retained.
TEST_F(TransformStartCookiesTest,Retain)386 TEST_F(TransformStartCookiesTest, Retain) {
387 auto packet = CreateFrameEnd(kTimestampA, kCookieA);
388 auto bytes = packet.SerializeAsString();
389
390 ASSERT_OK(filter_.Transform(context_, &bytes));
391
392 protos::gen::TracePacket redacted;
393 ASSERT_TRUE(redacted.ParseFromString(bytes));
394
395 ASSERT_TRUE(redacted.has_frame_timeline_event());
396 const auto& timeline = redacted.frame_timeline_event();
397
398 ASSERT_TRUE(timeline.has_frame_end());
399 const auto& end = timeline.frame_end();
400
401 ASSERT_EQ(end.cookie(), kCookieA);
402 }
403
TEST_F(TransformStartCookiesTest,Drop)404 TEST_F(TransformStartCookiesTest, Drop) {
405 auto packet = CreateFrameEnd(kTimestampA, kCookieB);
406 auto bytes = packet.SerializeAsString();
407
408 ASSERT_OK(filter_.Transform(context_, &bytes));
409
410 protos::gen::TracePacket redacted;
411 ASSERT_TRUE(redacted.ParseFromString(bytes));
412
413 ASSERT_FALSE(redacted.has_frame_timeline_event());
414 }
415
416 } // namespace
417 } // namespace perfetto::trace_redaction
418