Skip to content

Commit 883ed23

Browse files
authored
tracing_proto_extesions: change schema, add root (#4939)
I just realized that the format I introduced in #4833 is not future proof. The problem is specifically extending both TrackEvent and TracePacket. This PR adds an "extension" root, so we can define in the same json two objects, one for each message to extend.
1 parent a67f134 commit 883ed23

File tree

4 files changed

+349
-221
lines changed

4 files changed

+349
-221
lines changed
Lines changed: 39 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,45 @@
11
{
2-
"scope": "perfetto.protos.TrackEvent",
3-
"range": [1000, 9999],
4-
"allocations": [
2+
"extensions": [
53
{
6-
"name": "chromium",
7-
"range": [1000, 1999],
8-
"contact": "tracing@chromium.org",
9-
"description": "Chrome browser and chromium-based projects",
10-
"comment": ["The source of truth lives in chromium. A copy exists in ",
11-
"the Perfetto GitHub repo under ",
12-
"protos/third_party/chromium/chrome_track_event.proto ",
13-
"and is kept up-to-date via a Skia autoroller."
14-
],
4+
"scope": "perfetto.protos.TrackEvent",
5+
"range": [1000, 9999],
6+
"allocations": [
7+
{
8+
"name": "chromium",
9+
"range": [1000, 1999],
10+
"contact": "tracing@chromium.org",
11+
"description": "Chrome browser and chromium-based projects",
12+
"comment": ["The source of truth lives in chromium. A copy exists in ",
13+
"the Perfetto GitHub repo under ",
14+
"protos/third_party/chromium/chrome_track_event.proto ",
15+
"and is kept up-to-date via a Skia autoroller."
16+
],
1517

16-
"repo": "https://chromium.googlesource.com/chromium/src",
17-
"proto": "base/tracing/protos/chrome_track_event.proto"
18-
},
19-
{
20-
"name": "android_system",
21-
"range": [2000, 2999],
22-
"contact": "perfetto-dev@googlegroups.com",
23-
"comment": ["TODO(primiano): moved these into the Android tree and ",
24-
"create sub-registries there."],
25-
"description": "Android OS platform (System image)",
26-
"proto": "protos/perfetto/trace/android/android_track_event.proto"
27-
},
28-
{
29-
"name": "unallocated",
30-
"comment": ["The next allocation goes here. Shrink and take from here."],
31-
"range": [3000, 8999]
32-
},
33-
{
34-
"name": "perfetto_tests",
35-
"range": [9000, 9999],
36-
"contact": "perfetto-dev@googlegroups.com",
37-
"description": "Reserved for Perfetto unit and integration tests",
38-
"proto": "protos/perfetto/trace/test_extensions.proto"
18+
"repo": "https://chromium.googlesource.com/chromium/src",
19+
"proto": "base/tracing/protos/chrome_track_event.proto"
20+
},
21+
{
22+
"name": "android_system",
23+
"range": [2000, 2999],
24+
"contact": "perfetto-dev@googlegroups.com",
25+
"comment": ["TODO(primiano): move these into the Android tree and ",
26+
"create sub-registries there."],
27+
"description": "Android OS platform (System image)",
28+
"proto": "protos/perfetto/trace/android/android_track_event.proto"
29+
},
30+
{
31+
"name": "unallocated",
32+
"comment": ["The next allocation goes here. Shrink and take from here."],
33+
"range": [3000, 8999]
34+
},
35+
{
36+
"name": "perfetto_tests",
37+
"range": [9000, 9999],
38+
"contact": "perfetto-dev@googlegroups.com",
39+
"description": "Reserved for Perfetto unit and integration tests",
40+
"proto": "protos/perfetto/trace/test_extensions.proto"
41+
}
42+
]
3943
}
4044
]
4145
}

src/tools/tracing_proto_extensions.cc

Lines changed: 98 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -43,12 +43,13 @@
4343

4444
namespace perfetto {
4545
namespace gen_proto_extensions {
46-
namespace {
4746

4847
namespace pbzero = protos::pbzero;
4948
using trace_processor::json::FieldResult;
5049
using trace_processor::json::SimpleJsonParser;
5150

51+
namespace {
52+
5253
// Sorts ranges by start and checks for validity (start <= end, no internal
5354
// overlaps).
5455
base::Status SortAndValidateRanges(std::vector<Range>& ranges,
@@ -259,53 +260,14 @@ base::Status ValidateFieldNumbers(
259260
return base::OkStatus();
260261
}
261262

262-
// Recursively collects all proto files from the registry tree.
263-
struct ProtoEntry {
264-
std::string proto_path;
265-
std::string scope;
266-
std::vector<Range> ranges;
267-
};
268-
269-
base::Status CollectProtos(const std::string& json_path,
270-
const std::string& root_dir,
271-
std::vector<ProtoEntry>* out) {
272-
std::string contents;
273-
if (!base::ReadFile(json_path, &contents)) {
274-
return base::ErrStatus("Failed to read '%s'", json_path.c_str());
275-
}
276-
277-
ASSIGN_OR_RETURN(auto reg, ParseRegistry(contents, json_path));
278-
RETURN_IF_ERROR(ValidateRegistry(reg));
279-
280-
for (const auto& alloc : reg.allocations) {
281-
if (!alloc.proto.empty() && alloc.repo.empty()) {
282-
// Local proto leaf.
283-
out->push_back({root_dir + "/" + alloc.proto, reg.scope, alloc.ranges});
284-
} else if (!alloc.registry.empty() && alloc.repo.empty()) {
285-
// Local sub-registry.
286-
std::string sub_path = root_dir + "/" + alloc.registry;
287-
RETURN_IF_ERROR(CollectProtos(sub_path, root_dir, out));
288-
}
289-
// Remote entries (repo is set) are skipped.
290-
}
291-
return base::OkStatus();
292-
}
293-
294-
} // namespace
295-
296-
base::StatusOr<Registry> ParseRegistry(const std::string& json_contents,
297-
const std::string& source_path) {
263+
// Parses a single registry object from the current parser position.
264+
// The parser should be positioned at a JSON object with
265+
// scope/range/allocations.
266+
base::StatusOr<Registry> ParseRegistryObject(SimpleJsonParser& parser,
267+
const std::string& source_path) {
298268
Registry reg;
299269
reg.source_path = source_path;
300270

301-
SimpleJsonParser parser(json_contents);
302-
{
303-
auto status = parser.Parse();
304-
if (!status.ok())
305-
return base::ErrStatus("Failed to parse JSON in '%s': %s",
306-
source_path.c_str(), status.message().c_str());
307-
}
308-
309271
bool has_range = false;
310272
bool has_ranges = false;
311273
RETURN_IF_ERROR(parser.ForEachField([&](std::string_view key) -> FieldResult {
@@ -462,6 +424,46 @@ base::StatusOr<Registry> ParseRegistry(const std::string& json_contents,
462424
return std::move(reg);
463425
}
464426

427+
} // namespace
428+
429+
base::StatusOr<std::vector<Registry>> ParseRegistryFile(
430+
const std::string& json_contents,
431+
const std::string& source_path) {
432+
SimpleJsonParser parser(json_contents);
433+
{
434+
auto status = parser.Parse();
435+
if (!status.ok())
436+
return base::ErrStatus("Failed to parse JSON in '%s': %s",
437+
source_path.c_str(), status.message().c_str());
438+
}
439+
440+
std::vector<Registry> extensions;
441+
RETURN_IF_ERROR(parser.ForEachField([&](std::string_view key) -> FieldResult {
442+
if (key == "extensions") {
443+
if (!parser.IsArray())
444+
return base::ErrStatus("'extensions' must be an array in '%s'",
445+
source_path.c_str());
446+
RETURN_IF_ERROR(parser.ForEachArrayElement([&]() -> base::Status {
447+
if (!parser.IsObject())
448+
return base::ErrStatus(
449+
"Each entry in 'extensions' must be an object in '%s'",
450+
source_path.c_str());
451+
ASSIGN_OR_RETURN(auto reg, ParseRegistryObject(parser, source_path));
452+
extensions.push_back(std::move(reg));
453+
return base::OkStatus();
454+
}));
455+
return FieldResult::Handled{};
456+
}
457+
if (key == "comment")
458+
return FieldResult::Skip{};
459+
return base::ErrStatus("Unknown field '%.*s' in '%s'",
460+
static_cast<int>(key.size()), key.data(),
461+
source_path.c_str());
462+
}));
463+
464+
return extensions;
465+
}
466+
465467
base::Status ValidateRegistry(const Registry& reg) {
466468
// Currently only TrackEvent extensions are supported. In the future, this
467469
// field could be used to disambiguate TracePacket extensions.
@@ -554,13 +556,62 @@ base::Status ValidateRegistry(const Registry& reg) {
554556
return base::OkStatus();
555557
}
556558

559+
namespace {
560+
561+
// Recursively collects all proto files from the registry tree.
562+
struct ProtoEntry {
563+
std::string proto_path;
564+
std::string scope;
565+
std::vector<Range> ranges;
566+
};
567+
568+
// Walks allocations from an already-parsed registry. For sub-registries,
569+
// reads and parses them using the flat (non-wrapped) format.
570+
base::Status CollectProtosFromRegistry(const Registry& reg,
571+
const std::string& root_dir,
572+
std::vector<ProtoEntry>* out) {
573+
for (const auto& alloc : reg.allocations) {
574+
if (!alloc.proto.empty() && alloc.repo.empty()) {
575+
// Local proto leaf.
576+
out->push_back({root_dir + "/" + alloc.proto, reg.scope, alloc.ranges});
577+
} else if (!alloc.registry.empty() && alloc.repo.empty()) {
578+
// Local sub-registry (same {"extensions": [...]} format).
579+
std::string sub_path = root_dir + "/" + alloc.registry;
580+
std::string contents;
581+
if (!base::ReadFile(sub_path, &contents)) {
582+
return base::ErrStatus("Failed to read '%s'", sub_path.c_str());
583+
}
584+
ASSIGN_OR_RETURN(auto sub_regs, ParseRegistryFile(contents, sub_path));
585+
for (const auto& sub_reg : sub_regs) {
586+
RETURN_IF_ERROR(ValidateRegistry(sub_reg));
587+
RETURN_IF_ERROR(CollectProtosFromRegistry(sub_reg, root_dir, out));
588+
}
589+
}
590+
// Remote entries (repo is set) are skipped.
591+
}
592+
return base::OkStatus();
593+
}
594+
595+
} // namespace
596+
557597
base::StatusOr<std::vector<uint8_t>> GenerateExtensionDescriptors(
558598
const std::string& root_json_path,
559599
const std::vector<std::string>& proto_paths,
560600
const std::string& root_dir) {
561-
// 1. Recursively collect all local proto entries from the JSON hierarchy.
601+
// 1. Read and parse the root registry file (uses the "extensions" wrapper).
602+
std::string root_contents;
603+
if (!base::ReadFile(root_json_path, &root_contents)) {
604+
return base::ErrStatus("Failed to read '%s'", root_json_path.c_str());
605+
}
606+
ASSIGN_OR_RETURN(auto extensions,
607+
ParseRegistryFile(root_contents, root_json_path));
608+
609+
// 2. Recursively collect all local proto entries from each registry.
562610
std::vector<ProtoEntry> entries;
563-
RETURN_IF_ERROR(CollectProtos(root_json_path, root_dir, &entries));
611+
for (const auto& reg : extensions) {
612+
RETURN_IF_ERROR(ValidateRegistry(reg));
613+
RETURN_IF_ERROR(CollectProtosFromRegistry(reg, root_dir, &entries));
614+
}
564615

565616
if (entries.empty()) {
566617
PERFETTO_ILOG("No local proto files found in registry.");

src/tools/tracing_proto_extensions.h

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -55,9 +55,11 @@ struct Registry {
5555
std::string source_path;
5656
};
5757

58-
// Parses a track_event_extensions.json file from its contents.
59-
base::StatusOr<Registry> ParseRegistry(const std::string& json_contents,
60-
const std::string& source_path);
58+
// Parses a track_event_extensions.json file with the {"extensions": [...]}
59+
// format. Returns one Registry per entry in the array.
60+
base::StatusOr<std::vector<Registry>> ParseRegistryFile(
61+
const std::string& json_contents,
62+
const std::string& source_path);
6163

6264
// Validates a registry: checks that allocations tile the ranges exactly
6365
// (no gaps or overlaps) and that constraints on proto/registry fields are met.

0 commit comments

Comments
 (0)