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