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