1 /*
2 * Copyright (C) 2024 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 #include "src/trace_redaction/redact_sched_events.h"
18 #include "src/base/test/status_matchers.h"
19 #include "test/gtest_and_gmock.h"
20
21 #include "protos/perfetto/trace/ftrace/ftrace_event.gen.h"
22 #include "protos/perfetto/trace/ftrace/ftrace_event_bundle.gen.h"
23 #include "protos/perfetto/trace/ftrace/sched.gen.h"
24 #include "protos/perfetto/trace/trace.gen.h"
25 #include "protos/perfetto/trace/trace_packet.gen.h"
26
27 namespace perfetto::trace_redaction {
28
29 namespace {
30 constexpr uint64_t kUidA = 1;
31 constexpr uint64_t kUidB = 2;
32 constexpr uint64_t kUidC = 3;
33
34 constexpr int32_t kNoParent = 10;
35 constexpr int32_t kPidA = 11;
36 constexpr int32_t kPidB = 12;
37 constexpr int32_t kPidC = 13;
38 constexpr int32_t kPidD = 14;
39
40 constexpr int32_t kCpuA = 0;
41 constexpr int32_t kCpuB = 1;
42 constexpr int32_t kCpuC = 2;
43
44 constexpr uint64_t kHalfStep = 500;
45 constexpr uint64_t kFullStep = kHalfStep * 2;
46
47 constexpr uint64_t kTimeA = 0;
48 constexpr uint64_t kTimeB = kFullStep;
49 constexpr uint64_t kTimeC = kFullStep * 2;
50
51 constexpr auto kCommA = "comm-a";
52 constexpr auto kCommB = "comm-b";
53 constexpr auto kCommC = "comm-c";
54 constexpr auto kCommNone = "";
55
56 template <int32_t new_pid>
57 class ChangePidTo : public PidCommModifier {
58 public:
Modify(const Context & context,uint64_t ts,int32_t,int32_t * pid,std::string *) const59 void Modify(const Context& context,
60 uint64_t ts,
61 int32_t,
62 int32_t* pid,
63 std::string*) const override {
64 PERFETTO_DCHECK(context.timeline);
65 PERFETTO_DCHECK(context.package_uid.has_value());
66 PERFETTO_DCHECK(pid);
67 if (!context.timeline->PidConnectsToUid(ts, *pid, *context.package_uid)) {
68 *pid = new_pid;
69 }
70 }
71 };
72 } // namespace
73
74 class RedactSchedSwitchFtraceEventTest : public testing::Test {
75 protected:
SetUp()76 void SetUp() override {
77 // Create a packet where two pids are swapping back-and-forth.
78 auto* bundle = packet_.mutable_ftrace_events();
79 bundle->set_cpu(kCpuA);
80
81 {
82 auto* event = bundle->add_event();
83
84 event->set_timestamp(kTimeA);
85 event->set_pid(kPidA);
86
87 auto* sched_switch = event->mutable_sched_switch();
88 sched_switch->set_prev_comm(kCommA);
89 sched_switch->set_prev_pid(kPidA);
90 sched_switch->set_prev_prio(0);
91 sched_switch->set_prev_state(0);
92 sched_switch->set_next_comm(kCommB);
93 sched_switch->set_next_pid(kPidB);
94 sched_switch->set_next_prio(0);
95 }
96
97 {
98 auto* event = bundle->add_event();
99
100 event->set_timestamp(kTimeB);
101 event->set_pid(kPidB);
102
103 auto* sched_switch = event->mutable_sched_switch();
104 sched_switch->set_prev_comm(kCommB);
105 sched_switch->set_prev_pid(kPidB);
106 sched_switch->set_prev_prio(0);
107 sched_switch->set_prev_state(0);
108 sched_switch->set_next_comm(kCommA);
109 sched_switch->set_next_pid(kPidA);
110 sched_switch->set_next_prio(0);
111 }
112
113 // PID A and PID B need to be attached to different packages (UID) so that
114 // its possible to include one but not the other.
115 context_.timeline = std::make_unique<ProcessThreadTimeline>();
116 context_.timeline->Append(
117 ProcessThreadTimeline::Event::Open(kTimeA, kPidA, kNoParent, kUidA));
118 context_.timeline->Append(
119 ProcessThreadTimeline::Event::Open(kTimeA, kPidB, kNoParent, kUidB));
120 context_.timeline->Sort();
121
122 redact_.emplace_modifier<ClearComms>();
123 redact_.emplace_waking_filter<AllowAll>();
124 }
125
126 protos::gen::TracePacket packet_;
127 Context context_;
128 RedactSchedEvents redact_;
129 };
130
131 // In this case, the target uid will be UID A. That means the comm values for
132 // PID B should be removed, and the comm values for PID A should remain.
TEST_F(RedactSchedSwitchFtraceEventTest,KeepsTargetCommValues)133 TEST_F(RedactSchedSwitchFtraceEventTest, KeepsTargetCommValues) {
134 context_.package_uid = kUidA;
135
136 auto packet_buffer = packet_.SerializeAsString();
137
138 ASSERT_OK(redact_.Transform(context_, &packet_buffer));
139
140 protos::gen::TracePacket packet;
141 ASSERT_TRUE(packet.ParseFromString(packet_buffer));
142
143 const auto& bundle = packet.ftrace_events();
144 const auto& events = bundle.event();
145
146 ASSERT_EQ(events.size(), 2u);
147
148 ASSERT_EQ(events[0].sched_switch().prev_pid(), kPidA);
149 ASSERT_EQ(events[0].sched_switch().prev_comm(), kCommA);
150
151 ASSERT_EQ(events[0].sched_switch().next_pid(), kPidB);
152 ASSERT_EQ(events[0].sched_switch().next_comm(), kCommNone);
153
154 ASSERT_EQ(events[1].sched_switch().prev_pid(), kPidB);
155 ASSERT_EQ(events[1].sched_switch().prev_comm(), kCommNone);
156
157 ASSERT_EQ(events[1].sched_switch().next_pid(), kPidA);
158 ASSERT_EQ(events[1].sched_switch().next_comm(), kCommA);
159 }
160
161 // This case is very similar to the "some are connected", expect that it
162 // verifies all comm values will be removed when testing against an unused
163 // uid.
TEST_F(RedactSchedSwitchFtraceEventTest,RemovesAllCommsIfPackageDoesntExist)164 TEST_F(RedactSchedSwitchFtraceEventTest, RemovesAllCommsIfPackageDoesntExist) {
165 context_.package_uid = kUidC;
166
167 auto packet_buffer = packet_.SerializeAsString();
168
169 ASSERT_OK(redact_.Transform(context_, &packet_buffer));
170
171 protos::gen::TracePacket packet;
172 ASSERT_TRUE(packet.ParseFromString(packet_buffer));
173
174 const auto& bundle = packet.ftrace_events();
175 const auto& events = bundle.event();
176
177 ASSERT_EQ(events.size(), 2u);
178
179 ASSERT_EQ(events[0].sched_switch().prev_comm(), kCommNone);
180 ASSERT_EQ(events[0].sched_switch().next_comm(), kCommNone);
181
182 ASSERT_EQ(events[1].sched_switch().prev_comm(), kCommNone);
183 ASSERT_EQ(events[1].sched_switch().next_comm(), kCommNone);
184 }
185
186 class RedactCompactSchedSwitchTest : public testing::Test {
187 protected:
SetUp()188 void SetUp() override {
189 // PID A and PID B need to be attached to different packages (UID) so that
190 // its possible to include one but not the other.
191 context_.timeline = std::make_unique<ProcessThreadTimeline>();
192 context_.timeline->Append(
193 ProcessThreadTimeline::Event::Open(kTimeA, kPidA, kNoParent, kUidA));
194 context_.timeline->Append(
195 ProcessThreadTimeline::Event::Open(kTimeA, kPidB, kNoParent, kUidB));
196 context_.timeline->Sort();
197
198 auto* bundle = packet_.mutable_ftrace_events();
199 bundle->set_cpu(kCpuA); // All switch events occur on this CPU
200
201 compact_sched = bundle->mutable_compact_sched();
202
203 compact_sched->add_intern_table(kCommA);
204 compact_sched->add_intern_table(kCommB);
205
206 redact_.emplace_modifier<ClearComms>();
207 redact_.emplace_waking_filter<AllowAll>();
208 }
209
AddSwitchEvent(uint64_t ts,int32_t next_pid,int32_t prev_state,int32_t prio,uint32_t comm)210 void AddSwitchEvent(uint64_t ts,
211 int32_t next_pid,
212 int32_t prev_state,
213 int32_t prio,
214 uint32_t comm) {
215 compact_sched->add_switch_timestamp(ts);
216 compact_sched->add_switch_next_pid(next_pid);
217 compact_sched->add_switch_prev_state(prev_state);
218 compact_sched->add_switch_next_prio(prio);
219 compact_sched->add_switch_next_comm_index(comm);
220 }
221
222 protos::gen::TracePacket packet_;
223 protos::gen::FtraceEventBundle::CompactSched* compact_sched;
224
225 Context context_;
226 RedactSchedEvents redact_;
227 };
228
TEST_F(RedactCompactSchedSwitchTest,KeepsTargetCommValues)229 TEST_F(RedactCompactSchedSwitchTest, KeepsTargetCommValues) {
230 uint32_t kCommIndexA = 0;
231 uint32_t kCommIndexB = 1;
232
233 // The new entry will be appended to the table. Another primitive can be used
234 // to reduce the intern string table.
235 uint32_t kCommIndexNone = 2;
236
237 AddSwitchEvent(kTimeA, kPidA, 0, 0, kCommIndexA);
238 AddSwitchEvent(kTimeB, kPidB, 0, 0, kCommIndexB);
239
240 context_.package_uid = kUidA;
241
242 auto packet_buffer = packet_.SerializeAsString();
243
244 ASSERT_OK(redact_.Transform(context_, &packet_buffer));
245
246 protos::gen::TracePacket packet;
247 ASSERT_TRUE(packet.ParseFromString(packet_buffer));
248
249 const auto& bundle = packet.ftrace_events();
250 ASSERT_TRUE(bundle.has_compact_sched());
251
252 const auto& compact_sched = bundle.compact_sched();
253
254 // A new entry (empty string) should have been added to the table.
255 ASSERT_EQ(compact_sched.intern_table_size(), 3);
256 ASSERT_EQ(compact_sched.intern_table().back(), kCommNone);
257
258 ASSERT_EQ(compact_sched.switch_next_comm_index_size(), 2);
259 ASSERT_EQ(compact_sched.switch_next_comm_index().at(0), kCommIndexA);
260 ASSERT_EQ(compact_sched.switch_next_comm_index().at(1), kCommIndexNone);
261 }
262
263 // If two pids use the same comm, but one pid changes, the shared comm should
264 // still be available.
TEST_F(RedactCompactSchedSwitchTest,ChangingSharedCommonRetainsComm)265 TEST_F(RedactCompactSchedSwitchTest, ChangingSharedCommonRetainsComm) {
266 uint32_t kCommIndexA = 0;
267
268 AddSwitchEvent(kTimeA, kPidA, 0, 0, kCommIndexA);
269 AddSwitchEvent(kTimeB, kPidB, 0, 0, kCommIndexA);
270
271 context_.package_uid = kUidA;
272
273 auto packet_buffer = packet_.SerializeAsString();
274
275 ASSERT_OK(redact_.Transform(context_, &packet_buffer));
276
277 protos::gen::TracePacket packet;
278 ASSERT_TRUE(packet.ParseFromString(packet_buffer));
279
280 const auto& bundle = packet.ftrace_events();
281 ASSERT_TRUE(bundle.has_compact_sched());
282
283 const auto& compact_sched = bundle.compact_sched();
284
285 // A new entry should have been appended, but comm A (previously shared)
286 // should still exist in the table.
287 ASSERT_EQ(compact_sched.intern_table_size(), 3);
288 ASSERT_EQ(compact_sched.intern_table().front(), kCommA);
289 ASSERT_EQ(compact_sched.intern_table().back(), kCommNone);
290 }
291
TEST_F(RedactCompactSchedSwitchTest,RemovesAllCommsIfPackageDoesntExist)292 TEST_F(RedactCompactSchedSwitchTest, RemovesAllCommsIfPackageDoesntExist) {
293 uint32_t kCommIndexA = 0;
294 uint32_t kCommIndexB = 1;
295
296 // The new entry will be appended to the table. Another primitive can be used
297 // to reduce the intern string table.
298 uint32_t kCommIndexNone = 2;
299
300 AddSwitchEvent(kTimeA, kPidA, 0, 0, kCommIndexA);
301 AddSwitchEvent(kTimeB, kPidB, 0, 0, kCommIndexB);
302
303 context_.package_uid = kUidC;
304
305 auto packet_buffer = packet_.SerializeAsString();
306
307 ASSERT_OK(redact_.Transform(context_, &packet_buffer));
308
309 protos::gen::TracePacket packet;
310 ASSERT_TRUE(packet.ParseFromString(packet_buffer));
311
312 const auto& bundle = packet.ftrace_events();
313 ASSERT_TRUE(bundle.has_compact_sched());
314
315 const auto& compact_sched = bundle.compact_sched();
316
317 // A new entry (empty string) should have been added to the table.
318 ASSERT_EQ(compact_sched.intern_table_size(), 3);
319 ASSERT_EQ(compact_sched.intern_table().back(), kCommNone);
320
321 ASSERT_EQ(compact_sched.switch_next_comm_index_size(), 2);
322 ASSERT_EQ(compact_sched.switch_next_comm_index().at(0), kCommIndexNone);
323 ASSERT_EQ(compact_sched.switch_next_comm_index().at(1), kCommIndexNone);
324 }
325
TEST_F(RedactCompactSchedSwitchTest,CanChangePid)326 TEST_F(RedactCompactSchedSwitchTest, CanChangePid) {
327 uint32_t kCommIndexA = 0;
328 uint32_t kCommIndexB = 1;
329
330 AddSwitchEvent(kTimeA, kPidA, 0, 0, kCommIndexA);
331 AddSwitchEvent(kTimeB, kPidB, 0, 0, kCommIndexB);
332
333 // Because the target is package A, PidA should be remain. PidB should change.
334 context_.package_uid = kUidA;
335
336 auto packet_buffer = packet_.SerializeAsString();
337
338 redact_.emplace_modifier<ChangePidTo<kPidC>>();
339
340 ASSERT_OK(redact_.Transform(context_, &packet_buffer));
341
342 protos::gen::TracePacket packet;
343 ASSERT_TRUE(packet.ParseFromString(packet_buffer));
344
345 const auto& bundle = packet.ftrace_events();
346 ASSERT_TRUE(bundle.has_compact_sched());
347
348 const auto& compact_sched = bundle.compact_sched();
349
350 // The intern table should not change.
351 ASSERT_EQ(compact_sched.intern_table_size(), 2);
352
353 ASSERT_EQ(compact_sched.switch_next_pid_size(), 2);
354 ASSERT_EQ(compact_sched.switch_next_pid().at(0), kPidA);
355
356 // Because Pid B was not connected to Uid A, it should have its pid changed.
357 ASSERT_EQ(compact_sched.switch_next_pid().at(1), kPidC);
358 }
359
360 class RedactSchedWakingFtraceEventTest : public testing::Test {
361 protected:
SetUp()362 void SetUp() override {
363 // Create a packet where two pids are swapping back-and-forth.
364 auto* bundle = packet_.mutable_ftrace_events();
365 bundle->set_cpu(kCpuA);
366
367 // Pid A wakes up Pid B at time Time B
368 {
369 auto* event = bundle->add_event();
370
371 event->set_timestamp(kTimeB);
372 event->set_pid(kPidA);
373
374 auto* sched_waking = event->mutable_sched_waking();
375 sched_waking->set_comm(kCommB);
376 sched_waking->set_pid(kPidB);
377 sched_waking->set_prio(0);
378 sched_waking->set_success(true);
379 sched_waking->set_target_cpu(kCpuB);
380 }
381
382 // Pid A wakes up Pid C at time Time C.
383 {
384 auto* event = bundle->add_event();
385
386 event->set_timestamp(kTimeC);
387 event->set_pid(kPidA);
388
389 auto* sched_waking = event->mutable_sched_waking();
390 sched_waking->set_comm(kCommC);
391 sched_waking->set_pid(kPidC);
392 sched_waking->set_prio(0);
393 sched_waking->set_success(true);
394 sched_waking->set_target_cpu(kCpuC);
395 }
396
397 // PID A and PID B need to be attached to different packages (UID) so that
398 // its possible to include one but not the other.
399 context_.timeline = std::make_unique<ProcessThreadTimeline>();
400 context_.timeline->Append(
401 ProcessThreadTimeline::Event::Open(kTimeA, kPidA, kNoParent, kUidA));
402 context_.timeline->Append(
403 ProcessThreadTimeline::Event::Open(kTimeA, kPidB, kNoParent, kUidB));
404 context_.timeline->Append(
405 ProcessThreadTimeline::Event::Open(kTimeA, kPidC, kNoParent, kUidC));
406 context_.timeline->Sort();
407
408 redact.emplace_modifier<ClearComms>();
409 redact.emplace_waking_filter<AllowAll>();
410 }
411
412 protos::gen::TracePacket packet_;
413 Context context_;
414
415 RedactSchedEvents redact;
416 };
417
TEST_F(RedactSchedWakingFtraceEventTest,WakeeKeepsCommWhenConnectedToPackage)418 TEST_F(RedactSchedWakingFtraceEventTest, WakeeKeepsCommWhenConnectedToPackage) {
419 context_.package_uid = kUidB;
420
421 auto packet_buffer = packet_.SerializeAsString();
422
423 ASSERT_OK(redact.Transform(context_, &packet_buffer));
424
425 protos::gen::TracePacket packet;
426 ASSERT_TRUE(packet.ParseFromString(packet_buffer));
427
428 const auto& bundle = packet.ftrace_events();
429 const auto& events = bundle.event();
430
431 ASSERT_EQ(events.size(), 2u);
432
433 ASSERT_EQ(events.front().sched_waking().comm(), kCommB);
434 ASSERT_EQ(events.back().sched_waking().comm(), kCommNone);
435 }
436
TEST_F(RedactSchedWakingFtraceEventTest,WakeeLosesCommWhenNotConnectedToPackage)437 TEST_F(RedactSchedWakingFtraceEventTest,
438 WakeeLosesCommWhenNotConnectedToPackage) {
439 context_.package_uid = kUidA;
440
441 auto packet_buffer = packet_.SerializeAsString();
442
443 ASSERT_OK(redact.Transform(context_, &packet_buffer));
444
445 protos::gen::TracePacket packet;
446 ASSERT_TRUE(packet.ParseFromString(packet_buffer));
447
448 const auto& bundle = packet.ftrace_events();
449 const auto& events = bundle.event();
450
451 ASSERT_EQ(events.size(), 2u);
452
453 ASSERT_EQ(events.front().sched_waking().comm(), kCommNone);
454 ASSERT_EQ(events.back().sched_waking().comm(), kCommNone);
455 }
456
TEST_F(RedactSchedWakingFtraceEventTest,WakeeKeepsPidWhenConnectedToPackage)457 TEST_F(RedactSchedWakingFtraceEventTest, WakeeKeepsPidWhenConnectedToPackage) {
458 redact.emplace_modifier<ChangePidTo<kPidD>>();
459
460 context_.package_uid = kUidB;
461
462 auto packet_buffer = packet_.SerializeAsString();
463
464 ASSERT_OK(redact.Transform(context_, &packet_buffer));
465
466 protos::gen::TracePacket packet;
467 ASSERT_TRUE(packet.ParseFromString(packet_buffer));
468
469 const auto& bundle = packet.ftrace_events();
470 const auto& events = bundle.event();
471
472 ASSERT_EQ(events.size(), 2u);
473
474 ASSERT_EQ(events.front().sched_waking().pid(), kPidB);
475
476 // Because Pid C was not connected to Uid B, it should have its pid changed.
477 ASSERT_EQ(events.back().sched_waking().pid(), kPidD);
478 }
479
TEST_F(RedactSchedWakingFtraceEventTest,WakeeLosesPidWhenNotConnectedToPackage)480 TEST_F(RedactSchedWakingFtraceEventTest,
481 WakeeLosesPidWhenNotConnectedToPackage) {
482 redact.emplace_modifier<ChangePidTo<kPidD>>();
483
484 context_.package_uid = kUidA;
485
486 auto packet_buffer = packet_.SerializeAsString();
487
488 ASSERT_OK(redact.Transform(context_, &packet_buffer));
489
490 protos::gen::TracePacket packet;
491 ASSERT_TRUE(packet.ParseFromString(packet_buffer));
492
493 const auto& bundle = packet.ftrace_events();
494 const auto& events = bundle.event();
495
496 ASSERT_EQ(events.size(), 2u);
497
498 // Both pids should have changed.
499 ASSERT_EQ(events.at(0).sched_waking().pid(), kPidD);
500 ASSERT_EQ(events.at(1).sched_waking().pid(), kPidD);
501 }
502
TEST_F(RedactSchedWakingFtraceEventTest,WakerPidIsLeftUnaffected)503 TEST_F(RedactSchedWakingFtraceEventTest, WakerPidIsLeftUnaffected) {
504 redact.emplace_modifier<ChangePidTo<kPidD>>();
505
506 context_.package_uid = kUidB;
507
508 auto packet_buffer = packet_.SerializeAsString();
509
510 ASSERT_OK(redact.Transform(context_, &packet_buffer));
511
512 protos::gen::TracePacket packet;
513 ASSERT_TRUE(packet.ParseFromString(packet_buffer));
514
515 const auto& bundle = packet.ftrace_events();
516 const auto& events = bundle.event();
517
518 ASSERT_EQ(events.size(), 2u);
519
520 // The waker in the ftrace event waking event should change, but by another
521 // primitive. This case only appears in the ftrace events because the waker is
522 // inferred in the comp sched case.
523 ASSERT_EQ(events.at(0).pid(), static_cast<uint32_t>(kPidA));
524 ASSERT_EQ(events.at(1).pid(), static_cast<uint32_t>(kPidA));
525 }
526
527 class FilterCompactSchedWakingEventsTest : public testing::Test {
528 protected:
SetUp()529 void SetUp() {
530 // Uid B is used instead of Uid A because Pid A, belonging to Uid A, is the
531 // waker. Pid B and Pid C are the wakees.
532 context_.package_uid = kUidB;
533
534 // FilterSchedWakingEvents expects a timeline because most
535 // FilterSchedWakingEvents::Filter filters will need one. However, the
536 // filter used in this test doesn't require one.
537 context_.timeline = std::make_unique<ProcessThreadTimeline>();
538
539 context_.timeline->Append(
540 ProcessThreadTimeline::Event::Open(kTimeA, kPidA, kNoParent, kUidA));
541 context_.timeline->Append(
542 ProcessThreadTimeline::Event::Open(kTimeA, kPidB, kNoParent, kUidB));
543 context_.timeline->Append(
544 ProcessThreadTimeline::Event::Open(kTimeA, kPidC, kNoParent, kUidC));
545 context_.timeline->Sort();
546
547 // Default to "allow all" and "change nothing" so a test only needs to
548 // override what they need.
549 redact_.emplace_waking_filter<AllowAll>();
550 redact_.emplace_modifier<DoNothing>();
551 }
552
553 Context context_;
554 RedactSchedEvents redact_;
555 };
556
557 // Builds a simple ftrace bundle that contains two ftrace events:
558 //
559 // - Pid A wakes up pid B
560 // - Pid A wakes up pid C
561 //
562 // Because compact sched uses associative arrays, the data will look like:
563 //
564 // - Time | PID | CPU | *
565 // -----+-------+-------+---
566 // 0.5 | kPidB | kCpuB |
567 // 1.5 | kPidC | kCpuB |
568 //
569 // Because the filter will only keep events where pid is being waked, only the
570 // first of the two events should remain.
TEST_F(FilterCompactSchedWakingEventsTest,FilterCompactSched)571 TEST_F(FilterCompactSchedWakingEventsTest, FilterCompactSched) {
572 redact_.emplace_waking_filter<ConnectedToPackage>();
573
574 protos::gen::TracePacket packet_builder;
575 packet_builder.mutable_ftrace_events()->set_cpu(kCpuA);
576
577 auto* compact_sched =
578 packet_builder.mutable_ftrace_events()->mutable_compact_sched();
579
580 compact_sched->add_intern_table(kCommA);
581
582 // Implementation detail: The timestamp, target cpu, and pid matter. The other
583 // values are copied to the output, but have no influence over the internal
584 // logic.
585 compact_sched->add_waking_comm_index(0);
586 compact_sched->add_waking_common_flags(0);
587 compact_sched->add_waking_prio(0);
588 compact_sched->add_waking_timestamp(kHalfStep);
589 compact_sched->add_waking_target_cpu(kCpuB);
590 compact_sched->add_waking_pid(kPidB);
591
592 compact_sched->add_waking_comm_index(0);
593 compact_sched->add_waking_common_flags(0);
594 compact_sched->add_waking_prio(0);
595 compact_sched->add_waking_timestamp(kFullStep + kHalfStep);
596 compact_sched->add_waking_target_cpu(kCpuB);
597 compact_sched->add_waking_pid(kPidC);
598
599 auto bytes = packet_builder.SerializeAsString();
600 ASSERT_OK(redact_.Transform(context_, &bytes));
601
602 protos::gen::TracePacket packet;
603 packet.ParseFromString(bytes);
604
605 ASSERT_TRUE(packet.has_ftrace_events());
606
607 const auto& events = packet.ftrace_events();
608 ASSERT_TRUE(events.has_compact_sched());
609
610 // All events not from Pid B should be removed. In this case, that means the
611 // event from Pid C should be dropped.
612 ASSERT_EQ(events.compact_sched().waking_pid_size(), 1);
613 ASSERT_EQ(events.compact_sched().waking_pid().at(0), kPidB);
614 }
615
616 // Timing information is based off delta-time values. When a row is removed
617 // from the compact sched arrays, downstream timing data is corrupted. The
618 // delta value of removed rows should be rolled into the next row.
TEST_F(FilterCompactSchedWakingEventsTest,CorrectsTimeWhenRemovingWakingEvents)619 TEST_F(FilterCompactSchedWakingEventsTest,
620 CorrectsTimeWhenRemovingWakingEvents) {
621 // All the times are delta times. The commented times are the absolute times.
622 std::array<uint64_t, 7> before = {
623 0,
624 kFullStep, // 1
625 kFullStep, // 2
626 kHalfStep, // 2.5
627 kHalfStep, // 3
628 kFullStep, // 4
629 kFullStep, // 5
630 };
631
632 // These are the times that should be drop
633 std::array<uint64_t, 3> drop_times = {
634 kFullStep, // 6
635 kFullStep, // 7
636 kHalfStep, // 7.5
637 };
638
639 // When the times are dropped, the times removed from drop_times should be
640 // rolling into the first time. So it should got from 1 unit to 3.5 units.
641 std::array<uint64_t, 2> after = {
642 kFullStep, // 8
643 kFullStep, // 9
644 };
645
646 protos::gen::TracePacket packet_builder;
647 packet_builder.mutable_ftrace_events()->set_cpu(kCpuA);
648
649 auto* compact_sched =
650 packet_builder.mutable_ftrace_events()->mutable_compact_sched();
651
652 compact_sched->add_intern_table(kCommA);
653
654 // Before and after, this events should not be affected.
655 for (auto time : before) {
656 compact_sched->add_waking_comm_index(0);
657 compact_sched->add_waking_common_flags(0);
658 compact_sched->add_waking_prio(0);
659 compact_sched->add_waking_timestamp(time);
660 compact_sched->add_waking_target_cpu(kCpuB);
661 compact_sched->add_waking_pid(kPidB);
662 }
663
664 // Use pid B so that these times will be dropped.
665 for (auto time : drop_times) {
666 compact_sched->add_waking_comm_index(0);
667 compact_sched->add_waking_common_flags(0);
668 compact_sched->add_waking_prio(0);
669 compact_sched->add_waking_timestamp(time);
670 compact_sched->add_waking_target_cpu(kCpuB);
671 compact_sched->add_waking_pid(kPidC);
672 }
673
674 // After redaction, these events should still exist, but the first event in
675 // this series, the timestamp should be larger (before of the dropped events).
676 for (auto time : after) {
677 compact_sched->add_waking_comm_index(0);
678 compact_sched->add_waking_common_flags(0);
679 compact_sched->add_waking_prio(0);
680 compact_sched->add_waking_timestamp(time);
681 compact_sched->add_waking_target_cpu(kCpuB);
682 compact_sched->add_waking_pid(kPidB);
683 }
684
685 auto bytes = packet_builder.SerializeAsString();
686
687 redact_.emplace_waking_filter<ConnectedToPackage>();
688 ASSERT_OK(redact_.Transform(context_, &bytes));
689
690 protos::gen::TracePacket packet;
691 ASSERT_TRUE(packet.ParseFromString(bytes));
692
693 ASSERT_TRUE(packet.has_ftrace_events());
694 const auto& events = packet.ftrace_events();
695
696 ASSERT_TRUE(events.has_compact_sched());
697 const auto& times = packet.ftrace_events().compact_sched().waking_timestamp();
698
699 ASSERT_EQ(times.size(), 9u); // i.e. before + after
700
701 // Nothing in the before should have changed.
702 for (size_t i = 0; i < before.size(); ++i) {
703 ASSERT_EQ(times[i], before[i]);
704 }
705
706 // Sum of all dropped event time.
707 ASSERT_EQ(drop_times.size(), 3u);
708 auto lost_time = drop_times[0] + drop_times[1] + drop_times[2];
709
710 // Only the first of the two "after" events should have changed.
711 ASSERT_EQ(times[before.size()], after[0] + lost_time);
712 ASSERT_EQ(times[before.size() + 1], after[1]);
713 }
714
715 // This is an implementation detail. When an event is removed, the gap is
716 // collapsed into the next event by tracking the error created by removing the
717 // event. If implemented incorrectly, flipping between keep and remove will
718 // break as the error will not be reset correctly.
TEST_F(FilterCompactSchedWakingEventsTest,RemovingWakingEventsThrashing)719 TEST_F(FilterCompactSchedWakingEventsTest, RemovingWakingEventsThrashing) {
720 // X : Drop this event
721 // [ ] : This is an event
722 // = : Number of time units
723 //
724 // X X X
725 // [==][==][=][==][==][=][==][==][=]
726 //
727 // Events are going to follow a "keep, keep, drop" pattern. All keep events
728 // will be full time units. All drop events will be half time units.
729 //
730 // It is key to notice that the series ends on a removed event. This creates a
731 // special: remove an event without an event to accept the error.
732 std::array<uint64_t, 9> before = {
733 0, // abs time 0
734 kFullStep, // abs time 1
735 kHalfStep, // abs time 1.5
736
737 kFullStep, // abs time 2.5
738 kFullStep, // abs time 3.5
739 kHalfStep, // abs time 4
740
741 kFullStep, // abs time 5
742 kFullStep, // abs time 6
743 kHalfStep, // abs time 6.5
744 };
745
746 std::array<uint64_t, 6> after = {
747 0, // abs time 0
748 kFullStep, // abs time 1
749 kFullStep + kHalfStep, // abs time 2.5
750 kFullStep, // abs time 3.5
751 kFullStep + kHalfStep, // abs time 5
752 kFullStep, // abs time 6
753 };
754
755 protos::gen::TracePacket packet_builder;
756 packet_builder.mutable_ftrace_events()->set_cpu(kCpuA);
757
758 auto* compact_sched =
759 packet_builder.mutable_ftrace_events()->mutable_compact_sched();
760
761 compact_sched->add_intern_table(kCommA);
762
763 for (size_t i = 0; i < before.size(); ++i) {
764 auto time = before[i];
765
766 compact_sched->add_waking_comm_index(0);
767 compact_sched->add_waking_common_flags(0);
768 compact_sched->add_waking_prio(0);
769 compact_sched->add_waking_timestamp(time);
770 compact_sched->add_waking_target_cpu(kCpuB);
771
772 // The pattern is "keep, keep, drop", therefore, PID B > B > C ...
773 if (i % 3 == 2) {
774 compact_sched->add_waking_pid(kPidC);
775 } else {
776 compact_sched->add_waking_pid(kPidB);
777 }
778 }
779
780 auto bytes = packet_builder.SerializeAsString();
781
782 redact_.emplace_waking_filter<ConnectedToPackage>();
783 ASSERT_OK(redact_.Transform(context_, &bytes));
784
785 protos::gen::TracePacket packet;
786 ASSERT_TRUE(packet.ParseFromString(bytes));
787
788 ASSERT_TRUE(packet.has_ftrace_events());
789 const auto& events = packet.ftrace_events();
790
791 ASSERT_TRUE(events.has_compact_sched());
792 const auto& times = packet.ftrace_events().compact_sched().waking_timestamp();
793
794 ASSERT_EQ(times.size(), after.size());
795
796 for (size_t i = 0; i < after.size(); ++i) {
797 ASSERT_EQ(times[i], after[i]);
798 }
799 }
800
801 } // namespace perfetto::trace_redaction
802