diff --git a/mg.pbtx b/mg.pbtx new file mode 100644 index 0000000..8632237 --- /dev/null +++ b/mg.pbtx @@ -0,0 +1,18 @@ +buffers: { + size_kb: 522240 + fill_policy: RING_BUFFER +} +data_sources { + config { + name: "track_event" + track_event_config { + enabled_categories: "glcalls" + disabled_categories: "*" + } + } +} +duration_ms: 1800000 +flush_period_ms: 30000 +incremental_state_config { + clear_period_ms: 5000 +} \ No newline at end of file diff --git a/record_android_trace.py b/record_android_trace.py new file mode 100644 index 0000000..203b636 --- /dev/null +++ b/record_android_trace.py @@ -0,0 +1,794 @@ +#!/usr/bin/env python3 +# Copyright (C) 2021 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! +# DO NOT EDIT. Auto-generated by tools/gen_amalgamated_python_tools +# !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +import atexit +import argparse +import datetime +import hashlib +import http.server +import os +import re +import shutil +import signal +import socketserver +import subprocess +import sys +import time +import webbrowser + + +# ----- Amalgamator: begin of python/perfetto/prebuilts/manifests/tracebox.py +# This file has been generated by: tools/roll-prebuilts v48.1 +TRACEBOX_MANIFEST = [{ + 'arch': + 'mac-amd64', + 'file_name': + 'tracebox', + 'file_size': + 1613864, + 'url': + 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v48.1/mac-amd64/tracebox', + 'sha256': + 'dfb1a3affe905d2e7d1f82bc4dda46b1fda6db054d60ae87c3215dd529b77fee', + 'platform': + 'darwin', + 'machine': ['x86_64'] +}, { + 'arch': + 'mac-arm64', + 'file_name': + 'tracebox', + 'file_size': + 1492184, + 'url': + 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v48.1/mac-arm64/tracebox', + 'sha256': + '4a492a629dd1f13f3146c4b8267c0b163afba8cef1d49e0c00c48bb727496066', + 'platform': + 'darwin', + 'machine': ['arm64'] +}, { + 'arch': + 'linux-amd64', + 'file_name': + 'tracebox', + 'file_size': + 2380040, + 'url': + 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v48.1/linux-amd64/tracebox', + 'sha256': + 'd70b284e8c28858fd539ae61ca59764d7f9fd6232073c304926e892fe75e692a', + 'platform': + 'linux', + 'machine': ['x86_64'] +}, { + 'arch': + 'linux-arm', + 'file_name': + 'tracebox', + 'file_size': + 1450708, + 'url': + 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v48.1/linux-arm/tracebox', + 'sha256': + '178fa6a1a9bc80f72d81938d40fe201c25c595ffaff7e030d59c2af09dfcc06c', + 'platform': + 'linux', + 'machine': ['armv6l', 'armv7l', 'armv8l'] +}, { + 'arch': + 'linux-arm64', + 'file_name': + 'tracebox', + 'file_size': + 2269816, + 'url': + 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v48.1/linux-arm64/tracebox', + 'sha256': + '42c64f9807756aaa08a2bfa13e9e4828c193a6b90ba1329408873c3ebf5adf3f', + 'platform': + 'linux', + 'machine': ['aarch64'] +}, { + 'arch': + 'android-arm', + 'file_name': + 'tracebox', + 'file_size': + 1333336, + 'url': + 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v48.1/android-arm/tracebox', + 'sha256': + '93a78d2c42e3c00f117e2f155326383f69c891281ed693a39d87b8cb54ca4e19' +}, { + 'arch': + 'android-arm64', + 'file_name': + 'tracebox', + 'file_size': + 2115984, + 'url': + 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v48.1/android-arm64/tracebox', + 'sha256': + '508248a9e47ab605fd742efb700391d7267b68b586199a93e13e6ca14b72fe3d' +}, { + 'arch': + 'android-x86', + 'file_name': + 'tracebox', + 'file_size': + 2302960, + 'url': + 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v48.1/android-x86/tracebox', + 'sha256': + '63d20a69c4e0c291329d7917e640fa0d4f146c344e79988e87393b1431d594b1' +}, { + 'arch': + 'android-x64', + 'file_name': + 'tracebox', + 'file_size': + 2147880, + 'url': + 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v48.1/android-x64/tracebox', + 'sha256': + 'c0ea1d5fd6d020e4c2b45d4d45cdd0c44ae63cd755d69260a3e5d2bacd3cbd6a' +}] + +# ----- Amalgamator: end of python/perfetto/prebuilts/manifests/tracebox.py + +# ----- Amalgamator: begin of python/perfetto/prebuilts/perfetto_prebuilts.py +# Copyright (C) 2021 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +""" +Functions to fetch pre-pinned Perfetto prebuilts. + +This function is used in different places: +- Into the //tools/{trace_processor, traceconv} scripts, which are just plain + wrappers around executables. +- Into the //tools/{heap_profiler, record_android_trace} scripts, which contain + some other hand-written python code. + +The manifest argument looks as follows: +TRACECONV_MANIFEST = [ + { + 'arch': 'mac-amd64', + 'file_name': 'traceconv', + 'file_size': 7087080, + 'url': https://commondatastorage.googleapis.com/.../trace_to_text', + 'sha256': 7d957c005b0dc130f5bd855d6cec27e060d38841b320d04840afc569f9087490', + 'platform': 'darwin', + 'machine': 'x86_64' + }, + ... +] + +The intended usage is: + + from perfetto.prebuilts.manifests.traceconv import TRACECONV_MANIFEST + bin_path = get_perfetto_prebuilt(TRACECONV_MANIFEST) + subprocess.call(bin_path, ...) +""" + +import hashlib +import os +import platform +import random +import subprocess +import sys + + +def download_or_get_cached(file_name, url, sha256): + """ Downloads a prebuilt or returns a cached version + + The first time this is invoked, it downloads the |url| and caches it into + ~/.local/share/perfetto/prebuilts/$tool_name. On subsequent invocations it + just runs the cached version. + """ + dir = os.path.join( + os.path.expanduser('~'), '.local', 'share', 'perfetto', 'prebuilts') + os.makedirs(dir, exist_ok=True) + bin_path = os.path.join(dir, file_name) + sha256_path = os.path.join(dir, file_name + '.sha256') + needs_download = True + + # Avoid recomputing the SHA-256 on each invocation. The SHA-256 of the last + # download is cached into file_name.sha256, just check if that matches. + if os.path.exists(bin_path) and os.path.exists(sha256_path): + with open(sha256_path, 'rb') as f: + digest = f.read().decode() + if digest == sha256: + needs_download = False + + if needs_download: # The file doesn't exist or the SHA256 doesn't match. + # Use a unique random file to guard against concurrent executions. + # See https://github.com/google/perfetto/issues/786 . + tmp_path = '%s.%d.tmp' % (bin_path, random.randint(0, 100000)) + print('Downloading ' + url) + subprocess.check_call(['curl', '-f', '-L', '-#', '-o', tmp_path, url]) + with open(tmp_path, 'rb') as fd: + actual_sha256 = hashlib.sha256(fd.read()).hexdigest() + if actual_sha256 != sha256: + raise Exception('Checksum mismatch for %s (actual: %s, expected: %s)' % + (url, actual_sha256, sha256)) + os.chmod(tmp_path, 0o755) + os.replace(tmp_path, bin_path) + with open(tmp_path, 'w') as f: + f.write(sha256) + os.replace(tmp_path, sha256_path) + return bin_path + + +def get_perfetto_prebuilt(manifest, soft_fail=False, arch=None): + """ Downloads the prebuilt, if necessary, and returns its path on disk. """ + plat = sys.platform.lower() + machine = platform.machine().lower() + manifest_entry = None + for entry in manifest: + # If the caller overrides the arch, just match that (for Android prebuilts). + if arch: + if entry.get('arch') == arch: + manifest_entry = entry + break + continue + # Otherwise guess the local machine arch. + if entry.get('platform') == plat and machine in entry.get('machine', []): + manifest_entry = entry + break + if manifest_entry is None: + if soft_fail: + return None + raise Exception( + ('No prebuilts available for %s-%s\n' % (plat, machine)) + + 'See https://perfetto.dev/docs/contributing/build-instructions') + + return download_or_get_cached( + file_name=manifest_entry['file_name'], + url=manifest_entry['url'], + sha256=manifest_entry['sha256']) + + +def run_perfetto_prebuilt(manifest): + bin_path = get_perfetto_prebuilt(manifest) + if sys.platform.lower() == 'win32': + sys.exit(subprocess.check_call([bin_path, *sys.argv[1:]])) + os.execv(bin_path, [bin_path] + sys.argv[1:]) + +# ----- Amalgamator: end of python/perfetto/prebuilts/perfetto_prebuilts.py + +# ----- Amalgamator: begin of python/perfetto/common/repo_utils.py +# Copyright (C) 2021 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import os + + +def repo_root(): + """ Finds the repo root by traversing up the hierarchy + + This is for use in scripts that get amalgamated, where _file_ can be either + python/perfetto/... or tools/amalgamated_tool. + """ + path = os.path.dirname(os.path.abspath(__file__)) # amalgamator:nocheck + last_dir = '' + while path and path != last_dir: + if os.path.exists(os.path.join(path, 'perfetto.rc')): + return path + last_dir = path + path = os.path.dirname(path) + return None + + +def repo_dir(rel_path): + return os.path.join(repo_root() or '', rel_path) + +# ----- Amalgamator: end of python/perfetto/common/repo_utils.py + +# This is not required. It's only used as a fallback if no adb is found on the +# PATH. It's fine if it doesn't exist so this script can be copied elsewhere. +HERMETIC_ADB_PATH = repo_dir('buildtools/android_sdk/platform-tools/adb') + +# Translates the Android ro.product.cpu.abi into the GN's target_cpu. +ABI_TO_ARCH = { + 'armeabi-v7a': 'arm', + 'arm64-v8a': 'arm64', + 'x86': 'x86', + 'x86_64': 'x64', +} + +MAX_ADB_FAILURES = 15 # 2 seconds between retries, 30 seconds total. + +devnull = open(os.devnull, 'rb') +adb_path = None +procs = [] + + +class ANSI: + END = '\033[0m' + BOLD = '\033[1m' + RED = '\033[91m' + BLACK = '\033[30m' + BLUE = '\033[94m' + BG_YELLOW = '\033[43m' + BG_BLUE = '\033[44m' + + +# HTTP Server used to open the trace in the browser. +class HttpHandler(http.server.SimpleHTTPRequestHandler): + + def end_headers(self): + self.send_header('Access-Control-Allow-Origin', self.server.allow_origin) + self.send_header('Cache-Control', 'no-cache') + super().end_headers() + + def do_GET(self): + if self.path != '/' + self.server.expected_fname: + self.send_error(404, "File not found") + return + + self.server.fname_get_completed = True + super().do_GET() + + def do_POST(self): + self.send_error(404, "File not found") + + +def setup_arguments(): + atexit.register(kill_all_subprocs_on_exit) + default_out_dir_str = '~/traces/' + default_out_dir = os.path.expanduser(default_out_dir_str) + + examples = '\n'.join([ + ANSI.BOLD + 'Examples' + ANSI.END, ' -t 10s -b 32mb sched gfx wm -a*', + ' -t 5s sched/sched_switch raw_syscalls/sys_enter raw_syscalls/sys_exit', + ' -c /path/to/full-textual-trace.config', '', + ANSI.BOLD + 'Long traces' + ANSI.END, + 'If you want to record a hours long trace and stream it into a file ', + 'you need to pass a full trace config and set write_into_file = true.', + 'See https://perfetto.dev/docs/concepts/config#long-traces .' + ]) + parser = argparse.ArgumentParser( + epilog=examples, formatter_class=argparse.RawTextHelpFormatter) + + help = 'Output file or directory (default: %s)' % default_out_dir_str + parser.add_argument('-o', '--out', default=default_out_dir, help=help) + + help = 'Don\'t open or serve the trace' + parser.add_argument('-n', '--no-open', action='store_true', help=help) + + help = 'Don\'t open in browser, but still serve trace (good for remote use)' + parser.add_argument('--no-open-browser', action='store_true', help=help) + + help = 'The web address used to open trace files' + parser.add_argument('--origin', default='https://ui.perfetto.dev', help=help) + + help = 'Force the use of the sideloaded binaries rather than system daemons' + parser.add_argument('--sideload', action='store_true', help=help) + + help = ('Sideload the given binary rather than downloading it. ' + + 'Implies --sideload') + parser.add_argument('--sideload-path', default=None, help=help) + + help = 'Ignores any tracing guardrails which might be used' + parser.add_argument('--no-guardrails', action='store_true', help=help) + + help = 'Don\'t run `adb root` run as user (only when sideloading)' + parser.add_argument('-u', '--user', action='store_true', help=help) + + help = 'Specify the ADB device serial' + parser.add_argument('--serial', '-s', default=None, help=help) + + grp = parser.add_argument_group( + 'Short options: (only when not using -c/--config)') + + help = 'Trace duration N[s,m,h] (default: trace until stopped)' + grp.add_argument('-t', '--time', default='0s', help=help) + + help = 'Ring buffer size N[mb,gb] (default: 32mb)' + grp.add_argument('-b', '--buffer', default='32mb', help=help) + + help = ('Android (atrace) app names. Can be specified multiple times.\n-a*' + + 'for all apps (without space between a and * or bash will expand it)') + grp.add_argument( + '-a', + '--app', + metavar='com.myapp', + action='append', + default=[], + help=help) + + help = 'sched, gfx, am, wm (see --list)' + grp.add_argument('events', metavar='Atrace events', nargs='*', help=help) + + help = 'sched/sched_switch kmem/kmem (see --list-ftrace)' + grp.add_argument('_', metavar='Ftrace events', nargs='*', help=help) + + help = 'Lists all the categories available' + grp.add_argument('--list', action='store_true', help=help) + + help = 'Lists all the ftrace events available' + grp.add_argument('--list-ftrace', action='store_true', help=help) + + section = ('Full trace config (only when not using short options)') + grp = parser.add_argument_group(section) + + help = 'Can be generated with https://ui.perfetto.dev/#!/record' + grp.add_argument('-c', '--config', default=None, help=help) + + help = 'Parse input from --config as binary proto (default: parse as text)' + grp.add_argument('--bin', action='store_true', help=help) + + help = ('Pass the trace through the trace reporter API. Only works when ' + 'using the full trace config (-c) with the reporter package name ' + "'android.perfetto.cts.reporter' and the reporter class name " + "'android.perfetto.cts.reporter.PerfettoReportService' with the " + 'reporter installed on the device (see ' + 'tools/install_test_reporter_app.py).') + grp.add_argument('--reporter-api', action='store_true', help=help) + + args = parser.parse_args() + args.sideload = args.sideload or args.sideload_path is not None + + if args.serial: + os.environ["ANDROID_SERIAL"] = args.serial + + find_adb() + + if args.list: + adb('shell', 'atrace', '--list_categories').wait() + sys.exit(0) + + if args.list_ftrace: + adb('shell', 'cat /d/tracing/available_events | tr : /').wait() + sys.exit(0) + + if args.config is not None and not os.path.exists(args.config): + prt('Config file not found: %s' % args.config, ANSI.RED) + sys.exit(1) + + if len(args.events) == 0 and args.config is None: + prt('Must either pass short options (e.g. -t 10s sched) or a --config file', + ANSI.RED) + parser.print_help() + sys.exit(1) + + if args.config is None and args.events and os.path.exists(args.events[0]): + prt(('The passed event name "%s" is a local file. ' % args.events[0] + + 'Did you mean to pass -c / --config ?'), ANSI.RED) + sys.exit(1) + + if args.reporter_api and not args.config: + prt('Must pass --config when using --reporter-api', ANSI.RED) + parser.print_help() + sys.exit(1) + + return args + + +class SignalException(Exception): + pass + + +def signal_handler(sig, frame): + raise SignalException('Received signal ' + str(sig)) + + +signal.signal(signal.SIGINT, signal_handler) +signal.signal(signal.SIGTERM, signal_handler) + + +def start_trace(args, print_log=True): + perfetto_cmd = 'perfetto' + device_dir = '/data/misc/perfetto-traces/' + + # Check the version of android. If too old (< Q) sideload tracebox. Also use + # use /data/local/tmp as /data/misc/perfetto-traces was introduced only later. + probe_cmd = 'getprop ro.build.version.sdk; getprop ro.product.cpu.abi; whoami' + probe = adb('shell', probe_cmd, stdout=subprocess.PIPE) + lines = probe.communicate()[0].decode().strip().split('\n') + lines = [x.strip() for x in lines] # To strip \r(s) on Windows. + if probe.returncode != 0: + prt('ADB connection failed', ANSI.RED) + sys.exit(1) + api_level = int(lines[0]) + abi = lines[1] + arch = ABI_TO_ARCH.get(abi) + if arch is None: + prt('Unsupported ABI: ' + abi) + sys.exit(1) + shell_user = lines[2] + if api_level < 29 or args.sideload: # 29: Android Q. + tracebox_bin = args.sideload_path + if tracebox_bin is None: + tracebox_bin = get_perfetto_prebuilt( + TRACEBOX_MANIFEST, arch='android-' + arch) + perfetto_cmd = '/data/local/tmp/tracebox' + exit_code = adb('push', '--sync', tracebox_bin, perfetto_cmd).wait() + exit_code |= adb('shell', 'chmod 755 ' + perfetto_cmd).wait() + if exit_code != 0: + prt('ADB push failed', ANSI.RED) + sys.exit(1) + device_dir = '/data/local/tmp/' + if shell_user != 'root' and not args.user: + # Run as root if possible as that will give access to more tracing + # capabilities. Non-root still works, but some ftrace events might not be + # available. + adb('root').wait() + + tstamp = datetime.datetime.now().strftime('%Y-%m-%d_%H-%M') + fname = '%s-%s.pftrace' % (tstamp, os.urandom(3).hex()) + device_file = device_dir + fname + + cmd = [perfetto_cmd, '--background'] + if not args.bin: + cmd.append('--txt') + + if args.no_guardrails: + cmd.append('--no-guardrails') + + if args.reporter_api: + # Remove all old reporter files to avoid polluting the file we will extract + # later. + adb('shell', + 'rm /sdcard/Android/data/android.perfetto.cts.reporter/files/*').wait() + cmd.append('--upload') + else: + cmd.extend(['-o', device_file]) + + on_device_config = None + on_host_config = None + if args.config is not None: + cmd += ['-c', '-'] + if api_level < 24: + # adb shell does not redirect stdin. Push the config on a temporary file + # on the device. + mktmp = adb( + 'shell', + 'mktemp', + '--tmpdir', + '/data/local/tmp', + stdout=subprocess.PIPE) + on_device_config = mktmp.communicate()[0].decode().strip().strip() + if mktmp.returncode != 0: + prt('Failed to create config on device', ANSI.RED) + sys.exit(1) + exit_code = adb('push', '--sync', args.config, on_device_config).wait() + if exit_code != 0: + prt('Failed to push config on device', ANSI.RED) + sys.exit(1) + cmd = ['cat', on_device_config, '|'] + cmd + else: + on_host_config = args.config + else: + cmd += ['-t', args.time, '-b', args.buffer] + for app in args.app: + cmd += ['--app', '\'' + app + '\''] + cmd += args.events + + # Work out the output file or directory. + if args.out.endswith('/') or os.path.isdir(args.out): + host_dir = args.out + host_file = os.path.join(args.out, fname) + else: + host_file = args.out + host_dir = os.path.dirname(host_file) + if host_dir == '': + host_dir = '.' + host_file = './' + host_file + if not os.path.exists(host_dir): + shutil.os.makedirs(host_dir) + + with open(on_host_config or os.devnull, 'rb') as f: + if print_log: + print('Running ' + ' '.join(cmd)) + proc = adb('shell', *cmd, stdin=f, stdout=subprocess.PIPE) + proc_out = proc.communicate()[0].decode().strip() + if on_device_config is not None: + adb('shell', 'rm', on_device_config).wait() + # On older versions of Android (x86_64 emulator running API 22) the output + # looks like: + # WARNING: linker: /data/local/tmp/tracebox: unused DT entry: ... + # WARNING: ... (other 2 WARNING: linker: lines) + # 1234 <-- The actual pid we want. + match = re.search(r'^(\d+)$', proc_out, re.M) + if match is None: + prt('Failed to read the pid from perfetto --background', ANSI.RED) + prt(proc_out) + sys.exit(1) + bg_pid = match.group(1) + exit_code = proc.wait() + + if exit_code != 0: + prt('Perfetto invocation failed', ANSI.RED) + sys.exit(1) + + prt('Trace started. Press CTRL+C to stop', ANSI.BLACK + ANSI.BG_BLUE) + log_level = "-v" + if not print_log: + log_level = "-e" + logcat = adb('logcat', log_level, 'brief', '-s', 'perfetto', '-b', 'main', + '-T', '1') + + ctrl_c_count = 0 + adb_failure_count = 0 + while ctrl_c_count < 2: + try: + # On older Android devices adbd doesn't propagate the exit code. Hence + # the RUN/TERM parts. + poll = adb( + 'shell', + 'test -d /proc/%s && echo RUN || echo TERM' % bg_pid, + stdout=subprocess.PIPE) + poll_res = poll.communicate()[0].decode().strip() + if poll_res == 'TERM': + break # Process terminated + if poll_res == 'RUN': + # The 'perfetto' cmdline client is still running. If previously we had + # an ADB error, tell the user now it's all right again. + if adb_failure_count > 0: + adb_failure_count = 0 + prt('ADB connection re-established, the trace is still ongoing', + ANSI.BLUE) + time.sleep(0.5) + continue + # Some ADB error happened. This can happen when tracing soon after boot, + # before logging in, when adb gets restarted. + adb_failure_count += 1 + if adb_failure_count >= MAX_ADB_FAILURES: + prt('Too many unrecoverable ADB failures, bailing out', ANSI.RED) + sys.exit(1) + time.sleep(2) + except (KeyboardInterrupt, SignalException): + sig = 'TERM' if ctrl_c_count == 0 else 'KILL' + ctrl_c_count += 1 + if print_log: + prt('Stopping the trace (SIG%s)' % sig, ANSI.BLACK + ANSI.BG_YELLOW) + adb('shell', 'kill -%s %s' % (sig, bg_pid)).wait() + + logcat.kill() + logcat.wait() + + if args.reporter_api: + if print_log: + prt('Waiting a few seconds to allow reporter to copy trace') + time.sleep(5) + + ret = adb( + 'shell', + 'cp /sdcard/Android/data/android.perfetto.cts.reporter/files/* ' + + device_file).wait() + if ret != 0: + prt('Failed to extract reporter trace', ANSI.RED) + sys.exit(1) + + if print_log: + prt('\n') + prt('Pulling into %s' % host_file, ANSI.BOLD) + adb('pull', device_file, host_file).wait() + adb('shell', 'rm -f ' + device_file).wait() + + if not args.no_open: + if print_log: + prt('\n') + prt('Opening the trace (%s) in the browser' % host_file) + open_browser = not args.no_open_browser + open_trace_in_browser(host_file, open_browser, args.origin) + + return host_file + + +def main(): + args = setup_arguments() + start_trace(args) + + +def prt(msg, colors=ANSI.END): + print(colors + msg + ANSI.END) + + +def find_adb(): + """ Locate the "right" adb path + + If adb is in the PATH use that (likely what the user wants) otherwise use the + hermetic one in our SDK copy. + """ + global adb_path + for path in ['adb', HERMETIC_ADB_PATH]: + try: + subprocess.call([path, '--version'], stdout=devnull, stderr=devnull) + adb_path = path + break + except OSError: + continue + if adb_path is None: + sdk_url = 'https://developer.android.com/studio/releases/platform-tools' + prt('Could not find a suitable adb binary in the PATH. ', ANSI.RED) + prt('You can download adb from %s' % sdk_url, ANSI.RED) + sys.exit(1) + + +def open_trace_in_browser(path, open_browser, origin): + # We reuse the HTTP+RPC port because it's the only one allowed by the CSP. + PORT = 9001 + path = os.path.abspath(path) + os.chdir(os.path.dirname(path)) + fname = os.path.basename(path) + socketserver.TCPServer.allow_reuse_address = True + with socketserver.TCPServer(('127.0.0.1', PORT), HttpHandler) as httpd: + address = f'{origin}/#!/?url=http://127.0.0.1:{PORT}/{fname}&referrer=record_android_trace' + if open_browser: + webbrowser.open_new_tab(address) + else: + print(f'Open URL in browser: {address}') + + httpd.expected_fname = fname + httpd.fname_get_completed = None + httpd.allow_origin = origin + while httpd.fname_get_completed is None: + httpd.handle_request() + + +def adb(*args, stdin=devnull, stdout=None, stderr=None): + cmd = [adb_path, *args] + setpgrp = None + if os.name != 'nt': + # On Linux/Mac, start a new process group so all child processes are killed + # on exit. Unsupported on Windows. + setpgrp = lambda: os.setpgrp() + proc = subprocess.Popen( + cmd, stdin=stdin, stdout=stdout, stderr=stderr, preexec_fn=setpgrp) + procs.append(proc) + return proc + + +def kill_all_subprocs_on_exit(): + for p in [p for p in procs if p.poll() is None]: + p.kill() + + +def check_hash(file_name, sha_value): + with open(file_name, 'rb') as fd: + file_hash = hashlib.sha1(fd.read()).hexdigest() + return file_hash == sha_value + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/src/main/cpp/gl/log.h b/src/main/cpp/gl/log.h index f18f7d0..279aa7e 100644 --- a/src/main/cpp/gl/log.h +++ b/src/main/cpp/gl/log.h @@ -25,7 +25,15 @@ const char *glEnumToString(GLenum e); #define LOG_E(...) {} #define LOG_F(...) {} #else -#define LOG() if(DEBUG||GLOBAL_DEBUG) {__android_log_print(ANDROID_LOG_DEBUG, RENDERERNAME, "Use function: %s", __FUNCTION__);printf("Use function: %s\n", __FUNCTION__);write_log("Use function: %s\n", __FUNCTION__);} +#if PROFILING +#define LOG() \ + perfetto::StaticString _FUNC_NAME_ = __func__; \ + TRACE_EVENT("glcalls", _FUNC_NAME_); +#else +#define LOG() \ + if(DEBUG||GLOBAL_DEBUG) {__android_log_print(ANDROID_LOG_DEBUG, RENDERERNAME, "Use function: %s", __FUNCTION__);printf("Use function: %s\n", __FUNCTION__);write_log("Use function: %s\n", __FUNCTION__);} +#endif + #define LOG_D(...) if(DEBUG||GLOBAL_DEBUG) {__android_log_print(ANDROID_LOG_DEBUG, RENDERERNAME, __VA_ARGS__);printf(__VA_ARGS__);printf("\n");write_log(__VA_ARGS__);} #define LOG_W(...) if(DEBUG||GLOBAL_DEBUG) {__android_log_print(ANDROID_LOG_WARN, RENDERERNAME, __VA_ARGS__);printf(__VA_ARGS__);printf("\n");write_log(__VA_ARGS__);} #define LOG_E(...) if(DEBUG||GLOBAL_DEBUG) {__android_log_print(ANDROID_LOG_ERROR, RENDERERNAME, __VA_ARGS__);printf(__VA_ARGS__);printf("\n");write_log(__VA_ARGS__);} diff --git a/src/main/cpp/includes.h b/src/main/cpp/includes.h index c54c48d..a6df3f8 100644 --- a/src/main/cpp/includes.h +++ b/src/main/cpp/includes.h @@ -15,14 +15,14 @@ #include "egl/egl.h" #include "egl/loader.h" +#if PROFILING #include - PERFETTO_DEFINE_CATEGORIES( - perfetto::Category("GLCalls") + perfetto::Category("glcalls") .SetDescription("Calls from OpenGL"), - perfetto::Category("Internal") + perfetto::Category("internal") .SetDescription("Internal calls")); - +#endif #ifdef __cplusplus extern "C" { diff --git a/src/main/cpp/main.cpp b/src/main/cpp/main.cpp index 88b1473..f3fe138 100644 --- a/src/main/cpp/main.cpp +++ b/src/main/cpp/main.cpp @@ -16,10 +16,6 @@ #define DEBUG 0 -#ifdef __cplusplus -extern "C" { -#endif - __attribute__((used)) const char* copyright = "Copyright (C) 2025 Swung0x48, BZLZHH, Tungsten. All rights reserved. Logo artwork kindly provided by Aou156."; extern char* (*MesaConvertShader)(const char *src, unsigned int type, unsigned int glsl, unsigned int essl); @@ -54,6 +50,20 @@ void show_copyright() { LOG_V(" %s", copyright); } +#if PROFILING + +PERFETTO_TRACK_EVENT_STATIC_STORAGE(); + +void init_perfetto() { + perfetto::TracingInitArgs args; + + args.backends |= perfetto::kSystemBackend; + + perfetto::Tracing::Initialize(args); + perfetto::TrackEvent::Register(); +} +#endif + void proc_init() { init_config(); @@ -71,9 +81,9 @@ void proc_init() { init_libshaderconv(); +#if PROFILING + init_perfetto(); +#endif + g_initialized = 1; } - -#ifdef __cplusplus -} -#endif diff --git a/trace.bat b/trace.bat new file mode 100644 index 0000000..9383d27 --- /dev/null +++ b/trace.bat @@ -0,0 +1 @@ +python .\record_android_trace.py -c .\mg.pbtx -o trace_file.perfetto-trace \ No newline at end of file