1#!/usr/bin/env python3 2# Copyright (C) 2024 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# DO NOT EDIT. Auto-generated by tools/gen_amalgamated_python_tools 18# !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! 19 20import argparse 21import os 22import re 23import sys 24import tempfile 25import time 26 27 28# ----- Amalgamator: begin of python/perfetto/prebuilts/perfetto_prebuilts.py 29# Copyright (C) 2021 The Android Open Source Project 30# 31# Licensed under the Apache License, Version 2.0 (the "License"); 32# you may not use this file except in compliance with the License. 33# You may obtain a copy of the License at 34# 35# http://www.apache.org/licenses/LICENSE-2.0 36# 37# Unless required by applicable law or agreed to in writing, software 38# distributed under the License is distributed on an "AS IS" BASIS, 39# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 40# See the License for the specific language governing permissions and 41# limitations under the License. 42""" 43Functions to fetch pre-pinned Perfetto prebuilts. 44 45This function is used in different places: 46- Into the //tools/{trace_processor, traceconv} scripts, which are just plain 47 wrappers around executables. 48- Into the //tools/{heap_profiler, record_android_trace} scripts, which contain 49 some other hand-written python code. 50 51The manifest argument looks as follows: 52TRACECONV_MANIFEST = [ 53 { 54 'arch': 'mac-amd64', 55 'file_name': 'traceconv', 56 'file_size': 7087080, 57 'url': https://commondatastorage.googleapis.com/.../trace_to_text', 58 'sha256': 7d957c005b0dc130f5bd855d6cec27e060d38841b320d04840afc569f9087490', 59 'platform': 'darwin', 60 'machine': 'x86_64' 61 }, 62 ... 63] 64 65The intended usage is: 66 67 from perfetto.prebuilts.manifests.traceconv import TRACECONV_MANIFEST 68 bin_path = get_perfetto_prebuilt(TRACECONV_MANIFEST) 69 subprocess.call(bin_path, ...) 70""" 71 72import hashlib 73import os 74import platform 75import random 76import subprocess 77import sys 78 79 80def download_or_get_cached(file_name, url, sha256): 81 """ Downloads a prebuilt or returns a cached version 82 83 The first time this is invoked, it downloads the |url| and caches it into 84 ~/.local/share/perfetto/prebuilts/$tool_name. On subsequent invocations it 85 just runs the cached version. 86 """ 87 dir = os.path.join( 88 os.path.expanduser('~'), '.local', 'share', 'perfetto', 'prebuilts') 89 os.makedirs(dir, exist_ok=True) 90 bin_path = os.path.join(dir, file_name) 91 sha256_path = os.path.join(dir, file_name + '.sha256') 92 needs_download = True 93 94 # Avoid recomputing the SHA-256 on each invocation. The SHA-256 of the last 95 # download is cached into file_name.sha256, just check if that matches. 96 if os.path.exists(bin_path) and os.path.exists(sha256_path): 97 with open(sha256_path, 'rb') as f: 98 digest = f.read().decode() 99 if digest == sha256: 100 needs_download = False 101 102 if needs_download: # The file doesn't exist or the SHA256 doesn't match. 103 # Use a unique random file to guard against concurrent executions. 104 # See https://github.com/google/perfetto/issues/786 . 105 tmp_path = '%s.%d.tmp' % (bin_path, random.randint(0, 100000)) 106 print('Downloading ' + url) 107 subprocess.check_call(['curl', '-f', '-L', '-#', '-o', tmp_path, url]) 108 with open(tmp_path, 'rb') as fd: 109 actual_sha256 = hashlib.sha256(fd.read()).hexdigest() 110 if actual_sha256 != sha256: 111 raise Exception('Checksum mismatch for %s (actual: %s, expected: %s)' % 112 (url, actual_sha256, sha256)) 113 os.chmod(tmp_path, 0o755) 114 os.replace(tmp_path, bin_path) 115 with open(tmp_path, 'w') as f: 116 f.write(sha256) 117 os.replace(tmp_path, sha256_path) 118 return bin_path 119 120 121def get_perfetto_prebuilt(manifest, soft_fail=False, arch=None): 122 """ Downloads the prebuilt, if necessary, and returns its path on disk. """ 123 plat = sys.platform.lower() 124 machine = platform.machine().lower() 125 manifest_entry = None 126 for entry in manifest: 127 # If the caller overrides the arch, just match that (for Android prebuilts). 128 if arch: 129 if entry.get('arch') == arch: 130 manifest_entry = entry 131 break 132 continue 133 # Otherwise guess the local machine arch. 134 if entry.get('platform') == plat and machine in entry.get('machine', []): 135 manifest_entry = entry 136 break 137 if manifest_entry is None: 138 if soft_fail: 139 return None 140 raise Exception( 141 ('No prebuilts available for %s-%s\n' % (plat, machine)) + 142 'See https://perfetto.dev/docs/contributing/build-instructions') 143 144 return download_or_get_cached( 145 file_name=manifest_entry['file_name'], 146 url=manifest_entry['url'], 147 sha256=manifest_entry['sha256']) 148 149 150def run_perfetto_prebuilt(manifest): 151 bin_path = get_perfetto_prebuilt(manifest) 152 if sys.platform.lower() == 'win32': 153 sys.exit(subprocess.check_call([bin_path, *sys.argv[1:]])) 154 os.execv(bin_path, [bin_path] + sys.argv[1:]) 155 156# ----- Amalgamator: end of python/perfetto/prebuilts/perfetto_prebuilts.py 157 158PERMSISION_REGEX = re.compile(r'''uses-permission: name='(.*)'.*''') 159NAME_REGEX = re.compile(r'''package: name='(.*?)' .*''') 160 161 162def cmd(args: list[str]): 163 print('Running command ' + ' '.join(args)) 164 return subprocess.check_output(args) 165 166 167def main(): 168 parser = argparse.ArgumentParser() 169 parser.add_argument('--apk', help='Local APK to use instead of builtin') 170 171 args = parser.parse_args() 172 173 if args.apk: 174 apk = args.apk 175 else: 176 apk = download_or_get_cached( 177 'CtsPerfettoReporterApp.apk', 178 'https://storage.googleapis.com/perfetto/CtsPerfettoReporterApp.apk', 179 'f21dda36668c368793500b13724ab2a6231d12ded05746f7cfaaba4adedd7d46') 180 181 # Figure out the package name and the permissions we need 182 aapt = subprocess.check_output(['aapt', 'dump', 'badging', apk]).decode() 183 permission_names = [] 184 name = '' 185 for l in aapt.splitlines(): 186 name_match = NAME_REGEX.match(l) 187 if name_match: 188 name = name_match[1] 189 continue 190 191 permission_match = PERMSISION_REGEX.match(l) 192 if permission_match: 193 permission_names.append(permission_match[1]) 194 continue 195 196 # Root and remount the device. 197 cmd(['adb', 'root']) 198 cmd(['adb', 'wait-for-device']) 199 cmd(['adb', 'remount', '-R']) 200 input('The device might now reboot. If so, please unlock the device then ' 201 'press enter to continue') 202 cmd(['adb', 'wait-for-device']) 203 cmd(['adb', 'root']) 204 cmd(['adb', 'wait-for-device']) 205 cmd(['adb', 'remount', '-R']) 206 207 # Write out the permission file on device. 208 permissions = '\n'.join( 209 f'''<permission name='{p}' />''' for p in permission_names) 210 permission_file_contents = f''' 211 <permissions> 212 <privapp-permissions package="{name}"> 213 {permissions} 214 </privapp-permissions> 215 </permissions> 216 ''' 217 with tempfile.NamedTemporaryFile() as f: 218 f.write(permission_file_contents.encode()) 219 f.flush() 220 221 cmd([ 222 'adb', 'push', f.name, 223 f'/system/etc/permissions/privapp-permissions-{name}.xml' 224 ]) 225 226 # Stop system_server, push the apk on device and restart system_server 227 priv_app_path = f'/system/priv-app/{name}/{name}.apk' 228 cmd(['adb', 'shell', 'stop']) 229 cmd(['adb', 'push', apk, priv_app_path]) 230 cmd(['adb', 'shell', 'start']) 231 cmd(['adb', 'wait-for-device']) 232 time.sleep(10) 233 234 # Wait for system_server and package manager to come up. 235 while 'system_server' not in cmd(['adb', 'shell', 'ps']).decode(): 236 time.sleep(1) 237 while True: 238 ps = set([ 239 l.strip() 240 for l in cmd(['adb', 'shell', 'dumpsys', '-l']).decode().splitlines() 241 ]) 242 if 'storaged' in ps and 'settings' in ps and 'package' in ps: 243 break 244 time.sleep(1) 245 246 # Install the actual APK. 247 cmd(['adb', 'shell', 'pm', 'install', '-r', '-d', '-g', '-t', priv_app_path]) 248 249 return 0 250 251 252sys.exit(main()) 253