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 signal 6import subprocess 7import sys 8import tempfile 9 10from telemetry.core import exceptions 11from telemetry.core import util 12from telemetry.internal.platform import profiler 13 14 15class _SingleProcessSampleProfiler(object): 16 """An internal class for using iprofiler for a given process.""" 17 def __init__(self, pid, output_path): 18 self._output_path = output_path 19 self._tmp_output_file = tempfile.NamedTemporaryFile('w', 0) 20 self._proc = subprocess.Popen( 21 ['sample', str(pid), '-mayDie', '-file', self._output_path], 22 stdout=self._tmp_output_file, stderr=subprocess.STDOUT) 23 def IsStarted(): 24 stdout = self._GetStdOut() 25 if 'sample cannot examine process' in stdout: 26 raise exceptions.ProfilingException( 27 'Failed to start sample for process %s\n' % 28 self._output_path.split('.')[1]) 29 return 'Sampling process' in stdout 30 util.WaitFor(IsStarted, 120) 31 32 def CollectProfile(self): 33 self._proc.send_signal(signal.SIGINT) 34 exit_code = self._proc.wait() 35 try: 36 if exit_code: 37 raise Exception( 38 'sample failed with exit code %d. Output:\n%s' % ( 39 exit_code, self._GetStdOut())) 40 finally: 41 self._proc = None 42 self._tmp_output_file.close() 43 44 print 'To view the profile, run:' 45 print ' open -a TextEdit %s' % self._output_path 46 47 return self._output_path 48 49 def _GetStdOut(self): 50 self._tmp_output_file.flush() 51 try: 52 with open(self._tmp_output_file.name) as f: 53 return f.read() 54 except IOError: 55 return '' 56 57 58class SampleProfiler(profiler.Profiler): 59 60 def __init__(self, browser_backend, platform_backend, output_path, state): 61 super(SampleProfiler, self).__init__( 62 browser_backend, platform_backend, output_path, state) 63 process_output_file_map = self._GetProcessOutputFileMap() 64 self._process_profilers = [] 65 for pid, output_file in process_output_file_map.iteritems(): 66 if '.utility' in output_file: 67 # The utility process may not have been started by Telemetry. 68 # So we won't have permissing to profile it 69 continue 70 self._process_profilers.append( 71 _SingleProcessSampleProfiler(pid, output_file)) 72 73 @classmethod 74 def name(cls): 75 return 'sample' 76 77 @classmethod 78 def is_supported(cls, browser_type): 79 if sys.platform != 'darwin': 80 return False 81 if browser_type == 'any': 82 return True 83 return (not browser_type.startswith('android') and 84 not browser_type.startswith('cros')) 85 86 def CollectProfile(self): 87 output_paths = [] 88 for single_process in self._process_profilers: 89 output_paths.append(single_process.CollectProfile()) 90 return output_paths 91