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 os
6import signal
7import sys
8
9from telemetry.core import exceptions
10from telemetry.core import util
11from telemetry.core.platform import profiler
12
13# pexpect is not available on all platforms so use the third_party version.
14util.AddDirToPythonPath(util.GetChromiumSrcDir(), 'third_party', 'pexpect')
15try:
16  import pexpect  # pylint: disable=F0401
17except ImportError:
18  pass
19
20
21class _SingleProcessIprofilerProfiler(object):
22  """An internal class for using iprofiler for a given process."""
23  def __init__(self, pid, output_path):
24    self._output_path = output_path
25    output_dir = os.path.dirname(self._output_path)
26    output_file = os.path.basename(self._output_path)
27    self._proc = pexpect.spawn(
28        'iprofiler', ['-timeprofiler', '-T', '300', '-a', str(pid),
29                      '-d', output_dir, '-o', output_file],
30        timeout=300)
31    while True:
32      if self._proc.getecho():
33        output = self._proc.readline().strip()
34        if not output:
35          continue
36        if 'iprofiler: Profiling process' in output:
37          break
38        print output
39      self._proc.interact(escape_character='\x0d')
40      if 'Failed to authorize rights' in output:
41        raise exceptions.ProfilingException(
42            'Failed to authorize rights for iprofiler\n')
43      if 'iprofiler error' in output:
44        raise exceptions.ProfilingException(
45            'Failed to start iprofiler for process %s\n' %
46            self._output_path.split('.')[1])
47      self._proc.write('\x0d')
48      print
49      def Echo():
50        return self._proc.getecho()
51      util.WaitFor(Echo, timeout=5)
52
53  def CollectProfile(self):
54    self._proc.kill(signal.SIGINT)
55    try:
56      self._proc.wait()
57    except pexpect.ExceptionPexpect:
58      pass
59    finally:
60      self._proc = None
61
62    print 'To view the profile, run:'
63    print '  open -a Instruments %s.dtps' % self._output_path
64    return self._output_path
65
66
67class IprofilerProfiler(profiler.Profiler):
68
69  def __init__(self, browser_backend, platform_backend, output_path, state):
70    super(IprofilerProfiler, self).__init__(
71        browser_backend, platform_backend, output_path, state)
72    process_output_file_map = self._GetProcessOutputFileMap()
73    self._process_profilers = []
74    for pid, output_file in process_output_file_map.iteritems():
75      if '.utility' in output_file:
76        # The utility process may not have been started by Telemetry.
77        # So we won't have permissing to profile it
78        continue
79      self._process_profilers.append(
80          _SingleProcessIprofilerProfiler(pid, output_file))
81
82  @classmethod
83  def name(cls):
84    return 'iprofiler'
85
86  @classmethod
87  def is_supported(cls, browser_type):
88    if sys.platform != 'darwin':
89      return False
90    if browser_type == 'any':
91      return True
92    return (not browser_type.startswith('android') and
93            not browser_type.startswith('cros'))
94
95  def CollectProfile(self):
96    output_files = []
97    for single_process in self._process_profilers:
98      output_files.append(single_process.CollectProfile())
99    return output_files
100