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