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