1 /*
2 * Copyright (C) 2021 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_processor/util/proto_to_args_parser.h"
18
19 #include "perfetto/ext/base/string_view.h"
20 #include "perfetto/protozero/scattered_heap_buffer.h"
21 #include "perfetto/trace_processor/trace_blob.h"
22 #include "perfetto/trace_processor/trace_blob_view.h"
23 #include "protos/perfetto/common/descriptor.pbzero.h"
24 #include "protos/perfetto/trace/track_event/source_location.pbzero.h"
25 #include "src/protozero/test/example_proto/test_messages.pbzero.h"
26 #include "src/trace_processor/test_messages.descriptor.h"
27 #include "src/trace_processor/util/interned_message_view.h"
28 #include "test/gtest_and_gmock.h"
29
30 #include <sstream>
31
32 namespace perfetto {
33 namespace trace_processor {
34 namespace util {
35 namespace {
36
37 constexpr size_t kChunkSize = 42;
38
ToChars(const char * str)39 protozero::ConstChars ToChars(const char* str) {
40 return protozero::ConstChars{str, strlen(str)};
41 }
42
43 class ProtoToArgsParserTest : public ::testing::Test,
44 public ProtoToArgsParser::Delegate {
45 protected:
ProtoToArgsParserTest()46 ProtoToArgsParserTest() {}
47
args() const48 const std::vector<std::string>& args() const { return args_; }
49
AddInternedSourceLocation(uint64_t iid,TraceBlobView data)50 void AddInternedSourceLocation(uint64_t iid, TraceBlobView data) {
51 interned_source_locations_[iid] = std::unique_ptr<InternedMessageView>(
52 new InternedMessageView(std::move(data)));
53 }
54
55 private:
56 using Key = ProtoToArgsParser::Key;
57
AddInteger(const Key & key,int64_t value)58 void AddInteger(const Key& key, int64_t value) override {
59 std::stringstream ss;
60 ss << key.flat_key << " " << key.key << " " << value;
61 args_.push_back(ss.str());
62 }
63
AddUnsignedInteger(const Key & key,uint64_t value)64 void AddUnsignedInteger(const Key& key, uint64_t value) override {
65 std::stringstream ss;
66 ss << key.flat_key << " " << key.key << " " << value;
67 args_.push_back(ss.str());
68 }
69
AddString(const Key & key,const protozero::ConstChars & value)70 void AddString(const Key& key, const protozero::ConstChars& value) override {
71 std::stringstream ss;
72 ss << key.flat_key << " " << key.key << " " << value.ToStdString();
73 args_.push_back(ss.str());
74 }
75
AddDouble(const Key & key,double value)76 void AddDouble(const Key& key, double value) override {
77 std::stringstream ss;
78 ss << key.flat_key << " " << key.key << " " << value;
79 args_.push_back(ss.str());
80 }
81
AddPointer(const Key & key,const void * value)82 void AddPointer(const Key& key, const void* value) override {
83 std::stringstream ss;
84 ss << key.flat_key << " " << key.key << " " << std::hex
85 << reinterpret_cast<uintptr_t>(value) << std::dec;
86 args_.push_back(ss.str());
87 }
88
AddBoolean(const Key & key,bool value)89 void AddBoolean(const Key& key, bool value) override {
90 std::stringstream ss;
91 ss << key.flat_key << " " << key.key << " " << (value ? "true" : "false");
92 args_.push_back(ss.str());
93 }
94
AddJson(const Key & key,const protozero::ConstChars & value)95 bool AddJson(const Key& key, const protozero::ConstChars& value) override {
96 std::stringstream ss;
97 ss << key.flat_key << " " << key.key << " " << std::hex
98 << value.ToStdString() << std::dec;
99 args_.push_back(ss.str());
100 return true;
101 }
102
AddNull(const Key & key)103 void AddNull(const Key& key) override {
104 std::stringstream ss;
105 ss << key.flat_key << " " << key.key << " [NULL]";
106 args_.push_back(ss.str());
107 }
108
GetArrayEntryIndex(const std::string &)109 size_t GetArrayEntryIndex(const std::string&) final { return 0; }
110
IncrementArrayEntryIndex(const std::string &)111 size_t IncrementArrayEntryIndex(const std::string&) final { return 0; }
112
GetInternedMessageView(uint32_t field_id,uint64_t iid)113 InternedMessageView* GetInternedMessageView(uint32_t field_id,
114 uint64_t iid) override {
115 if (field_id != protos::pbzero::InternedData::kSourceLocationsFieldNumber)
116 return nullptr;
117 return interned_source_locations_.at(iid).get();
118 }
119
120 std::vector<std::string> args_;
121 std::map<uint64_t, std::unique_ptr<InternedMessageView>>
122 interned_source_locations_;
123 };
124
TEST_F(ProtoToArgsParserTest,EnsureTestMessageProtoParses)125 TEST_F(ProtoToArgsParserTest, EnsureTestMessageProtoParses) {
126 DescriptorPool pool;
127 auto status = pool.AddFromFileDescriptorSet(kTestMessagesDescriptor.data(),
128 kTestMessagesDescriptor.size());
129 ProtoToArgsParser parser(pool);
130 EXPECT_TRUE(status.ok()) << "Failed to parse kTestMessagesDescriptor: "
131 << status.message();
132 }
133
TEST_F(ProtoToArgsParserTest,BasicSingleLayerProto)134 TEST_F(ProtoToArgsParserTest, BasicSingleLayerProto) {
135 using namespace protozero::test::protos::pbzero;
136 protozero::HeapBuffered<EveryField> msg{kChunkSize, kChunkSize};
137 msg->set_field_int32(-1);
138 msg->set_field_int64(-333123456789ll);
139 msg->set_field_uint32(600);
140 msg->set_field_uint64(333123456789ll);
141 msg->set_field_sint32(-5);
142 msg->set_field_sint64(-9000);
143 msg->set_field_fixed32(12345);
144 msg->set_field_fixed64(444123450000ll);
145 msg->set_field_sfixed32(-69999);
146 msg->set_field_sfixed64(-200);
147 msg->set_field_double(0.5555);
148 msg->set_field_bool(true);
149 msg->set_small_enum(SmallEnum::TO_BE);
150 msg->set_signed_enum(SignedEnum::NEGATIVE);
151 msg->set_big_enum(BigEnum::BEGIN);
152 msg->set_nested_enum(EveryField::PONG);
153 msg->set_field_float(3.14f);
154 msg->set_field_string("FizzBuzz");
155 msg->add_repeated_int32(1);
156 msg->add_repeated_int32(-1);
157 msg->add_repeated_int32(100);
158 msg->add_repeated_int32(2000000);
159
160 auto binary_proto = msg.SerializeAsArray();
161
162 DescriptorPool pool;
163 auto status = pool.AddFromFileDescriptorSet(kTestMessagesDescriptor.data(),
164 kTestMessagesDescriptor.size());
165 ProtoToArgsParser parser(pool);
166 ASSERT_TRUE(status.ok()) << "Failed to parse kTestMessagesDescriptor: "
167 << status.message();
168
169 status = parser.ParseMessage(
170 protozero::ConstBytes{binary_proto.data(), binary_proto.size()},
171 ".protozero.test.protos.EveryField", nullptr, *this);
172
173 EXPECT_TRUE(status.ok())
174 << "InternProtoFieldsIntoArgsTable failed with error: "
175 << status.message();
176
177 EXPECT_THAT(
178 args(),
179 testing::ElementsAre(
180 "field_int32 field_int32 -1", "field_int64 field_int64 -333123456789",
181 "field_uint32 field_uint32 600",
182 "field_uint64 field_uint64 333123456789",
183 "field_sint32 field_sint32 -5", "field_sint64 field_sint64 -9000",
184 "field_fixed32 field_fixed32 12345",
185 "field_fixed64 field_fixed64 444123450000",
186 "field_sfixed32 field_sfixed32 -69999",
187 "field_sfixed64 field_sfixed64 -200",
188 "field_double field_double 0.5555", "field_bool field_bool true",
189 "small_enum small_enum TO_BE", "signed_enum signed_enum NEGATIVE",
190 "big_enum big_enum BEGIN", "nested_enum nested_enum PONG",
191 "field_float field_float 3.14", "field_string field_string FizzBuzz",
192 "repeated_int32 repeated_int32[0] 1",
193 "repeated_int32 repeated_int32[1] -1",
194 "repeated_int32 repeated_int32[2] 100",
195 "repeated_int32 repeated_int32[3] 2000000"));
196 }
197
TEST_F(ProtoToArgsParserTest,NestedProto)198 TEST_F(ProtoToArgsParserTest, NestedProto) {
199 using namespace protozero::test::protos::pbzero;
200 protozero::HeapBuffered<NestedA> msg{kChunkSize, kChunkSize};
201 msg->set_super_nested()->set_value_c(3);
202
203 auto binary_proto = msg.SerializeAsArray();
204
205 DescriptorPool pool;
206 auto status = pool.AddFromFileDescriptorSet(kTestMessagesDescriptor.data(),
207 kTestMessagesDescriptor.size());
208 ProtoToArgsParser parser(pool);
209 ASSERT_TRUE(status.ok()) << "Failed to parse kTestMessagesDescriptor: "
210 << status.message();
211
212 status = parser.ParseMessage(
213 protozero::ConstBytes{binary_proto.data(), binary_proto.size()},
214 ".protozero.test.protos.NestedA", nullptr, *this);
215 EXPECT_TRUE(status.ok())
216 << "InternProtoFieldsIntoArgsTable failed with error: "
217 << status.message();
218 EXPECT_THAT(args(), testing::ElementsAre(
219 "super_nested.value_c super_nested.value_c 3"));
220 }
221
TEST_F(ProtoToArgsParserTest,CamelCaseFieldsProto)222 TEST_F(ProtoToArgsParserTest, CamelCaseFieldsProto) {
223 using namespace protozero::test::protos::pbzero;
224 protozero::HeapBuffered<CamelCaseFields> msg{kChunkSize, kChunkSize};
225 msg->set_barbaz(true);
226 msg->set_moomoo(true);
227 msg->set___bigbang(true);
228
229 auto binary_proto = msg.SerializeAsArray();
230
231 DescriptorPool pool;
232 auto status = pool.AddFromFileDescriptorSet(kTestMessagesDescriptor.data(),
233 kTestMessagesDescriptor.size());
234 ProtoToArgsParser parser(pool);
235 ASSERT_TRUE(status.ok()) << "Failed to parse kTestMessagesDescriptor: "
236 << status.message();
237
238 status = parser.ParseMessage(
239 protozero::ConstBytes{binary_proto.data(), binary_proto.size()},
240 ".protozero.test.protos.CamelCaseFields", nullptr, *this);
241 EXPECT_TRUE(status.ok())
242 << "InternProtoFieldsIntoArgsTable failed with error: "
243 << status.message();
244 EXPECT_THAT(args(),
245 testing::ElementsAre("barBaz barBaz true", "MooMoo MooMoo true",
246 "__bigBang __bigBang true"));
247 }
248
TEST_F(ProtoToArgsParserTest,NestedProtoParsingOverrideHandled)249 TEST_F(ProtoToArgsParserTest, NestedProtoParsingOverrideHandled) {
250 using namespace protozero::test::protos::pbzero;
251 protozero::HeapBuffered<NestedA> msg{kChunkSize, kChunkSize};
252 msg->set_super_nested()->set_value_c(3);
253
254 auto binary_proto = msg.SerializeAsArray();
255
256 DescriptorPool pool;
257 auto status = pool.AddFromFileDescriptorSet(kTestMessagesDescriptor.data(),
258 kTestMessagesDescriptor.size());
259 ProtoToArgsParser parser(pool);
260 ASSERT_TRUE(status.ok()) << "Failed to parse kTestMessagesDescriptor: "
261 << status.message();
262
263 parser.AddParsingOverrideForField(
264 "super_nested.value_c",
265 [](const protozero::Field& field, ProtoToArgsParser::Delegate& writer) {
266 EXPECT_EQ(field.type(), protozero::proto_utils::ProtoWireType::kVarInt);
267 std::string key = "super_nested.value_b.replaced";
268 writer.AddInteger({key, key}, field.as_int32());
269 // We've handled this field by adding the desired args.
270 return base::OkStatus();
271 });
272
273 status = parser.ParseMessage(
274 protozero::ConstBytes{binary_proto.data(), binary_proto.size()},
275 ".protozero.test.protos.NestedA", nullptr, *this);
276 EXPECT_TRUE(status.ok())
277 << "InternProtoFieldsIntoArgsTable failed with error: "
278 << status.message();
279 EXPECT_THAT(
280 args(),
281 testing::ElementsAre(
282 "super_nested.value_b.replaced super_nested.value_b.replaced 3"));
283 }
284
TEST_F(ProtoToArgsParserTest,NestedProtoParsingOverrideSkipped)285 TEST_F(ProtoToArgsParserTest, NestedProtoParsingOverrideSkipped) {
286 using namespace protozero::test::protos::pbzero;
287 protozero::HeapBuffered<NestedA> msg{kChunkSize, kChunkSize};
288 msg->set_super_nested()->set_value_c(3);
289
290 auto binary_proto = msg.SerializeAsArray();
291
292 DescriptorPool pool;
293 auto status = pool.AddFromFileDescriptorSet(kTestMessagesDescriptor.data(),
294 kTestMessagesDescriptor.size());
295 ProtoToArgsParser parser(pool);
296 ASSERT_TRUE(status.ok()) << "Failed to parse kTestMessagesDescriptor: "
297 << status.message();
298
299 parser.AddParsingOverrideForField(
300 "super_nested.value_c",
301 [](const protozero::Field& field, ProtoToArgsParser::Delegate&) {
302 static int val = 0;
303 ++val;
304 EXPECT_EQ(1, val);
305 EXPECT_EQ(field.type(), protozero::proto_utils::ProtoWireType::kVarInt);
306 return base::nullopt;
307 });
308
309 status = parser.ParseMessage(
310 protozero::ConstBytes{binary_proto.data(), binary_proto.size()},
311 ".protozero.test.protos.NestedA", nullptr, *this);
312 EXPECT_TRUE(status.ok())
313 << "InternProtoFieldsIntoArgsTable failed with error: "
314 << status.message();
315 EXPECT_THAT(args(), testing::ElementsAre(
316 "super_nested.value_c super_nested.value_c 3"));
317 }
318
TEST_F(ProtoToArgsParserTest,LookingUpInternedStateParsingOverride)319 TEST_F(ProtoToArgsParserTest, LookingUpInternedStateParsingOverride) {
320 using namespace protozero::test::protos::pbzero;
321 // The test proto, we will use |value_c| as the source_location iid.
322 protozero::HeapBuffered<NestedA> msg{kChunkSize, kChunkSize};
323 msg->set_super_nested()->set_value_c(3);
324 auto binary_proto = msg.SerializeAsArray();
325
326 // The interned source location.
327 protozero::HeapBuffered<protos::pbzero::SourceLocation> src_loc{kChunkSize,
328 kChunkSize};
329 const uint64_t kIid = 3;
330 src_loc->set_iid(kIid);
331 src_loc->set_file_name("test_file_name");
332 // We need to update sequence_state to point to it.
333 auto binary_data = src_loc.SerializeAsArray();
334 std::unique_ptr<uint8_t[]> buffer(new uint8_t[binary_data.size()]);
335 for (size_t i = 0; i < binary_data.size(); ++i) {
336 buffer.get()[i] = binary_data[i];
337 }
338 TraceBlob blob =
339 TraceBlob::TakeOwnership(std::move(buffer), binary_data.size());
340 AddInternedSourceLocation(kIid, TraceBlobView(std::move(blob)));
341
342 DescriptorPool pool;
343 auto status = pool.AddFromFileDescriptorSet(kTestMessagesDescriptor.data(),
344 kTestMessagesDescriptor.size());
345 ASSERT_TRUE(status.ok()) << "Failed to parse kTestMessagesDescriptor: "
346 << status.message();
347
348 ProtoToArgsParser parser(pool);
349 // Now we override the behaviour of |value_c| so we can expand the iid into
350 // multiple args rows.
351 parser.AddParsingOverrideForField(
352 "super_nested.value_c",
353 [](const protozero::Field& field, ProtoToArgsParser::Delegate& delegate)
354 -> base::Optional<base::Status> {
355 auto* decoder = delegate.GetInternedMessage(
356 protos::pbzero::InternedData::kSourceLocations, field.as_uint64());
357 if (!decoder) {
358 // Lookup failed fall back on default behaviour.
359 return base::nullopt;
360 }
361 delegate.AddString(ProtoToArgsParser::Key("file_name"),
362 protozero::ConstChars{"file", 4});
363 delegate.AddInteger(ProtoToArgsParser::Key("line_number"), 2);
364 return base::OkStatus();
365 });
366
367 status = parser.ParseMessage(
368 protozero::ConstBytes{binary_proto.data(), binary_proto.size()},
369 ".protozero.test.protos.NestedA", nullptr, *this);
370 EXPECT_TRUE(status.ok())
371 << "InternProtoFieldsIntoArgsTable failed with error: "
372 << status.message();
373 EXPECT_THAT(args(), testing::ElementsAre("file_name file_name file",
374 "line_number line_number 2"));
375 }
376
TEST_F(ProtoToArgsParserTest,OverrideForType)377 TEST_F(ProtoToArgsParserTest, OverrideForType) {
378 using namespace protozero::test::protos::pbzero;
379 protozero::HeapBuffered<NestedA> msg{kChunkSize, kChunkSize};
380 msg->set_super_nested()->set_value_c(3);
381
382 auto binary_proto = msg.SerializeAsArray();
383
384 DescriptorPool pool;
385 auto status = pool.AddFromFileDescriptorSet(kTestMessagesDescriptor.data(),
386 kTestMessagesDescriptor.size());
387 ASSERT_TRUE(status.ok()) << "Failed to parse kTestMessagesDescriptor: "
388 << status.message();
389
390 ProtoToArgsParser parser(pool);
391
392 parser.AddParsingOverrideForType(
393 ".protozero.test.protos.NestedA.NestedB.NestedC",
394 [](ProtoToArgsParser::ScopedNestedKeyContext&,
395 const protozero::ConstBytes&, Delegate& delegate) {
396 delegate.AddInteger(ProtoToArgsParser::Key("arg"), 42);
397 return base::OkStatus();
398 });
399
400 status = parser.ParseMessage(
401 protozero::ConstBytes{binary_proto.data(), binary_proto.size()},
402 ".protozero.test.protos.NestedA", nullptr, *this);
403 EXPECT_TRUE(status.ok())
404 << "InternProtoFieldsIntoArgsTable failed with error: "
405 << status.message();
406 EXPECT_THAT(args(), testing::ElementsAre("arg arg 42"));
407 }
408
TEST_F(ProtoToArgsParserTest,FieldOverrideTakesPrecedence)409 TEST_F(ProtoToArgsParserTest, FieldOverrideTakesPrecedence) {
410 using namespace protozero::test::protos::pbzero;
411 protozero::HeapBuffered<NestedA> msg{kChunkSize, kChunkSize};
412 msg->set_super_nested()->set_value_c(3);
413
414 auto binary_proto = msg.SerializeAsArray();
415
416 DescriptorPool pool;
417 auto status = pool.AddFromFileDescriptorSet(kTestMessagesDescriptor.data(),
418 kTestMessagesDescriptor.size());
419 ASSERT_TRUE(status.ok()) << "Failed to parse kTestMessagesDescriptor: "
420 << status.message();
421
422 ProtoToArgsParser parser(pool);
423
424 parser.AddParsingOverrideForField(
425 "super_nested",
426 [](const protozero::Field&, ProtoToArgsParser::Delegate& writer) {
427 writer.AddString(ProtoToArgsParser::Key("arg"),
428 ToChars("override-for-field"));
429 return base::OkStatus();
430 });
431
432 parser.AddParsingOverrideForType(
433 ".protozero.test.protos.NestedA.NestedB.NestedC",
434 [](ProtoToArgsParser::ScopedNestedKeyContext&,
435 const protozero::ConstBytes&, Delegate& delegate) {
436 delegate.AddString(ProtoToArgsParser::Key("arg"),
437 ToChars("override-for-type"));
438 return base::OkStatus();
439 });
440
441 status = parser.ParseMessage(
442 protozero::ConstBytes{binary_proto.data(), binary_proto.size()},
443 ".protozero.test.protos.NestedA", nullptr, *this);
444 EXPECT_TRUE(status.ok())
445 << "InternProtoFieldsIntoArgsTable failed with error: "
446 << status.message();
447 EXPECT_THAT(args(), testing::ElementsAre("arg arg override-for-field"));
448 }
449
TEST_F(ProtoToArgsParserTest,EmptyMessage)450 TEST_F(ProtoToArgsParserTest, EmptyMessage) {
451 using namespace protozero::test::protos::pbzero;
452 protozero::HeapBuffered<NestedA> msg{kChunkSize, kChunkSize};
453 msg->set_super_nested();
454
455 auto binary_proto = msg.SerializeAsArray();
456
457 DescriptorPool pool;
458 auto status = pool.AddFromFileDescriptorSet(kTestMessagesDescriptor.data(),
459 kTestMessagesDescriptor.size());
460 ASSERT_TRUE(status.ok()) << "Failed to parse kTestMessagesDescriptor: "
461 << status.message();
462
463 ProtoToArgsParser parser(pool);
464 status = parser.ParseMessage(
465 protozero::ConstBytes{binary_proto.data(), binary_proto.size()},
466 ".protozero.test.protos.NestedA", nullptr, *this);
467 EXPECT_TRUE(status.ok())
468 << "InternProtoFieldsIntoArgsTable failed with error: "
469 << status.message();
470 EXPECT_THAT(args(), testing::ElementsAre("super_nested super_nested [NULL]"));
471 }
472
TEST_F(ProtoToArgsParserTest,WidthAndSignednessOfScalars)473 TEST_F(ProtoToArgsParserTest, WidthAndSignednessOfScalars) {
474 using namespace protozero::test::protos::pbzero;
475 protozero::HeapBuffered<EveryField> msg{kChunkSize, kChunkSize};
476
477 // Set fields to values with the top bit set, and check that the parser
478 // retains the full value with the correct sign.
479 msg->set_field_int32(-0x80000000ll);
480 msg->set_field_sint32(-0x80000000ll);
481 msg->set_field_sfixed32(-0x80000000ll);
482
483 msg->set_field_uint32(0x80000000ull);
484 msg->set_field_fixed32(0x80000000ull);
485
486 msg->set_field_int64(-0x7FFFFFFFFFFFFFFFll - 1);
487 msg->set_field_sint64(-0x7FFFFFFFFFFFFFFFll - 1);
488 msg->set_field_sfixed64(-0x7FFFFFFFFFFFFFFFll - 1);
489
490 msg->set_field_uint64(0x8000000000000000ull);
491 msg->set_field_fixed64(0x8000000000000000ull);
492
493 auto binary_proto = msg.SerializeAsArray();
494
495 DescriptorPool pool;
496 auto status = pool.AddFromFileDescriptorSet(kTestMessagesDescriptor.data(),
497 kTestMessagesDescriptor.size());
498 ProtoToArgsParser parser(pool);
499 ASSERT_TRUE(status.ok()) << "Failed to parse kTestMessagesDescriptor: "
500 << status.message();
501
502 status = parser.ParseMessage(
503 protozero::ConstBytes{binary_proto.data(), binary_proto.size()},
504 ".protozero.test.protos.EveryField", nullptr, *this);
505
506 EXPECT_TRUE(status.ok())
507 << "InternProtoFieldsIntoArgsTable failed with error: "
508 << status.message();
509
510 EXPECT_THAT(args(), testing::ElementsAre(
511 "field_int32 field_int32 -2147483648",
512 "field_sint32 field_sint32 -2147483648",
513 "field_sfixed32 field_sfixed32 -2147483648",
514 "field_uint32 field_uint32 2147483648",
515 "field_fixed32 field_fixed32 2147483648",
516 "field_int64 field_int64 -9223372036854775808",
517 "field_sint64 field_sint64 -9223372036854775808",
518 "field_sfixed64 field_sfixed64 -9223372036854775808",
519 "field_uint64 field_uint64 9223372036854775808",
520 "field_fixed64 field_fixed64 9223372036854775808"));
521 }
522
523 } // namespace
524 } // namespace util
525 } // namespace trace_processor
526 } // namespace perfetto
527