Chromium Code Reviews| Index: build/android/chrome_profiler/perf_controller.py |
| diff --git a/build/android/chrome_profiler/perf_controller.py b/build/android/chrome_profiler/perf_controller.py |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..175a8fd806a598dde99f3cbcfb62d87127910fc0 |
| --- /dev/null |
| +++ b/build/android/chrome_profiler/perf_controller.py |
| @@ -0,0 +1,142 @@ |
| +# Copyright 2014 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 os |
| +import subprocess |
| +import sys |
| +import tempfile |
| + |
| +from chrome_profiler import controllers |
| + |
| +from pylib import android_commands |
| +from pylib import constants |
| + |
| +sys.path.append(os.path.join(constants.DIR_SOURCE_ROOT, |
| + 'tools', |
| + 'telemetry')) |
| +# pylint: disable=F0401 |
| +from telemetry.core.platform.profiler import android_profiling_helper |
| +from telemetry.util import support_binaries |
| + |
| + |
| +_PERF_OPTIONS = [ |
| + '--all-cpus', |
| + '--call-graph', |
| + '--realtime', '80', |
| + '--raw-samples', |
| + '--freq', '2000', |
| +] |
| + |
| + |
| +class _PerfProfilerInstance(object): |
| + def __init__(self, device, perf_binary, categories): |
| + self._device = device |
| + self._output_file = android_commands.DeviceTempFile( |
| + self._device.old_interface, prefix='perf_output') |
| + self._log_file = tempfile.TemporaryFile() |
| + |
| + device_param = (['-s', self._device.old_interface.GetDevice()] |
| + if self._device.old_interface.GetDevice() else []) |
| + cmd = ['adb'] + device_param + \ |
| + ['shell', perf_binary, 'record', |
| + '--output', self._output_file.name] + _PERF_OPTIONS |
| + if categories: |
| + cmd += ['--event', ','.join(categories)] |
| + self._perf_process = subprocess.Popen(cmd, |
| + stdout=self._log_file, |
| + stderr=subprocess.STDOUT) |
| + |
| + def Wait(self): |
| + self._perf_process.wait() |
| + |
| + def _FailWithLog(self, msg): |
| + self._log_file.seek(0) |
| + log = self._log_file.read() |
| + raise RuntimeError('%s. Log output:\n%s' % (msg, log)) |
| + |
| + def PullResult(self, output_path): |
| + if not self._device.old_interface.FileExistsOnDevice( |
| + self._output_file.name): |
| + self._FailWithLog('Perf recorded no data') |
| + |
| + perf_profile = os.path.join(output_path, |
| + os.path.basename(self._output_file.name)) |
| + self._device.old_interface.PullFileFromDevice(self._output_file.name, |
| + perf_profile) |
| + if not os.stat(perf_profile).st_size: |
| + os.remove(perf_profile) |
| + self._FailWithLog('Perf recorded a zero-sized file') |
|
Dominik Grewe
2014/05/28 17:09:43
Can we also somehow check if there are any samples
Sami
2014/06/02 17:56:38
Great point, we should complain if the perf trace
|
| + |
| + self._log_file.close() |
| + self._output_file.close() |
| + return perf_profile |
| + |
| + |
| +class PerfProfilerController(controllers.BaseController): |
| + def __init__(self, device, categories): |
| + controllers.BaseController.__init__(self) |
| + self._device = device |
| + self._categories = categories |
| + self._perf_binary = self._PrepareDevice(device) |
| + self._perf_instance = None |
| + |
| + def __repr__(self): |
| + return 'perf profile' |
| + |
| + @staticmethod |
| + def _PrepareDevice(device): |
| + if not 'BUILDTYPE' in os.environ: |
| + os.environ['BUILDTYPE'] = 'Release' |
| + return android_profiling_helper.PrepareDeviceForPerf(device) |
| + |
| + @classmethod |
| + def GetCategories(cls, device): |
| + perf_binary = cls._PrepareDevice(device) |
| + return device.old_interface.RunShellCommand('%s list' % perf_binary) |
| + |
| + def StartTracing(self, _): |
| + self._perf_instance = _PerfProfilerInstance(self._device, |
| + self._perf_binary, |
| + self._categories) |
| + |
| + def StopTracing(self): |
| + if not self._perf_instance: |
| + return |
| + perf_pids = self._device.old_interface.ExtractPid('perf') |
|
Dominik Grewe
2014/05/28 17:09:43
Why not ask the perf profiler instance directly fo
Sami
2014/06/02 17:56:38
Good idea, that's much cleaner.
|
| + self._device.old_interface.RunShellCommand( |
| + 'kill -SIGINT ' + ' '.join(perf_pids)) |
| + self._perf_instance.Wait() |
| + |
| + def PullTrace(self): |
| + symfs_dir = os.path.join(tempfile.gettempdir(), |
| + os.path.expandvars('$USER-perf-symfs')) |
| + if not os.path.exists(symfs_dir): |
| + os.makedirs(symfs_dir) |
| + required_libs = set() |
| + |
| + # Download the recorded perf profile. |
| + perf_profile = self._perf_instance.PullResult(symfs_dir) |
| + required_libs = \ |
| + android_profiling_helper.GetRequiredLibrariesForPerfProfile( |
| + perf_profile) |
| + |
| + # Build a symfs with all the necessary libraries. |
| + kallsyms = android_profiling_helper.CreateSymFs(self._device, |
| + symfs_dir, |
| + required_libs, |
| + use_symlinks=False) |
| + # Convert the perf profile into JSON. |
| + perfhost_path = \ |
| + os.path.abspath(support_binaries.FindPath('perfhost', 'linux')) |
| + perf_script_path = \ |
| + os.path.join(constants.DIR_SOURCE_ROOT, |
| + 'tools', 'telemetry', 'telemetry', 'core', 'platform', |
| + 'profiler', 'perf_vis', 'perf_to_tracing.py') |
| + json_file_name = os.path.basename(perf_profile) |
| + with open(json_file_name, 'w') as json_file: |
| + cmd = [perfhost_path, 'script', '-s', perf_script_path, |
| + '-i', perf_profile, '--symfs', symfs_dir, '--kallsyms', |
| + kallsyms] |
| + subprocess.call(cmd, stdout=json_file) |
| + return json_file_name |