| #!/usr/bin/env python3 |
| # Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file |
| # for details. All rights reserved. Use of this source code is governed by a |
| # BSD-style license that can be found in the LICENSE file. |
| |
| import os |
| import subprocess |
| import sys |
| |
| try: |
| from perfetto.trace_processor import TraceProcessor |
| except ImportError: |
| sys.exit( |
| 'Unable to analyze perfetto trace without the perfetto library. ' |
| 'Install instructions:\n' |
| ' sudo apt install python3-pip\n' |
| ' pip3 install perfetto') |
| |
| def ensure_record_android_trace(tmp_dir): |
| record_android_trace_path = os.path.join(tmp_dir, 'record_android_trace') |
| if not os.path.exists(record_android_trace_path): |
| cmd = [ |
| 'curl', |
| '--output', |
| record_android_trace_path, |
| '--silent', |
| 'https://raw.githubusercontent.com/google/perfetto/master/tools/' |
| 'record_android_trace' |
| ] |
| subprocess.check_call(cmd) |
| assert os.path.exists(record_android_trace_path) |
| return record_android_trace_path |
| |
| def record_android_trace(out_dir, tmp_dir, device_id=None): |
| record_android_trace_path = ensure_record_android_trace(tmp_dir) |
| config_path = os.path.join(os.path.dirname(__file__), 'config.pbtx') |
| perfetto_trace_path = os.path.join(out_dir, 'trace.perfetto-trace') |
| cmd = [ |
| sys.executable, |
| record_android_trace_path, |
| '--config', |
| config_path, |
| '--out', |
| perfetto_trace_path, |
| '--no-open'] |
| if device_id is not None: |
| cmd.extend(['--serial', device_id]) |
| perfetto_process = subprocess.Popen( |
| cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) |
| lines = [] |
| for line in perfetto_process.stdout: |
| line = line.decode('utf-8') |
| lines.append(line) |
| if 'enabled ftrace' in line.strip(): |
| return perfetto_process, perfetto_trace_path |
| raise ValueError( |
| 'Expected to find line containing: enabled ftrace, got: %s' % lines) |
| |
| def stop_record_android_trace(perfetto_process, out_dir): |
| if perfetto_process.poll() is not None: |
| raise ValueError('Expected perfetto process to be running') |
| # perfetto should terminate in at most 15 seconds, |
| perfetto_config_duration=15 |
| stdout, stderr = perfetto_process.communicate( |
| timeout=perfetto_config_duration*2) |
| stdout = stdout.decode('utf-8') |
| stderr = stderr.decode('utf-8') |
| assert perfetto_process.returncode == 0 |
| assert os.path.exists(os.path.join(out_dir, 'trace.perfetto-trace')) |
| |
| # https://perfetto.dev/docs/analysis/sql-tables |
| def find_slices_by_name(slice_name, options, trace_processor): |
| return trace_processor.query( |
| 'SELECT slice.dur, slice.ts FROM slice' |
| ' INNER JOIN thread_track ON (slice.track_id = thread_track.id)' |
| ' INNER JOIN thread using (utid)' |
| ' INNER JOIN process using (upid)' |
| ' WHERE slice.name = "%s"' |
| ' AND process.name = "%s"' |
| ' ORDER BY slice.ts ASC' |
| % (slice_name, options.app_id)) |
| |
| def find_unique_slice_by_name(slice_name, options, trace_processor): |
| query_it = find_slices_by_name(slice_name, options, trace_processor) |
| assert len(query_it) == 1 |
| return next(query_it) |
| |
| def get_slice_end_since_start(slice, initial_slice): |
| return (slice.ts + slice.dur - initial_slice.ts) / 1000000 |