Home | History | Annotate | Download | only in profiler
      1 # Copyright 2013 The Chromium Authors. All rights reserved.
      2 # Use of this source code is governed by a BSD-style license that can be
      3 # found in the LICENSE file.
      4 
      5 import logging
      6 import os
      7 import subprocess
      8 import sys
      9 import tempfile
     10 
     11 from telemetry.core import exceptions
     12 from telemetry.internal.platform import profiler
     13 from telemetry.internal.platform.profiler import android_profiling_helper
     14 
     15 from devil.android.sdk import adb_wrapper
     16 
     17 
     18 class _SingleProcessVTuneProfiler(object):
     19   """An internal class for using vtune for a given process."""
     20   def __init__(self, pid, output_file, browser_backend, platform_backend):
     21     self._pid = pid
     22     self._browser_backend = browser_backend
     23     self._platform_backend = platform_backend
     24     self._output_file = output_file
     25     self._tmp_output_file = tempfile.NamedTemporaryFile('w', 0)
     26     cmd = ['amplxe-cl', '-collect', 'hotspots',
     27            '-target-pid', str(pid), '-r', self._output_file]
     28     self._is_android = platform_backend.GetOSName() == 'android'
     29     if self._is_android:
     30       cmd += ['-target-system', 'android']
     31 
     32     self._proc = subprocess.Popen(
     33         cmd, stdout=self._tmp_output_file, stderr=subprocess.STDOUT)
     34 
     35   def CollectProfile(self):
     36     if 'renderer' in self._output_file:
     37       try:
     38         self._platform_backend.GetCommandLine(self._pid)
     39       except exceptions.ProcessGoneException:
     40         logging.warning('Renderer was swapped out during profiling. '
     41                         'To collect a full profile rerun with '
     42                         '"--extra-browser-args=--single-process"')
     43     subprocess.call(['amplxe-cl', '-command', 'stop', '-r', self._output_file])
     44 
     45     exit_code = self._proc.wait()
     46     try:
     47       # 1: amplxe: Error: Cannot find a running process with the specified ID.
     48       #    Provide a valid PID.
     49       if exit_code not in (0, 1):
     50         raise Exception(
     51             'amplxe-cl failed with exit code %d. Output:\n%s' % (exit_code,
     52             self._GetStdOut()))
     53     finally:
     54       self._tmp_output_file.close()
     55 
     56     if exit_code:
     57       # The renderer process was swapped out. Now that we made sure VTune has
     58       # stopped, return without further processing the invalid profile.
     59       return self._output_file
     60 
     61     if self._is_android:
     62       required_libs = \
     63           android_profiling_helper.GetRequiredLibrariesForVTuneProfile(
     64               self._output_file)
     65 
     66       device = self._browser_backend.device
     67       symfs_root = os.path.join(os.path.dirname(self._output_file), 'symfs')
     68       if not os.path.exists(symfs_root):
     69         os.makedirs(symfs_root)
     70       android_profiling_helper.CreateSymFs(device,
     71                                            symfs_root,
     72                                            required_libs,
     73                                            use_symlinks=True)
     74       logging.info('Resolving symbols in profile.')
     75       subprocess.call(['amplxe-cl', '-finalize', '-r', self._output_file,
     76                        '-search-dir', symfs_root])
     77 
     78     print 'To view the profile, run:'
     79     print '  amplxe-gui %s' % self._output_file
     80 
     81     return self._output_file
     82 
     83   def _GetStdOut(self):
     84     self._tmp_output_file.flush()
     85     try:
     86       with open(self._tmp_output_file.name) as f:
     87         return f.read()
     88     except IOError:
     89       return ''
     90 
     91 
     92 class VTuneProfiler(profiler.Profiler):
     93 
     94   def __init__(self, browser_backend, platform_backend, output_path, state):
     95     super(VTuneProfiler, self).__init__(
     96         browser_backend, platform_backend, output_path, state)
     97     process_output_file_map = self._GetProcessOutputFileMap()
     98     self._process_profilers = []
     99 
    100     has_renderer = False
    101     for pid, output_file in process_output_file_map.iteritems():
    102       if 'renderer' in output_file:
    103         has_renderer = True
    104         break
    105 
    106     for pid, output_file in process_output_file_map.iteritems():
    107       if has_renderer:
    108         if not 'renderer' in output_file:
    109           continue
    110       elif not 'browser0' in output_file:
    111         continue
    112 
    113       self._process_profilers.append(
    114           _SingleProcessVTuneProfiler(pid, output_file, browser_backend,
    115                                       platform_backend))
    116 
    117   @classmethod
    118   def name(cls):
    119     return 'vtune'
    120 
    121   @classmethod
    122   def is_supported(cls, browser_type):
    123     if sys.platform != 'linux2':
    124       return False
    125     if browser_type.startswith('cros'):
    126       return False
    127     try:
    128       proc = subprocess.Popen(['amplxe-cl', '-version'],
    129                               stderr=subprocess.STDOUT,
    130                               stdout=subprocess.PIPE)
    131       proc.communicate()
    132       if proc.returncode != 0:
    133         return False
    134 
    135       if browser_type.startswith('android'):
    136         # VTune checks if 'su' is available on the device.
    137         proc = subprocess.Popen(
    138             [adb_wrapper.AdbWrapper.GetAdbPath(),
    139              'shell', 'su', '-c', 'id'],
    140             stderr=subprocess.STDOUT,
    141             stdout=subprocess.PIPE)
    142         return 'not found' not in proc.communicate()[0]
    143 
    144       return True
    145     except OSError:
    146       return False
    147 
    148   @classmethod
    149   def CustomizeBrowserOptions(cls, browser_type, options):
    150     options.AppendExtraBrowserArgs([
    151         '--no-sandbox',
    152         '--allow-sandbox-debugging',
    153     ])
    154 
    155   def CollectProfile(self):
    156     print 'Processing profile, this will take a few minutes...'
    157 
    158     output_files = []
    159     for single_process in self._process_profilers:
    160       output_files.append(single_process.CollectProfile())
    161     return output_files
    162