diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:22:09 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:22:09 +0000 |
commit | 43a97878ce14b72f0981164f87f2e35e14151312 (patch) | |
tree | 620249daf56c0258faa40cbdcf9cfba06de2a846 /third_party/libwebrtc/build/android/pylib/utils/simpleperf.py | |
parent | Initial commit. (diff) | |
download | firefox-43a97878ce14b72f0981164f87f2e35e14151312.tar.xz firefox-43a97878ce14b72f0981164f87f2e35e14151312.zip |
Adding upstream version 110.0.1.upstream/110.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'third_party/libwebrtc/build/android/pylib/utils/simpleperf.py')
-rw-r--r-- | third_party/libwebrtc/build/android/pylib/utils/simpleperf.py | 260 |
1 files changed, 260 insertions, 0 deletions
diff --git a/third_party/libwebrtc/build/android/pylib/utils/simpleperf.py b/third_party/libwebrtc/build/android/pylib/utils/simpleperf.py new file mode 100644 index 0000000000..b3ba00e6c2 --- /dev/null +++ b/third_party/libwebrtc/build/android/pylib/utils/simpleperf.py @@ -0,0 +1,260 @@ +# Copyright 2018 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +import contextlib +import os +import shutil +import subprocess +import sys +import tempfile + +from devil import devil_env +from devil.android import device_signal +from devil.android.sdk import version_codes +from pylib import constants + + +def _ProcessType(proc): + _, _, suffix = proc.name.partition(':') + if not suffix: + return 'browser' + if suffix.startswith('sandboxed_process'): + return 'renderer' + if suffix.startswith('privileged_process'): + return 'gpu' + return None + + +def _GetSpecifiedPID(device, package_name, process_specifier): + if process_specifier is None: + return None + + # Check for numeric PID + try: + pid = int(process_specifier) + return pid + except ValueError: + pass + + # Check for exact process name; can be any of these formats: + # <package>:<process name>, i.e. 'org.chromium.chrome:sandboxed_process0' + # :<process name>, i.e. ':sandboxed_process0' + # <process name>, i.e. 'sandboxed_process0' + full_process_name = process_specifier + if process_specifier.startswith(':'): + full_process_name = package_name + process_specifier + elif ':' not in process_specifier: + full_process_name = '%s:%s' % (package_name, process_specifier) + matching_processes = device.ListProcesses(full_process_name) + if len(matching_processes) == 1: + return matching_processes[0].pid + if len(matching_processes) > 1: + raise RuntimeError('Found %d processes with name "%s".' % ( + len(matching_processes), process_specifier)) + + # Check for process type (i.e. 'renderer') + package_processes = device.ListProcesses(package_name) + matching_processes = [p for p in package_processes if ( + _ProcessType(p) == process_specifier)] + if process_specifier == 'renderer' and len(matching_processes) > 1: + raise RuntimeError('Found %d renderer processes; please re-run with only ' + 'one open tab.' % len(matching_processes)) + if len(matching_processes) != 1: + raise RuntimeError('Found %d processes of type "%s".' % ( + len(matching_processes), process_specifier)) + return matching_processes[0].pid + + +def _ThreadsForProcess(device, pid): + # The thread list output format for 'ps' is the same regardless of version. + # Here's the column headers, and a sample line for a thread belonging to + # pid 12345 (note that the last few columns are not aligned with headers): + # + # USER PID TID PPID VSZ RSS WCHAN ADDR S CMD + # u0_i101 12345 24680 567 1357902 97531 futex_wait_queue_me e85acd9c S \ + # CrRendererMain + if device.build_version_sdk >= version_codes.OREO: + pid_regex = ( + r'^[[:graph:]]\{1,\}[[:blank:]]\{1,\}%d[[:blank:]]\{1,\}' % pid) + ps_cmd = "ps -T -e | grep '%s'" % pid_regex + ps_output_lines = device.RunShellCommand( + ps_cmd, shell=True, check_return=True) + else: + ps_cmd = ['ps', '-p', str(pid), '-t'] + ps_output_lines = device.RunShellCommand(ps_cmd, check_return=True) + result = [] + for l in ps_output_lines: + fields = l.split() + # fields[2] is tid, fields[-1] is thread name. Output may include an entry + # for the process itself with tid=pid; omit that one. + if fields[2] == str(pid): + continue + result.append((int(fields[2]), fields[-1])) + return result + + +def _ThreadType(thread_name): + if not thread_name: + return 'unknown' + if (thread_name.startswith('Chrome_ChildIO') or + thread_name.startswith('Chrome_IO')): + return 'io' + if thread_name.startswith('Compositor'): + return 'compositor' + if (thread_name.startswith('ChildProcessMai') or + thread_name.startswith('CrGpuMain') or + thread_name.startswith('CrRendererMain')): + return 'main' + if thread_name.startswith('RenderThread'): + return 'render' + + +def _GetSpecifiedTID(device, pid, thread_specifier): + if thread_specifier is None: + return None + + # Check for numeric TID + try: + tid = int(thread_specifier) + return tid + except ValueError: + pass + + # Check for thread type + if pid is not None: + matching_threads = [t for t in _ThreadsForProcess(device, pid) if ( + _ThreadType(t[1]) == thread_specifier)] + if len(matching_threads) != 1: + raise RuntimeError('Found %d threads of type "%s".' % ( + len(matching_threads), thread_specifier)) + return matching_threads[0][0] + + return None + + +def PrepareDevice(device): + if device.build_version_sdk < version_codes.NOUGAT: + raise RuntimeError('Simpleperf profiling is only supported on Android N ' + 'and later.') + + # Necessary for profiling + # https://android-review.googlesource.com/c/platform/system/sepolicy/+/234400 + device.SetProp('security.perf_harden', '0') + + +def InstallSimpleperf(device, package_name): + package_arch = device.GetPackageArchitecture(package_name) or 'armeabi-v7a' + host_simpleperf_path = devil_env.config.LocalPath('simpleperf', package_arch) + if not host_simpleperf_path: + raise Exception('Could not get path to simpleperf executable on host.') + device_simpleperf_path = '/'.join( + ('/data/local/tmp/profilers', package_arch, 'simpleperf')) + device.PushChangedFiles([(host_simpleperf_path, device_simpleperf_path)]) + return device_simpleperf_path + + +@contextlib.contextmanager +def RunSimpleperf(device, device_simpleperf_path, package_name, + process_specifier, thread_specifier, profiler_args, + host_out_path): + pid = _GetSpecifiedPID(device, package_name, process_specifier) + tid = _GetSpecifiedTID(device, pid, thread_specifier) + if pid is None and tid is None: + raise RuntimeError('Could not find specified process/thread running on ' + 'device. Make sure the apk is already running before ' + 'attempting to profile.') + profiler_args = list(profiler_args) + if profiler_args and profiler_args[0] == 'record': + profiler_args.pop(0) + if '--call-graph' not in profiler_args and '-g' not in profiler_args: + profiler_args.append('-g') + if '-f' not in profiler_args: + profiler_args.extend(('-f', '1000')) + device_out_path = '/data/local/tmp/perf.data' + if '-o' in profiler_args: + device_out_path = profiler_args[profiler_args.index('-o') + 1] + else: + profiler_args.extend(('-o', device_out_path)) + + if tid: + profiler_args.extend(('-t', str(tid))) + else: + profiler_args.extend(('-p', str(pid))) + + adb_shell_simpleperf_process = device.adb.StartShell( + [device_simpleperf_path, 'record'] + profiler_args) + + completed = False + try: + yield + completed = True + + finally: + device.KillAll('simpleperf', signum=device_signal.SIGINT, blocking=True, + quiet=True) + if completed: + adb_shell_simpleperf_process.wait() + device.PullFile(device_out_path, host_out_path) + + +def ConvertSimpleperfToPprof(simpleperf_out_path, build_directory, + pprof_out_path): + # The simpleperf scripts require the unstripped libs to be installed in the + # same directory structure as the libs on the device. Much of the logic here + # is just figuring out and creating the necessary directory structure, and + # symlinking the unstripped shared libs. + + # Get the set of libs that we can symbolize + unstripped_lib_dir = os.path.join(build_directory, 'lib.unstripped') + unstripped_libs = set( + f for f in os.listdir(unstripped_lib_dir) if f.endswith('.so')) + + # report.py will show the directory structure above the shared libs; + # that is the directory structure we need to recreate on the host. + script_dir = devil_env.config.LocalPath('simpleperf_scripts') + report_path = os.path.join(script_dir, 'report.py') + report_cmd = [sys.executable, report_path, '-i', simpleperf_out_path] + device_lib_path = None + for line in subprocess.check_output( + report_cmd, stderr=subprocess.STDOUT).splitlines(): + fields = line.split() + if len(fields) < 5: + continue + shlib_path = fields[4] + shlib_dirname, shlib_basename = shlib_path.rpartition('/')[::2] + if shlib_basename in unstripped_libs: + device_lib_path = shlib_dirname + break + if not device_lib_path: + raise RuntimeError('No chrome-related symbols in profiling data in %s. ' + 'Either the process was idle for the entire profiling ' + 'period, or something went very wrong (and you should ' + 'file a bug at crbug.com/new with component ' + 'Speed>Tracing, and assign it to szager@chromium.org).' + % simpleperf_out_path) + + # Recreate the directory structure locally, and symlink unstripped libs. + processing_dir = tempfile.mkdtemp() + try: + processing_lib_dir = os.path.join( + processing_dir, 'binary_cache', device_lib_path.lstrip('/')) + os.makedirs(processing_lib_dir) + for lib in unstripped_libs: + unstripped_lib_path = os.path.join(unstripped_lib_dir, lib) + processing_lib_path = os.path.join(processing_lib_dir, lib) + os.symlink(unstripped_lib_path, processing_lib_path) + + # Run the script to annotate symbols and convert from simpleperf format to + # pprof format. + pprof_converter_script = os.path.join( + script_dir, 'pprof_proto_generator.py') + pprof_converter_cmd = [ + sys.executable, pprof_converter_script, '-i', simpleperf_out_path, '-o', + os.path.abspath(pprof_out_path), '--ndk_path', + constants.ANDROID_NDK_ROOT + ] + subprocess.check_output(pprof_converter_cmd, stderr=subprocess.STDOUT, + cwd=processing_dir) + finally: + shutil.rmtree(processing_dir, ignore_errors=True) |