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