| # Copyright 2013 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 logging |
| import os |
| import subprocess |
| import sys |
| import tempfile |
| |
| from telemetry.core import exceptions |
| from telemetry.internal.platform import profiler |
| from telemetry.internal.platform.profiler import android_profiling_helper |
| |
| |
| class _SingleProcessVTuneProfiler(object): |
| """An internal class for using vtune for a given process.""" |
| def __init__(self, pid, output_file, browser_backend, platform_backend): |
| self._pid = pid |
| self._browser_backend = browser_backend |
| self._platform_backend = platform_backend |
| self._output_file = output_file |
| self._tmp_output_file = tempfile.NamedTemporaryFile('w', 0) |
| cmd = ['amplxe-cl', '-collect', 'hotspots', |
| '-target-pid', str(pid), '-r', self._output_file] |
| self._is_android = platform_backend.GetOSName() == 'android' |
| if self._is_android: |
| cmd += ['-target-system', 'android'] |
| |
| self._proc = subprocess.Popen( |
| cmd, stdout=self._tmp_output_file, stderr=subprocess.STDOUT) |
| |
| def CollectProfile(self): |
| if 'renderer' in self._output_file: |
| try: |
| self._platform_backend.GetCommandLine(self._pid) |
| except exceptions.ProcessGoneException: |
| logging.warning('Renderer was swapped out during profiling. ' |
| 'To collect a full profile rerun with ' |
| '"--extra-browser-args=--single-process"') |
| subprocess.call(['amplxe-cl', '-command', 'stop', '-r', self._output_file]) |
| |
| exit_code = self._proc.wait() |
| try: |
| # 1: amplxe: Error: Cannot find a running process with the specified ID. |
| # Provide a valid PID. |
| if exit_code not in (0, 1): |
| raise Exception( |
| 'amplxe-cl failed with exit code %d. Output:\n%s' % (exit_code, |
| self._GetStdOut())) |
| finally: |
| self._tmp_output_file.close() |
| |
| if exit_code: |
| # The renderer process was swapped out. Now that we made sure VTune has |
| # stopped, return without further processing the invalid profile. |
| return self._output_file |
| |
| if self._is_android: |
| required_libs = \ |
| android_profiling_helper.GetRequiredLibrariesForVTuneProfile( |
| self._output_file) |
| |
| device = self._browser_backend.device |
| symfs_root = os.path.dirname(self._output_file) |
| android_profiling_helper.CreateSymFs(device, |
| symfs_root, |
| required_libs, |
| use_symlinks=True) |
| logging.info('Resolving symbols in profile.') |
| subprocess.call(['amplxe-cl', '-finalize', '-r', self._output_file, |
| '-search-dir', symfs_root]) |
| |
| print 'To view the profile, run:' |
| print ' amplxe-gui %s' % self._output_file |
| |
| return self._output_file |
| |
| def _GetStdOut(self): |
| self._tmp_output_file.flush() |
| try: |
| with open(self._tmp_output_file.name) as f: |
| return f.read() |
| except IOError: |
| return '' |
| |
| |
| class VTuneProfiler(profiler.Profiler): |
| |
| def __init__(self, browser_backend, platform_backend, output_path, state): |
| super(VTuneProfiler, self).__init__( |
| browser_backend, platform_backend, output_path, state) |
| process_output_file_map = self._GetProcessOutputFileMap() |
| self._process_profilers = [] |
| |
| has_renderer = False |
| for pid, output_file in process_output_file_map.iteritems(): |
| if 'renderer' in output_file: |
| has_renderer = True |
| break |
| |
| for pid, output_file in process_output_file_map.iteritems(): |
| if has_renderer: |
| if not 'renderer' in output_file: |
| continue |
| elif not 'browser0' in output_file: |
| continue |
| |
| self._process_profilers.append( |
| _SingleProcessVTuneProfiler(pid, output_file, browser_backend, |
| platform_backend)) |
| |
| @classmethod |
| def name(cls): |
| return 'vtune' |
| |
| @classmethod |
| def is_supported(cls, browser_type): |
| if sys.platform != 'linux2': |
| return False |
| if browser_type.startswith('cros'): |
| return False |
| try: |
| proc = subprocess.Popen(['amplxe-cl', '-version'], |
| stderr=subprocess.STDOUT, |
| stdout=subprocess.PIPE) |
| proc.communicate() |
| if proc.returncode != 0: |
| return False |
| |
| if browser_type.startswith('android'): |
| # VTune checks if 'su' is available on the device. |
| proc = subprocess.Popen(['adb', 'shell', 'su', '-c', 'id'], |
| stderr=subprocess.STDOUT, |
| stdout=subprocess.PIPE) |
| return 'not found' not in proc.communicate()[0] |
| |
| return True |
| except OSError: |
| return False |
| |
| @classmethod |
| def CustomizeBrowserOptions(cls, browser_type, options): |
| options.AppendExtraBrowserArgs([ |
| '--no-sandbox', |
| '--allow-sandbox-debugging', |
| ]) |
| |
| def CollectProfile(self): |
| print 'Processing profile, this will take a few minutes...' |
| |
| output_files = [] |
| for single_process in self._process_profilers: |
| output_files.append(single_process.CollectProfile()) |
| return output_files |