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