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