xref: /aosp_15_r20/external/perfetto/tools/update-statsd-descriptor (revision 6dbdd20afdafa5e3ca9b8809fa73465d530080dc)
1#!/usr/bin/env python3
2# Copyright (C) 2022 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
16import os
17import sys
18import re
19import subprocess
20import pathlib
21import tempfile
22import contextlib
23import argparse
24import itertools
25
26ROOT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
27TOOLS_DIR = os.path.join(ROOT_DIR, "tools")
28OUT_DIR = os.path.join(ROOT_DIR, "out", "tools")
29NINJA = os.path.join(TOOLS_DIR, "ninja")
30GN = os.path.join(TOOLS_DIR, "gn")
31PROTOC_PATH = os.path.join(OUT_DIR, "protoc")
32DESCRIPTOR_PATH = os.path.join(ROOT_DIR, "src", "trace_processor", "importers",
33                               "proto", "atoms.descriptor")
34PROTOBUF_BUILTINS_DIR = os.path.join(ROOT_DIR, "buildtools", "protobuf", "src")
35PROTO_LOGGING_URL = "https://android.googlesource.com/platform/frameworks/proto_logging.git"
36ATOM_RE = r"  message_type {\n.   name: \"Atom\"(\n    .+)+(\n  })"
37FIELD_RE = r"    field {\n      name: \"([^\"]+)\"\n      number: ([0-9]+)"
38EXTENSIONS_RE = r" extension {\n    name: \"([^\"]+)\"\n    extendee: \".android.os.statsd.Atom\"\n    number: ([0-9]+)(\n    .+)+(\n  })"
39ATOM_IDS_PATH = os.path.join(ROOT_DIR, "protos", "perfetto", "config", "statsd",
40                             "atom_ids.proto")
41
42ATOM_IDS_TEMPLATE = """/*
43 * Copyright (C) 2022 The Android Open Source Project
44 *
45 * Licensed under the Apache License, Version 2.0 (the "License");
46 * you may not use this file except in compliance with the License.
47 * You may obtain a copy of the License at
48 *
49 *      http://www.apache.org/licenses/LICENSE-2.0
50 *
51 * Unless required by applicable law or agreed to in writing, software
52 * distributed under the License is distributed on an "AS IS" BASIS,
53 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
54 * See the License for the specific language governing permissions and
55 * limitations under the License.
56 */
57syntax = "proto2";
58
59package perfetto.protos;
60
61// This enum is obtained by post-processing
62// AOSP/frameworks/proto_logging/stats/atoms.proto through
63// AOSP/external/perfetto/tools/update-statsd-descriptor, which extracts one
64// enum value for each proto field defined in the upstream atoms.proto.
65enum AtomId {{
66  ATOM_UNSPECIFIED = 0;
67{atoms}
68}}"""
69
70
71def call(*cmd, stdin=None):
72  try:
73    return subprocess.check_output(cmd, stdin=stdin)
74  except subprocess.CalledProcessError as e:
75    print("Error running the command:")
76    print(" ".join(cmd))
77    print(e)
78    exit(1)
79
80
81# Extract core atoms. To do this we regex the pbtext
82# of the descriptor. This is hopefully:
83# - more stable than regexing atom.proto directly
84# - less complicated than parsing finding, importing, and using the
85#   Python protobuf library.
86def atoms_from_descriptor():
87  with contextlib.ExitStack() as stack:
88    descriptor_in = stack.enter_context(open(DESCRIPTOR_PATH))
89    pbtext = call(
90        PROTOC_PATH,
91        f"--proto_path={PROTOBUF_BUILTINS_DIR}",
92        f"{PROTOBUF_BUILTINS_DIR}/google/protobuf/descriptor.proto",
93        "--decode=google.protobuf.FileDescriptorSet",
94        stdin=descriptor_in).decode("utf8")
95
96    # Core atoms:
97    atom_pbtext = re.search(ATOM_RE, pbtext, re.MULTILINE)[0]
98    for m in re.finditer(FIELD_RE, atom_pbtext):
99      yield m[1], m[2]
100
101    # Extensions
102    for m in re.finditer(EXTENSIONS_RE, pbtext):
103      yield m[1], m[2]
104
105
106def main():
107  parser = argparse.ArgumentParser()
108  parser.add_argument("--atoms-checkout")
109  args = parser.parse_args()
110
111  call(GN, "gen", OUT_DIR, "--args=is_debug=false")
112  call(NINJA, "-C", OUT_DIR, "protoc")
113
114  with contextlib.ExitStack() as stack:
115
116    # Write the descriptor.
117    if args.atoms_checkout:
118      atoms_root = args.atoms_checkout
119      proto_logging_dir = os.path.join(atoms_root, "frameworks",
120                                       "proto_logging")
121    else:
122      atoms_root = stack.enter_context(tempfile.TemporaryDirectory())
123      proto_logging_dir = os.path.join(atoms_root, "frameworks",
124                                       "proto_logging")
125      pathlib.Path(proto_logging_dir).mkdir(parents=True, exist_ok=True)
126      call("git", "clone", PROTO_LOGGING_URL, proto_logging_dir)
127
128
129    extensions_path = os.path.join(proto_logging_dir, "stats", "atoms")
130    extensions = []
131    if os.path.isdir(extensions_path):
132      for dirpath, dirnames, filenames in os.walk(extensions_path):
133        for name in filenames:
134          if name.endswith(".proto"):
135            path = os.path.join(dirpath, name)
136            extensions.append(path)
137
138    cmd = [
139      f"--proto_path={PROTOBUF_BUILTINS_DIR}",
140      f"--proto_path={atoms_root}",
141      f"--descriptor_set_out={DESCRIPTOR_PATH}",
142      "--include_imports",
143      ] + extensions + [
144      os.path.join(proto_logging_dir, "stats", "atoms.proto")
145    ]
146    call(PROTOC_PATH, *cmd)
147
148    lines = []
149    for name, field in atoms_from_descriptor():
150      name = "ATOM_" + name.upper()
151      lines.append(f"  {name} = {field};".format(name=name, field=field))
152    atom_ids_out = stack.enter_context(open(ATOM_IDS_PATH, "w"))
153    atom_ids_out.write(ATOM_IDS_TEMPLATE.format(atoms="\n".join(lines)))
154
155
156if __name__ == "__main__":
157  sys.exit(main())
158