def collect_machine_info(process): adb = AdbHelper() process.props = {} process.props['ro.product.model'] = adb.get_property('ro.product.model') process.props['ro.product.name'] = adb.get_property('ro.product.name') process.props['ro.product.manufacturer'] = adb.get_property( 'ro.product.manufacturer')
def __init__(self, ndk_path, disable_adb_root): self.adb = AdbHelper(enable_switch_to_root=not disable_adb_root) self.readelf = ReadElf(ndk_path) self.binary_cache_dir = 'binary_cache' if not os.path.isdir(self.binary_cache_dir): os.makedirs(self.binary_cache_dir) self.binaries = {}
def __init__(self, args): self.args = args self.adb = AdbHelper(enable_switch_to_root=not args.disable_adb_root) self.is_root_device = self.adb.switch_to_root() self.android_version = self.adb.get_android_version() if self.android_version < 7: log_exit("""app_profiler.py isn't supported on Android < N, please switch to use simpleperf binary directly.""") self.device_arch = self.adb.get_device_arch() self.record_subproc = None
def stop_recording(args): adb = AdbHelper() result = adb.run(['shell', 'pidof', 'simpleperf']) if not result: log_warning( 'No simpleperf process on device. The recording has ended.') else: adb.run(['shell', 'pkill', '-l', '2', 'simpleperf']) print('Waiting for simpleperf process to finish...') while adb.run(['shell', 'pidof', 'simpleperf']): time.sleep(1) adb.run(['shell', 'cat', '/data/local/tmp/simpleperf_output']) adb.check_run(['pull', '/data/local/tmp/perf.data', args.perf_data_path]) print('The recording data has been collected in %s.' % args.perf_data_path)
def stop_recording(args): adb = AdbHelper() result = adb.run(['shell', 'pidof', 'simpleperf']) if not result: log_warning('No simpleperf process on device. The recording has ended.') else: adb.run(['shell', 'pkill', '-l', '2', 'simpleperf']) print('Waiting for simpleperf process to finish...') while adb.run(['shell', 'pidof', 'simpleperf']): time.sleep(1) adb.run(['shell', 'cat', '/data/local/tmp/simpleperf_output']) adb.check_run(['pull', '/data/local/tmp/perf.data', args.perf_data_path]) print('The recording data has been collected in %s.' % args.perf_data_path)
def main(): disable_debug_log() adb = AdbHelper() device_arch = adb.get_device_arch() simpleperf_binary = get_target_binary_path(device_arch, 'simpleperf') adb.check_run(['push', simpleperf_binary, '/data/local/tmp']) adb.check_run(['shell', 'chmod', 'a+x', '/data/local/tmp/simpleperf']) shell_cmd = 'cd /data/local/tmp && ./simpleperf ' + ' '.join(sys.argv[1:]) sys.exit(subprocess.call([adb.adb_path, 'shell', shell_cmd]))
def start_recording(args): adb = AdbHelper() device_arch = adb.get_device_arch() simpleperf_binary = get_target_binary_path(device_arch, 'simpleperf') adb.check_run(['push', simpleperf_binary, '/data/local/tmp']) adb.check_run(['shell', 'chmod', 'a+x', '/data/local/tmp/simpleperf']) adb.check_run(['shell', 'rm', '-rf', '/data/local/tmp/perf.data', '/data/local/tmp/simpleperf_output']) shell_cmd = 'cd /data/local/tmp && nohup ./simpleperf record ' + args.record_options if args.app: shell_cmd += ' --app ' + args.app if args.size_limit: shell_cmd += ' --size-limit ' + args.size_limit shell_cmd += ' >/data/local/tmp/simpleperf_output 2>&1' print('shell_cmd: %s' % shell_cmd) subproc = subprocess.Popen([adb.adb_path, 'shell', shell_cmd]) # Wait 2 seconds to see if the simpleperf command fails to start. time.sleep(2) if subproc.poll() is None: print('Simpleperf recording has started. Please unplug the usb cable and run the app.') print('After that, run `%s stop` to get recording result.' % sys.argv[0]) else: adb.run(['shell', 'cat', '/data/local/tmp/simpleperf_output']) sys.exit(subproc.returncode)
class BinaryCacheBuilder(object): """Collect all binaries needed by perf.data in binary_cache.""" def __init__(self, ndk_path, disable_adb_root): self.adb = AdbHelper(enable_switch_to_root=not disable_adb_root) self.readelf = ReadElf(ndk_path) self.binary_cache_dir = 'binary_cache' if not os.path.isdir(self.binary_cache_dir): os.makedirs(self.binary_cache_dir) self.binaries = {} def build_binary_cache(self, perf_data_path, symfs_dirs): self._collect_used_binaries(perf_data_path) self.copy_binaries_from_symfs_dirs(symfs_dirs) self._pull_binaries_from_device() self._pull_kernel_symbols() def _collect_used_binaries(self, perf_data_path): """read perf.data, collect all used binaries and their build id (if available).""" # A dict mapping from binary name to build_id binaries = {} lib = ReportLib() lib.SetRecordFile(perf_data_path) lib.SetLogSeverity('error') while True: sample = lib.GetNextSample() if sample is None: lib.Close() break symbols = [lib.GetSymbolOfCurrentSample()] callchain = lib.GetCallChainOfCurrentSample() for i in range(callchain.nr): symbols.append(callchain.entries[i].symbol) for symbol in symbols: dso_name = symbol.dso_name if dso_name not in binaries: if is_jit_symfile(dso_name): continue binaries[dso_name] = lib.GetBuildIdForPath(dso_name) self.binaries = binaries def copy_binaries_from_symfs_dirs(self, symfs_dirs): """collect all files in symfs_dirs.""" if not symfs_dirs: return # It is possible that the path of the binary in symfs_dirs doesn't match # the one recorded in perf.data. For example, a file in symfs_dirs might # be "debug/arm/obj/armeabi-v7a/libsudo-game-jni.so", but the path in # perf.data is "/data/app/xxxx/lib/arm/libsudo-game-jni.so". So we match # binaries if they have the same filename (like libsudo-game-jni.so) # and same build_id. # Map from filename to binary paths. filename_dict = {} for binary in self.binaries: index = binary.rfind('/') filename = binary[index+1:] paths = filename_dict.get(filename) if paths is None: filename_dict[filename] = paths = [] paths.append(binary) # Walk through all files in symfs_dirs, and copy matching files to build_cache. for symfs_dir in symfs_dirs: for root, _, files in os.walk(symfs_dir): for filename in files: paths = filename_dict.get(filename) if not paths: continue build_id = self._read_build_id(os.path.join(root, filename)) if not build_id: continue for binary in paths: expected_build_id = self.binaries.get(binary) if expected_build_id == build_id: self._copy_to_binary_cache(os.path.join(root, filename), expected_build_id, binary) break def _copy_to_binary_cache(self, from_path, expected_build_id, target_file): if target_file[0] == '/': target_file = target_file[1:] target_file = target_file.replace('/', os.sep) target_file = os.path.join(self.binary_cache_dir, target_file) if not self._need_to_copy(from_path, target_file, expected_build_id): # The existing file in binary_cache can provide more information, so no need to copy. return target_dir = os.path.dirname(target_file) if not os.path.isdir(target_dir): os.makedirs(target_dir) log_info('copy to binary_cache: %s to %s' % (from_path, target_file)) shutil.copy(from_path, target_file) def _need_to_copy(self, source_file, target_file, expected_build_id): if not os.path.isfile(target_file): return True if self._read_build_id(target_file) != expected_build_id: return True return self._get_file_stripped_level(source_file) < self._get_file_stripped_level( target_file) def _get_file_stripped_level(self, file_path): """Return stripped level of an ELF file. Larger value means more stripped.""" sections = self.readelf.get_sections(file_path) if '.debug_line' in sections: return 0 if '.symtab' in sections: return 1 return 2 def _pull_binaries_from_device(self): """pull binaries needed in perf.data to binary_cache.""" for binary in self.binaries: build_id = self.binaries[binary] if not binary.startswith('/') or binary == "//anon" or binary.startswith("/dev/"): # [kernel.kallsyms] or unknown, or something we can't find binary. continue binary_cache_file = binary[1:].replace('/', os.sep) binary_cache_file = os.path.join(self.binary_cache_dir, binary_cache_file) self._check_and_pull_binary(binary, build_id, binary_cache_file) def _check_and_pull_binary(self, binary, expected_build_id, binary_cache_file): """If the binary_cache_file exists and has the expected_build_id, there is no need to pull the binary from device. Otherwise, pull it. """ need_pull = True if os.path.isfile(binary_cache_file): need_pull = False if expected_build_id: build_id = self._read_build_id(binary_cache_file) if expected_build_id != build_id: need_pull = True if need_pull: target_dir = os.path.dirname(binary_cache_file) if not os.path.isdir(target_dir): os.makedirs(target_dir) if os.path.isfile(binary_cache_file): os.remove(binary_cache_file) log_info('pull file to binary_cache: %s to %s' % (binary, binary_cache_file)) self._pull_file_from_device(binary, binary_cache_file) else: log_info('use current file in binary_cache: %s' % binary_cache_file) def _read_build_id(self, file_path): """read build id of a binary on host.""" return self.readelf.get_build_id(file_path) def _pull_file_from_device(self, device_path, host_path): if self.adb.run(['pull', device_path, host_path]): return True # In non-root device, we can't pull /data/app/XXX/base.odex directly. # Instead, we can first copy the file to /data/local/tmp, then pull it. filename = device_path[device_path.rfind('/')+1:] if (self.adb.run(['shell', 'cp', device_path, '/data/local/tmp']) and self.adb.run(['pull', '/data/local/tmp/' + filename, host_path])): self.adb.run(['shell', 'rm', '/data/local/tmp/' + filename]) return True log_warning('failed to pull %s from device' % device_path) return False def _pull_kernel_symbols(self): file_path = os.path.join(self.binary_cache_dir, 'kallsyms') if os.path.isfile(file_path): os.remove(file_path) if self.adb.switch_to_root(): self.adb.run(['shell', '"echo 0 >/proc/sys/kernel/kptr_restrict"']) self.adb.run(['pull', '/proc/kallsyms', file_path])
def main(): parser = argparse.ArgumentParser( description='Report samples in perf.data.') parser.add_argument( '--symfs', help="""Set the path to find binaries with symbols and debug info.""") parser.add_argument('--kallsyms', help='Set the path to find kernel symbols.') parser.add_argument('--record_file', default='perf.data', help='Default is perf.data.') parser.add_argument('-t', '--capture_duration', type=int, default=10, help="""Capture duration in seconds.""") parser.add_argument('-p', '--app', help="""Profile an Android app, given the package name. Like -p com.example.android.myapp.""") parser.add_argument( '-np', '--native_program', default="surfaceflinger", help="""Profile a native program. The program should be running on the device. Like -np surfaceflinger.""") parser.add_argument( '-c', '--color', default='hot', choices=['hot', 'dso', 'legacy'], help="""Color theme: hot=percentage of samples, dso=callsite DSO name, legacy=brendan style""") parser.add_argument('-sc', '--skip_collection', default=False, help='Skip data collection', action="store_true") parser.add_argument('-nc', '--skip_recompile', action='store_true', help="""When profiling an Android app, by default we recompile java bytecode to native instructions to profile java code. It takes some time. You can skip it if the code has been compiled or you don't need to profile java code.""" ) parser.add_argument('-f', '--sample_frequency', type=int, default=6000, help='Sample frequency') parser.add_argument('-du', '--dwarf_unwinding', help='Perform unwinding using dwarf instead of fp.', default=False, action='store_true') parser.add_argument('-e', '--events', help="""Sample based on event occurences instead of frequency. Format expected is "event_counts event_name". e.g: "10000 cpu-cyles". A few examples of event_name: cpu-cycles, cache-references, cache-misses, branch-instructions, branch-misses""", default="") parser.add_argument('--disable_adb_root', action='store_true', help="""Force adb to run in non root mode.""") parser.add_argument('-o', '--report_path', default='report.html', help="Set report path.") parser.add_argument('--embedded_flamegraph', action='store_true', help=""" Generate embedded flamegraph.""") parser.add_argument('--min_callchain_percentage', default=0.01, type=float, help=""" Set min percentage of callchains shown in the report. It is used to limit nodes shown in the flamegraph. For example, when set to 0.01, only callchains taking >= 0.01%% of the event count of the owner thread are collected in the report.""") parser.add_argument('--no_browser', action='store_true', help="Don't open report in browser.") args = parser.parse_args() process = Process("", 0) if not args.skip_collection: process.name = args.app or args.native_program log_info("Starting data collection stage for process '%s'." % process.name) if not collect_data(args): log_exit("Unable to collect data.") result, output = AdbHelper().run_and_return_output( ['shell', 'pidof', process.name]) if result: try: process.pid = int(output) except: process.pid = 0 collect_machine_info(process) else: args.capture_duration = 0 parse_samples(process, args) generate_threads_offsets(process) report_path = output_report(process, args) if not args.no_browser: open_report_in_browser(report_path) log_info("Flamegraph generated at '%s'." % report_path)
def main(): # Allow deep callchain with length >1000. sys.setrecursionlimit(1500) parser = argparse.ArgumentParser(description="""Report samples in perf.data. Default option is: "-np surfaceflinger -f 6000 -t 10".""") record_group = parser.add_argument_group('Record options') record_group.add_argument('-du', '--dwarf_unwinding', action='store_true', help="""Perform unwinding using dwarf instead of fp.""") record_group.add_argument('-e', '--events', default="", help="""Sample based on event occurences instead of frequency. Format expected is "event_counts event_name". e.g: "10000 cpu-cyles". A few examples of event_name: cpu-cycles, cache-references, cache-misses, branch-instructions, branch-misses""") record_group.add_argument('-f', '--sample_frequency', type=int, default=6000, help="""Sample frequency""") record_group.add_argument('--compile_java_code', action='store_true', help="""On Android N and Android O, we need to compile Java code into native instructions to profile Java code. Android O also needs wrap.sh in the apk to use the native instructions.""") record_group.add_argument('-np', '--native_program', default="surfaceflinger", help="""Profile a native program. The program should be running on the device. Like -np surfaceflinger.""") record_group.add_argument('-p', '--app', help="""Profile an Android app, given the package name. Like -p com.example.android.myapp.""") record_group.add_argument('--pid', type=int, default=-1, help="""Profile a native program with given pid, the pid should exist on the device.""") record_group.add_argument('--record_file', default='perf.data', help='Default is perf.data.') record_group.add_argument('-sc', '--skip_collection', action='store_true', help="""Skip data collection""") record_group.add_argument('--system_wide', action='store_true', help='Profile system wide.') record_group.add_argument('-t', '--capture_duration', type=int, default=10, help="""Capture duration in seconds.""") report_group = parser.add_argument_group('Report options') report_group.add_argument('-c', '--color', default='hot', choices=['hot', 'dso', 'legacy'], help="""Color theme: hot=percentage of samples, dso=callsite DSO name, legacy=brendan style""") report_group.add_argument('--embedded_flamegraph', action='store_true', help="""Generate embedded flamegraph.""") report_group.add_argument('--kallsyms', help='Set the path to find kernel symbols.') report_group.add_argument('--min_callchain_percentage', default=0.01, type=float, help=""" Set min percentage of callchains shown in the report. It is used to limit nodes shown in the flamegraph. For example, when set to 0.01, only callchains taking >= 0.01%% of the event count of the owner thread are collected in the report.""") report_group.add_argument('--no_browser', action='store_true', help="""Don't open report in browser.""") report_group.add_argument('-o', '--report_path', default='report.html', help="""Set report path.""") report_group.add_argument('--one-flamegraph', action='store_true', help="""Generate one flamegraph instead of one for each thread.""") report_group.add_argument('--symfs', help="""Set the path to find binaries with symbols and debug info.""") report_group.add_argument('--title', help='Show a title in the report.') report_group.add_argument('--show_art_frames', action='store_true', help='Show frames of internal methods in the ART Java interpreter.') debug_group = parser.add_argument_group('Debug options') debug_group.add_argument('--disable_adb_root', action='store_true', help="""Force adb to run in non root mode.""") args = parser.parse_args() process = Process("", 0) if not args.skip_collection: if args.pid != -1: process.pid = args.pid args.native_program = '' if args.system_wide: process.pid = -1 args.native_program = '' if args.system_wide: process.name = 'system_wide' else: process.name = args.app or args.native_program or ('Process %d' % args.pid) log_info("Starting data collection stage for '%s'." % process.name) if not collect_data(args): log_exit("Unable to collect data.") if process.pid == 0: result, output = AdbHelper().run_and_return_output(['shell', 'pidof', process.name]) if result: try: process.pid = int(output) except ValueError: process.pid = 0 collect_machine_info(process) else: args.capture_duration = 0 sample_filter_fn = None if args.one_flamegraph: def filter_fn(sample, _symbol, _callchain): sample.pid = sample.tid = process.pid return True sample_filter_fn = filter_fn if not args.title: args.title = '' args.title += '(One Flamegraph)' parse_samples(process, args, sample_filter_fn) generate_threads_offsets(process) report_path = output_report(process, args) if not args.no_browser: open_report_in_browser(report_path) log_info("Flamegraph generated at '%s'." % report_path)
def prepare_recording(args): adb = AdbHelper() enable_profiling_on_device(adb, args) upload_simpleperf_to_device(adb) run_simpleperf_prepare_cmd(adb)
def collect_machine_info(process): adb = AdbHelper() process.props = {} process.props['ro.product.model'] = adb.get_property('ro.product.model') process.props['ro.product.name'] = adb.get_property('ro.product.name') process.props['ro.product.manufacturer'] = adb.get_property('ro.product.manufacturer')
class ProfilerBase(object): """Base class of all Profilers.""" def __init__(self, args): self.args = args self.adb = AdbHelper(enable_switch_to_root=not args.disable_adb_root) self.is_root_device = self.adb.switch_to_root() self.android_version = self.adb.get_android_version() if self.android_version < 7: log_exit( """app_profiler.py isn't supported on Android < N, please switch to use simpleperf binary directly.""") self.device_arch = self.adb.get_device_arch() self.record_subproc = None def profile(self): log_info('prepare profiling') self.prepare() log_info('start profiling') self.start() self.wait_profiling() log_info('collect profiling data') self.collect_profiling_data() log_info('profiling is finished.') def prepare(self): """Prepare recording. """ self.download_simpleperf() if self.args.native_lib_dir: self.download_libs() def download_simpleperf(self): simpleperf_binary = get_target_binary_path(self.device_arch, 'simpleperf') self.adb.check_run(['push', simpleperf_binary, '/data/local/tmp']) self.adb.check_run( ['shell', 'chmod', 'a+x', '/data/local/tmp/simpleperf']) def download_libs(self): downloader = NativeLibDownloader(self.args.ndk_path, self.device_arch, self.adb) downloader.collect_native_libs_on_host(self.args.native_lib_dir) downloader.collect_native_libs_on_device() downloader.sync_native_libs_on_device() def start(self): raise NotImplementedError def start_profiling(self, target_args): """Start simpleperf reocrd process on device.""" args = [ '/data/local/tmp/simpleperf', 'record', '-o', '/data/local/tmp/perf.data', self.args.record_options ] if self.adb.run( ['shell', 'ls', NATIVE_LIBS_DIR_ON_DEVICE, '>/dev/null', '2>&1']): args += ['--symfs', NATIVE_LIBS_DIR_ON_DEVICE] args += ['--log', self.args.log] args += target_args adb_args = [self.adb.adb_path, 'shell'] + args log_info('run adb cmd: %s' % adb_args) self.record_subproc = subprocess.Popen(adb_args) def wait_profiling(self): """Wait until profiling finishes, or stop profiling when user presses Ctrl-C.""" returncode = None try: returncode = self.record_subproc.wait() except KeyboardInterrupt: self.stop_profiling() self.record_subproc = None # Don't check return value of record_subproc. Because record_subproc also # receives Ctrl-C, and always returns non-zero. returncode = 0 log_debug('profiling result [%s]' % (returncode == 0)) if returncode != 0: log_exit('Failed to record profiling data.') def stop_profiling(self): """Stop profiling by sending SIGINT to simpleperf, and wait until it exits to make sure perf.data is completely generated.""" has_killed = False while True: (result, _) = self.adb.run_and_return_output( ['shell', 'pidof', 'simpleperf']) if not result: break if not has_killed: has_killed = True self.adb.run_and_return_output( ['shell', 'pkill', '-l', '2', 'simpleperf']) time.sleep(1) def collect_profiling_data(self): self.adb.check_run_and_return_output( ['pull', '/data/local/tmp/perf.data', self.args.perf_data_path]) if not self.args.skip_collect_binaries: binary_cache_args = [ sys.executable, os.path.join(get_script_dir(), 'binary_cache_builder.py') ] binary_cache_args += [ '-i', self.args.perf_data_path, '--log', self.args.log ] if self.args.native_lib_dir: binary_cache_args += ['-lib', self.args.native_lib_dir] if self.args.disable_adb_root: binary_cache_args += ['--disable_adb_root'] if self.args.ndk_path: binary_cache_args += ['--ndk_path', self.args.ndk_path] subprocess.check_call(binary_cache_args)
def collect_data(args): adb = AdbHelper() if not os.path.isdir(args.out_dir): os.makedirs(args.out_dir) download_recording_data(adb, args) unzip_recording_data(args)
def start_recording(args): adb = AdbHelper() device_arch = adb.get_device_arch() simpleperf_binary = get_target_binary_path(device_arch, 'simpleperf') adb.check_run(['push', simpleperf_binary, '/data/local/tmp']) adb.check_run(['shell', 'chmod', 'a+x', '/data/local/tmp/simpleperf']) adb.check_run([ 'shell', 'rm', '-rf', '/data/local/tmp/perf.data', '/data/local/tmp/simpleperf_output' ]) shell_cmd = 'cd /data/local/tmp && nohup ./simpleperf record ' + args.record_options if args.app: shell_cmd += ' --app ' + args.app if args.size_limit: shell_cmd += ' --size-limit ' + args.size_limit shell_cmd += ' >/data/local/tmp/simpleperf_output 2>&1' print('shell_cmd: %s' % shell_cmd) subproc = subprocess.Popen([adb.adb_path, 'shell', shell_cmd]) # Wait 2 seconds to see if the simpleperf command fails to start. time.sleep(2) if subproc.poll() is None: print( 'Simpleperf recording has started. Please unplug the usb cable and run the app.' ) print('After that, run `%s stop` to get recording result.' % sys.argv[0]) else: adb.run(['shell', 'cat', '/data/local/tmp/simpleperf_output']) sys.exit(subproc.returncode)
class ProfilerBase(object): """Base class of all Profilers.""" def __init__(self, args): self.args = args self.adb = AdbHelper(enable_switch_to_root=not args.disable_adb_root) self.is_root_device = self.adb.switch_to_root() self.android_version = self.adb.get_android_version() if self.android_version < 7: log_exit("""app_profiler.py isn't supported on Android < N, please switch to use simpleperf binary directly.""") self.device_arch = self.adb.get_device_arch() self.record_subproc = None def profile(self): log_info('prepare profiling') self.prepare() log_info('start profiling') self.start() self.wait_profiling() log_info('collect profiling data') self.collect_profiling_data() log_info('profiling is finished.') def prepare(self): """Prepare recording. """ self.download_simpleperf() if self.args.native_lib_dir: self.download_libs() def download_simpleperf(self): simpleperf_binary = get_target_binary_path(self.device_arch, 'simpleperf') self.adb.check_run(['push', simpleperf_binary, '/data/local/tmp']) self.adb.check_run(['shell', 'chmod', 'a+x', '/data/local/tmp/simpleperf']) def download_libs(self): downloader = NativeLibDownloader(self.args.ndk_path, self.device_arch, self.adb) downloader.collect_native_libs_on_host(self.args.native_lib_dir) downloader.collect_native_libs_on_device() downloader.sync_natives_libs_on_device() def start(self): raise NotImplementedError def start_profiling(self, target_args): """Start simpleperf reocrd process on device.""" args = ['/data/local/tmp/simpleperf', 'record', '-o', '/data/local/tmp/perf.data', self.args.record_options] if self.adb.run(['shell', 'ls', NATIVE_LIBS_DIR_ON_DEVICE]): args += ['--symfs', NATIVE_LIBS_DIR_ON_DEVICE] args += target_args adb_args = [self.adb.adb_path, 'shell'] + args log_debug('run adb cmd: %s' % adb_args) self.record_subproc = subprocess.Popen(adb_args) def wait_profiling(self): """Wait until profiling finishes, or stop profiling when user presses Ctrl-C.""" returncode = None try: returncode = self.record_subproc.wait() except KeyboardInterrupt: self.stop_profiling() self.record_subproc = None # Don't check return value of record_subproc. Because record_subproc also # receives Ctrl-C, and always returns non-zero. returncode = 0 log_debug('profiling result [%s]' % (returncode == 0)) if returncode != 0: log_exit('Failed to record profiling data.') def stop_profiling(self): """Stop profiling by sending SIGINT to simpleperf, and wait until it exits to make sure perf.data is completely generated.""" has_killed = False while True: (result, _) = self.adb.run_and_return_output(['shell', 'pidof', 'simpleperf']) if not result: break if not has_killed: has_killed = True self.adb.run_and_return_output(['shell', 'pkill', '-l', '2', 'simpleperf']) time.sleep(1) def collect_profiling_data(self): self.adb.check_run_and_return_output(['pull', '/data/local/tmp/perf.data', self.args.perf_data_path]) if not self.args.skip_collect_binaries: binary_cache_args = [sys.executable, os.path.join(get_script_dir(), 'binary_cache_builder.py')] binary_cache_args += ['-i', self.args.perf_data_path] if self.args.native_lib_dir: binary_cache_args += ['-lib', self.args.native_lib_dir] if self.args.disable_adb_root: binary_cache_args += ['--disable_adb_root'] if self.args.ndk_path: binary_cache_args += ['--ndk_path', self.args.ndk_path] subprocess.check_call(binary_cache_args)