示例#1
0
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')
示例#2
0
 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 = {}
示例#3
0
 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
示例#4
0
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 __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
示例#7
0
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 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)
示例#10
0
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])
示例#11
0
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)
示例#12
0
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)
示例#13
0
def prepare_recording(args):
    adb = AdbHelper()
    enable_profiling_on_device(adb, args)
    upload_simpleperf_to_device(adb)
    run_simpleperf_prepare_cmd(adb)
示例#14
0
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')
示例#15
0
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)
示例#16
0
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)
示例#17
0
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)