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
19 #include "perfetto/protozero/scattered_heap_buffer.h"
20 #include "src/trace_processor/util/status_macros.h"
21 #include "src/trace_redaction/proto_util.h"
22
23 #include "protos/perfetto/trace/ftrace/ftrace_event.pbzero.h"
24 #include "protos/perfetto/trace/ftrace/ftrace_event_bundle.pbzero.h"
25 #include "protos/perfetto/trace/ftrace/sched.pbzero.h"
26
27 namespace perfetto::trace_redaction {
28
29 namespace {
IsTrue(bool value)30 bool IsTrue(bool value) {
31 return value;
32 }
33
34 // Copy a field from 'decoder' to 'message' if the field can be found. Returns
35 // false if the field cannot be found.
Passthrough(protozero::ProtoDecoder & decoder,uint32_t field_id,protozero::Message * message)36 bool Passthrough(protozero::ProtoDecoder& decoder,
37 uint32_t field_id,
38 protozero::Message* message) {
39 auto field = decoder.FindField(field_id);
40
41 if (field.valid()) {
42 proto_util::AppendField(field, message);
43 return true;
44 }
45
46 return false;
47 }
48 } // namespace
49
Push(const char * data,size_t size)50 int64_t InternTable::Push(const char* data, size_t size) {
51 std::string_view outer(data, size);
52
53 for (size_t i = 0; i < interned_comms_.size(); ++i) {
54 auto view = interned_comms_[i];
55
56 if (view == outer) {
57 return static_cast<int64_t>(i);
58 }
59 }
60
61 // No room for the new string, reject the request.
62 if (comms_length_ + size > comms_.size()) {
63 return -1;
64 }
65
66 auto* head = comms_.data() + comms_length_;
67
68 // Important note, the null byte is not copied.
69 memcpy(head, data, size);
70 comms_length_ += size;
71
72 size_t id = interned_comms_.size();
73 interned_comms_.emplace_back(head, size);
74
75 return static_cast<int64_t>(id);
76 }
77
Find(size_t index) const78 std::string_view InternTable::Find(size_t index) const {
79 if (index < interned_comms_.size()) {
80 return interned_comms_[index];
81 }
82
83 return {};
84 }
85
86 // Redact sched switch trace events in an ftrace event bundle:
87 //
88 // event {
89 // timestamp: 6702093744772646
90 // pid: 0
91 // sched_switch {
92 // prev_comm: "swapper/0"
93 // prev_pid: 0
94 // prev_prio: 120
95 // prev_state: 0
96 // next_comm: "writer"
97 // next_pid: 23020
98 // next_prio: 96
99 // }
100 // }
101 //
102 // In the above message, it should be noted that "event.pid" will always be
103 // equal to "event.sched_switch.prev_pid".
104 //
105 // "ftrace_event_bundle_message" is the ftrace event bundle (contains a
106 // collection of ftrace event messages) because data in a sched_switch message
107 // is needed in order to know if the event should be added to the bundle.
108
Transform(const Context & context,std::string * packet) const109 base::Status RedactSchedEvents::Transform(const Context& context,
110 std::string* packet) const {
111 PERFETTO_DCHECK(modifier_);
112 PERFETTO_DCHECK(waking_filter_);
113
114 if (!context.timeline) {
115 return base::ErrStatus("RedactSchedEvents: missing timeline.");
116 }
117
118 if (!context.package_uid.has_value()) {
119 return base::ErrStatus("RedactSchedEvents: missing package uid.");
120 }
121
122 if (!packet || packet->empty()) {
123 return base::ErrStatus("RedactSchedEvents: null or empty packet.");
124 }
125
126 protozero::HeapBuffered<protos::pbzero::TracePacket> message;
127 protozero::ProtoDecoder decoder(*packet);
128
129 for (auto field = decoder.ReadField(); field.valid();
130 field = decoder.ReadField()) {
131 if (field.id() == protos::pbzero::TracePacket::kFtraceEventsFieldNumber) {
132 RETURN_IF_ERROR(
133 OnFtraceEvents(context, field, message->set_ftrace_events()));
134 } else {
135 proto_util::AppendField(field, message.get());
136 }
137 }
138
139 packet->assign(message.SerializeAsString());
140
141 return base::OkStatus();
142 }
143
OnFtraceEvents(const Context & context,protozero::Field ftrace_events,protos::pbzero::FtraceEventBundle * message) const144 base::Status RedactSchedEvents::OnFtraceEvents(
145 const Context& context,
146 protozero::Field ftrace_events,
147 protos::pbzero::FtraceEventBundle* message) const {
148 PERFETTO_DCHECK(ftrace_events.id() ==
149 protos::pbzero::TracePacket::kFtraceEventsFieldNumber);
150
151 protozero::ProtoDecoder decoder(ftrace_events.as_bytes());
152
153 auto cpu =
154 decoder.FindField(protos::pbzero::FtraceEventBundle::kCpuFieldNumber);
155 if (!cpu.valid()) {
156 return base::ErrStatus(
157 "RedactSchedEvents: missing cpu in ftrace event bundle.");
158 }
159
160 for (auto field = decoder.ReadField(); field.valid();
161 field = decoder.ReadField()) {
162 if (field.id() == protos::pbzero::FtraceEventBundle::kEventFieldNumber) {
163 RETURN_IF_ERROR(
164 OnFtraceEvent(context, cpu.as_int32(), field, message->add_event()));
165 continue;
166 }
167
168 if (field.id() ==
169 protos::pbzero::FtraceEventBundle::kCompactSchedFieldNumber) {
170 protos::pbzero::FtraceEventBundle::CompactSched::Decoder comp_sched(
171 field.as_bytes());
172 RETURN_IF_ERROR(OnCompSched(context, cpu.as_int32(), comp_sched,
173 message->set_compact_sched()));
174 continue;
175 }
176
177 proto_util::AppendField(field, message);
178 }
179
180 return base::OkStatus();
181 }
182
OnFtraceEvent(const Context & context,int32_t cpu,protozero::Field ftrace_event,protos::pbzero::FtraceEvent * message) const183 base::Status RedactSchedEvents::OnFtraceEvent(
184 const Context& context,
185 int32_t cpu,
186 protozero::Field ftrace_event,
187 protos::pbzero::FtraceEvent* message) const {
188 PERFETTO_DCHECK(ftrace_event.id() ==
189 protos::pbzero::FtraceEventBundle::kEventFieldNumber);
190
191 protozero::ProtoDecoder decoder(ftrace_event.as_bytes());
192
193 auto ts =
194 decoder.FindField(protos::pbzero::FtraceEvent::kTimestampFieldNumber);
195 if (!ts.valid()) {
196 return base::ErrStatus(
197 "RedactSchedEvents: missing timestamp in ftrace event.");
198 }
199
200 std::string scratch_str;
201
202 for (auto field = decoder.ReadField(); field.valid();
203 field = decoder.ReadField()) {
204 switch (field.id()) {
205 case protos::pbzero::FtraceEvent::kSchedSwitchFieldNumber: {
206 protos::pbzero::SchedSwitchFtraceEvent::Decoder sched_switch(
207 field.as_bytes());
208 RETURN_IF_ERROR(OnFtraceEventSwitch(context, ts.as_uint64(), cpu,
209 sched_switch, &scratch_str,
210 message->set_sched_switch()));
211 break;
212 }
213
214 case protos::pbzero::FtraceEvent::kSchedWakingFieldNumber: {
215 protos::pbzero::SchedWakingFtraceEvent::Decoder sched_waking(
216 field.as_bytes());
217 RETURN_IF_ERROR(OnFtraceEventWaking(
218 context, ts.as_uint64(), cpu, sched_waking, &scratch_str, message));
219 break;
220 }
221
222 default: {
223 proto_util::AppendField(field, message);
224 break;
225 }
226 }
227 }
228
229 return base::OkStatus();
230 }
231
OnFtraceEventSwitch(const Context & context,uint64_t ts,int32_t cpu,protos::pbzero::SchedSwitchFtraceEvent::Decoder & sched_switch,std::string * scratch_str,protos::pbzero::SchedSwitchFtraceEvent * message) const232 base::Status RedactSchedEvents::OnFtraceEventSwitch(
233 const Context& context,
234 uint64_t ts,
235 int32_t cpu,
236 protos::pbzero::SchedSwitchFtraceEvent::Decoder& sched_switch,
237 std::string* scratch_str,
238 protos::pbzero::SchedSwitchFtraceEvent* message) const {
239 PERFETTO_DCHECK(modifier_);
240 PERFETTO_DCHECK(scratch_str);
241 PERFETTO_DCHECK(message);
242
243 std::array<bool, 7> has_fields = {
244 sched_switch.has_prev_comm(), sched_switch.has_prev_pid(),
245 sched_switch.has_prev_prio(), sched_switch.has_prev_state(),
246 sched_switch.has_next_comm(), sched_switch.has_next_pid(),
247 sched_switch.has_next_prio()};
248
249 if (!std::all_of(has_fields.begin(), has_fields.end(), IsTrue)) {
250 return base::ErrStatus(
251 "RedactSchedEvents: missing required SchedSwitchFtraceEvent "
252 "field.");
253 }
254
255 auto prev_pid = sched_switch.prev_pid();
256 auto prev_comm = sched_switch.prev_comm();
257
258 auto next_pid = sched_switch.next_pid();
259 auto next_comm = sched_switch.next_comm();
260
261 // There are 7 values in a sched switch message. Since 4 of the 7 can be
262 // replaced, it is easier/cleaner to go value-by-value. Go in proto-defined
263 // order.
264
265 scratch_str->assign(prev_comm.data, prev_comm.size);
266
267 modifier_->Modify(context, ts, cpu, &prev_pid, scratch_str);
268
269 message->set_prev_comm(*scratch_str); // FieldNumber = 1
270 message->set_prev_pid(prev_pid); // FieldNumber = 2
271 message->set_prev_prio(sched_switch.prev_prio()); // FieldNumber = 3
272 message->set_prev_state(sched_switch.prev_state()); // FieldNumber = 4
273
274 scratch_str->assign(next_comm.data, next_comm.size);
275
276 modifier_->Modify(context, ts, cpu, &next_pid, scratch_str);
277
278 message->set_next_comm(*scratch_str); // FieldNumber = 5
279 message->set_next_pid(next_pid); // FieldNumber = 6
280 message->set_next_prio(sched_switch.next_prio()); // FieldNumber = 7
281
282 return base::OkStatus();
283 }
284
285 // Redact sched waking trace events in a ftrace event bundle:
286 //
287 // event {
288 // timestamp: 6702093787823849
289 // pid: 814 <-- waker
290 // sched_waking {
291 // comm: "surfaceflinger"
292 // pid: 756 <-- target
293 // prio: 97
294 // success: 1
295 // target_cpu: 2
296 // }
297 // }
OnFtraceEventWaking(const Context & context,uint64_t ts,int32_t cpu,protos::pbzero::SchedWakingFtraceEvent::Decoder & sched_waking,std::string * scratch_str,protos::pbzero::FtraceEvent * parent_message) const298 base::Status RedactSchedEvents::OnFtraceEventWaking(
299 const Context& context,
300 uint64_t ts,
301 int32_t cpu,
302 protos::pbzero::SchedWakingFtraceEvent::Decoder& sched_waking,
303 std::string* scratch_str,
304 protos::pbzero::FtraceEvent* parent_message) const {
305 PERFETTO_DCHECK(modifier_);
306 PERFETTO_DCHECK(scratch_str);
307 PERFETTO_DCHECK(parent_message);
308
309 std::array<bool, 5> has_fields = {
310 sched_waking.has_comm(), sched_waking.has_pid(), sched_waking.has_prio(),
311 sched_waking.has_success(), sched_waking.has_target_cpu()};
312
313 if (!std::all_of(has_fields.begin(), has_fields.end(), IsTrue)) {
314 return base::ErrStatus(
315 "RedactSchedEvents: missing required SchedWakingFtraceEvent "
316 "field.");
317 }
318
319 auto pid = sched_waking.pid();
320
321 if (!waking_filter_->Includes(context, ts, pid)) {
322 return base::OkStatus();
323 }
324
325 auto comm = sched_waking.comm();
326
327 // There are 5 values in a sched switch message. Since 2 of the 5 can be
328 // replaced, it is easier/cleaner to go value-by-value. Go in proto-defined
329 // order.
330
331 scratch_str->assign(comm.data, comm.size);
332
333 modifier_->Modify(context, ts, cpu, &pid, scratch_str);
334
335 auto message = parent_message->set_sched_waking();
336 message->set_comm(*scratch_str); // FieldNumber = 1
337 message->set_pid(pid); // FieldNumber = 2
338 message->set_prio(sched_waking.prio()); // FieldNumber = 3
339 message->set_success(sched_waking.success()); // FieldNumber = 4
340 message->set_target_cpu(sched_waking.target_cpu()); // FieldNumber = 5
341
342 return base::OkStatus();
343 }
344
OnCompSched(const Context & context,int32_t cpu,protos::pbzero::FtraceEventBundle::CompactSched::Decoder & comp_sched,protos::pbzero::FtraceEventBundle::CompactSched * message) const345 base::Status RedactSchedEvents::OnCompSched(
346 const Context& context,
347 int32_t cpu,
348 protos::pbzero::FtraceEventBundle::CompactSched::Decoder& comp_sched,
349 protos::pbzero::FtraceEventBundle::CompactSched* message) const {
350 // Populate the intern table once; it will be used by both sched and waking.
351 InternTable intern_table;
352
353 for (auto it = comp_sched.intern_table(); it; ++it) {
354 auto chars = it->as_string();
355 auto index = intern_table.Push(chars.data, chars.size);
356
357 if (index < 0) {
358 return base::ErrStatus(
359 "RedactSchedEvents: failed to insert string into intern "
360 "table.");
361 }
362 }
363
364 std::array<bool, 5> has_switch_fields = {
365 comp_sched.has_switch_timestamp(),
366 comp_sched.has_switch_prev_state(),
367 comp_sched.has_switch_next_pid(),
368 comp_sched.has_switch_next_prio(),
369 comp_sched.has_switch_next_comm_index(),
370 };
371
372 if (std::any_of(has_switch_fields.begin(), has_switch_fields.end(), IsTrue)) {
373 RETURN_IF_ERROR(
374 OnCompSchedSwitch(context, cpu, comp_sched, &intern_table, message));
375 }
376
377 std::array<bool, 6> has_waking_fields = {
378 comp_sched.has_waking_timestamp(), comp_sched.has_waking_pid(),
379 comp_sched.has_waking_target_cpu(), comp_sched.has_waking_prio(),
380 comp_sched.has_waking_comm_index(), comp_sched.has_waking_common_flags(),
381 };
382
383 if (std::any_of(has_waking_fields.begin(), has_waking_fields.end(), IsTrue)) {
384 RETURN_IF_ERROR(
385 OnCompactSchedWaking(context, comp_sched, &intern_table, message));
386 }
387
388 // IMPORTANT: The intern table can only be added after switch and waking
389 // because switch and/or waking can/will modify the intern table.
390 for (auto view : intern_table.values()) {
391 message->add_intern_table(view.data(), view.size());
392 }
393
394 return base::OkStatus();
395 }
396
OnCompSchedSwitch(const Context & context,int32_t cpu,protos::pbzero::FtraceEventBundle::CompactSched::Decoder & comp_sched,InternTable * intern_table,protos::pbzero::FtraceEventBundle::CompactSched * message) const397 base::Status RedactSchedEvents::OnCompSchedSwitch(
398 const Context& context,
399 int32_t cpu,
400 protos::pbzero::FtraceEventBundle::CompactSched::Decoder& comp_sched,
401 InternTable* intern_table,
402 protos::pbzero::FtraceEventBundle::CompactSched* message) const {
403 PERFETTO_DCHECK(modifier_);
404 PERFETTO_DCHECK(message);
405
406 std::array<bool, 6> has_fields = {
407 comp_sched.has_intern_table(),
408 comp_sched.has_switch_timestamp(),
409 comp_sched.has_switch_prev_state(),
410 comp_sched.has_switch_next_pid(),
411 comp_sched.has_switch_next_prio(),
412 comp_sched.has_switch_next_comm_index(),
413 };
414
415 if (!std::all_of(has_fields.begin(), has_fields.end(), IsTrue)) {
416 return base::ErrStatus(
417 "RedactSchedEvents: missing required FtraceEventBundle::CompactSched "
418 "switch field.");
419 }
420
421 std::string scratch_str;
422
423 protozero::PackedVarInt packed_comm;
424 protozero::PackedVarInt packed_pid;
425
426 // The first it_ts value is an absolute value, all other values are delta
427 // values.
428 uint64_t ts = 0;
429
430 std::array<bool, 3> parse_errors = {false, false, false};
431
432 auto it_ts = comp_sched.switch_timestamp(&parse_errors.at(0));
433 auto it_pid = comp_sched.switch_next_pid(&parse_errors.at(1));
434 auto it_comm = comp_sched.switch_next_comm_index(&parse_errors.at(2));
435
436 while (it_ts && it_pid && it_comm) {
437 ts += *it_ts;
438
439 auto pid = *it_pid;
440
441 auto comm_index = *it_comm;
442 auto comm = intern_table->Find(comm_index);
443
444 scratch_str.assign(comm);
445
446 modifier_->Modify(context, ts, cpu, &pid, &scratch_str);
447
448 auto found = intern_table->Push(scratch_str.data(), scratch_str.size());
449
450 if (found < 0) {
451 return base::ErrStatus(
452 "RedactSchedEvents: failed to insert string into intern table.");
453 }
454
455 packed_comm.Append(found);
456 packed_pid.Append(pid);
457
458 ++it_ts;
459 ++it_pid;
460 ++it_comm;
461 }
462
463 if (std::any_of(parse_errors.begin(), parse_errors.end(), IsTrue)) {
464 return base::ErrStatus(
465 "RedactSchedEvents: error reading FtraceEventBundle::CompactSched.");
466 }
467
468 if (it_ts || it_pid || it_comm) {
469 return base::ErrStatus(
470 "RedactSchedEvents: uneven associative arrays in "
471 "FtraceEventBundle::CompactSched (switch).");
472 }
473
474 message->set_switch_next_pid(packed_pid);
475 message->set_switch_next_comm_index(packed_comm);
476
477 // There's a lot of data in a compact sched message. Most of it is packed data
478 // and most of the data is not going to change. To avoid unpacking, doing
479 // nothing, and then packing... cheat. Find the fields and pass them as opaque
480 // blobs.
481 //
482 // kInternTableFieldNumber: The intern table will be modified by both
483 // switch events and waking events. It will
484 // be written elsewhere.
485 //
486 // kSwitchNextPidFieldNumber: The switch pid will change during thread
487 // merging.
488 //
489 // kSwitchNextCommIndexFieldNumber: The switch comm value will change when
490 // clearing thread names and replaced
491 // during thread merging.
492
493 auto passed_through = {
494 Passthrough(comp_sched,
495 protos::pbzero::FtraceEventBundle::CompactSched::
496 kSwitchTimestampFieldNumber,
497 message),
498 Passthrough(comp_sched,
499 protos::pbzero::FtraceEventBundle::CompactSched::
500 kSwitchPrevStateFieldNumber,
501 message),
502 Passthrough(comp_sched,
503 protos::pbzero::FtraceEventBundle::CompactSched::
504 kSwitchNextPrioFieldNumber,
505 message)};
506
507 if (!std::all_of(passed_through.begin(), passed_through.end(), IsTrue)) {
508 return base::ErrStatus(
509 "RedactSchedEvents: missing required "
510 "FtraceEventBundle::CompactSched switch field.");
511 }
512
513 return base::OkStatus();
514 }
515
OnCompactSchedWaking(const Context & context,protos::pbzero::FtraceEventBundle::CompactSched::Decoder & compact_sched,InternTable * intern_table,protos::pbzero::FtraceEventBundle::CompactSched * compact_sched_message) const516 base::Status RedactSchedEvents::OnCompactSchedWaking(
517 const Context& context,
518 protos::pbzero::FtraceEventBundle::CompactSched::Decoder& compact_sched,
519 InternTable* intern_table,
520 protos::pbzero::FtraceEventBundle::CompactSched* compact_sched_message)
521 const {
522 protozero::PackedVarInt var_comm_index;
523 protozero::PackedVarInt var_common_flags;
524 protozero::PackedVarInt var_pid;
525 protozero::PackedVarInt var_prio;
526 protozero::PackedVarInt var_target_cpu;
527 protozero::PackedVarInt var_timestamp;
528
529 // Time is expressed as delta time, for example:
530 //
531 // Event: A B C D
532 // Absolute Time: 20 30 35 41
533 // | | | |
534 // Delta Time: 20 10 5 6
535 //
536 // When an event is removed, for example, event B, delta times are off:
537 //
538 // Event: A * C D
539 // Absolute Time: 20 30 35 41
540 // | | | |
541 // Delta Time: 20 * 5 6
542 // | | |
543 // Effective Abs. Time: 20 25 31
544 // Error: 0 10 10
545 //
546 // To address this issue, delta times are added into a bucket. The bucket is
547 // drained each time an event is retained. If an event is dropped, its time
548 // is added to the bucket, but the bucket won't be drained until a retained
549 // event drains it.
550 uint64_t ts_bucket = 0;
551 uint64_t ts_absolute = 0;
552
553 std::string comm;
554
555 std::array<bool, 7> parse_errors = {!compact_sched.has_intern_table(),
556 false,
557 false,
558 false,
559 false,
560 false,
561 false};
562
563 // A note on readability, because the waking iterators are the primary focus,
564 // they won't have a "waking" prefix.
565 auto it_comm_index = compact_sched.waking_comm_index(&parse_errors.at(1));
566 auto it_common_flags = compact_sched.waking_common_flags(&parse_errors.at(2));
567 auto it_pid = compact_sched.waking_pid(&parse_errors.at(3));
568 auto it_prio = compact_sched.waking_prio(&parse_errors.at(4));
569 auto it_target_cpu = compact_sched.waking_target_cpu(&parse_errors.at(5));
570 auto it_timestamp = compact_sched.waking_timestamp(&parse_errors.at(6));
571
572 while (it_comm_index && it_common_flags && it_pid && it_prio &&
573 it_target_cpu && it_timestamp) {
574 ts_bucket += *it_timestamp; // add time to the bucket
575 ts_absolute += *it_timestamp;
576
577 if (waking_filter_->Includes(context, ts_absolute, *it_pid)) {
578 // Now that the waking event will be kept, it can be modified using the
579 // same rules as switch events.
580 auto pid = *it_pid;
581 comm.assign(intern_table->Find(*it_comm_index));
582 modifier_->Modify(context, ts_absolute, *it_target_cpu, &pid, &comm);
583
584 auto comm_it = intern_table->Push(comm.data(), comm.size());
585
586 var_comm_index.Append(comm_it);
587 var_common_flags.Append(*it_common_flags);
588 var_pid.Append(pid);
589 var_prio.Append(*it_prio);
590 var_target_cpu.Append(*it_target_cpu);
591 var_timestamp.Append(ts_bucket);
592
593 ts_bucket = 0; // drain the whole bucket.
594 }
595
596 ++it_comm_index;
597 ++it_common_flags;
598 ++it_pid;
599 ++it_prio;
600 ++it_target_cpu;
601 ++it_timestamp;
602 }
603
604 if (std::any_of(parse_errors.begin(), parse_errors.end(), IsTrue)) {
605 return base::ErrStatus(
606 "RedactSchedEvents: failed to parse FtraceEventBundle::CompactSched.");
607 }
608
609 if (it_comm_index || it_common_flags || it_pid || it_prio || it_target_cpu ||
610 it_timestamp) {
611 return base::ErrStatus(
612 "RedactSchedEvents: uneven associative arrays in "
613 "FtraceEventBundle::CompactSched (waking).");
614 }
615
616 compact_sched_message->set_waking_comm_index(var_comm_index);
617 compact_sched_message->set_waking_common_flags(var_common_flags);
618 compact_sched_message->set_waking_pid(var_pid);
619 compact_sched_message->set_waking_prio(var_prio);
620 compact_sched_message->set_waking_target_cpu(var_target_cpu);
621 compact_sched_message->set_waking_timestamp(var_timestamp);
622
623 return base::OkStatus();
624 }
625
626 } // namespace perfetto::trace_redaction
627