146d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)# Copyright 2013 The Chromium Authors. All rights reserved.
2a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)# Use of this source code is governed by a BSD-style license that can be
3a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)# found in the LICENSE file.
4a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)
5a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)import os
6a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)import signal
7a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)import sys
8a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)
9558790d6acca3451cf3a6b497803a5f07d0bec58Ben Murdochfrom telemetry.core import exceptions
10a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)from telemetry.core import util
11a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)from telemetry.core.platform import profiler
12a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)
13a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)# pexpect is not available on all platforms so use the third_party version.
143551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)util.AddDirToPythonPath(util.GetChromiumSrcDir(), 'third_party', 'pexpect')
15a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)try:
16a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)  import pexpect  # pylint: disable=F0401
17a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)except ImportError:
18a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)  pass
19a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)
20a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)
2190dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)class _SingleProcessIprofilerProfiler(object):
2290dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)  """An internal class for using iprofiler for a given process."""
2390dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)  def __init__(self, pid, output_path):
2490dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)    self._output_path = output_path
2590dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)    output_dir = os.path.dirname(self._output_path)
2690dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)    output_file = os.path.basename(self._output_path)
27a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)    self._proc = pexpect.spawn(
28a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)        'iprofiler', ['-timeprofiler', '-T', '300', '-a', str(pid),
29a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)                      '-d', output_dir, '-o', output_file],
30a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)        timeout=300)
31a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)    while True:
32a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)      if self._proc.getecho():
33a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)        output = self._proc.readline().strip()
34a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)        if not output:
35a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)          continue
36a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)        if 'iprofiler: Profiling process' in output:
37a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)          break
38a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)        print output
39a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)      self._proc.interact(escape_character='\x0d')
40558790d6acca3451cf3a6b497803a5f07d0bec58Ben Murdoch      if 'Failed to authorize rights' in output:
41558790d6acca3451cf3a6b497803a5f07d0bec58Ben Murdoch        raise exceptions.ProfilingException(
42558790d6acca3451cf3a6b497803a5f07d0bec58Ben Murdoch            'Failed to authorize rights for iprofiler\n')
43558790d6acca3451cf3a6b497803a5f07d0bec58Ben Murdoch      if 'iprofiler error' in output:
44558790d6acca3451cf3a6b497803a5f07d0bec58Ben Murdoch        raise exceptions.ProfilingException(
45558790d6acca3451cf3a6b497803a5f07d0bec58Ben Murdoch            'Failed to start iprofiler for process %s\n' %
46558790d6acca3451cf3a6b497803a5f07d0bec58Ben Murdoch            self._output_path.split('.')[1])
47a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)      self._proc.write('\x0d')
48a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)      print
49a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)      def Echo():
50a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)        return self._proc.getecho()
51a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)      util.WaitFor(Echo, timeout=5)
52a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)
5390dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)  def CollectProfile(self):
5490dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)    self._proc.kill(signal.SIGINT)
5590dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)    try:
5690dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)      self._proc.wait()
5790dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)    except pexpect.ExceptionPexpect:
5890dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)      pass
5990dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)    finally:
6090dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)      self._proc = None
6190dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)
6290dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)    print 'To view the profile, run:'
6390dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)    print '  open -a Instruments %s.dtps' % self._output_path
64558790d6acca3451cf3a6b497803a5f07d0bec58Ben Murdoch    return self._output_path
6590dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)
6690dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)
6790dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)class IprofilerProfiler(profiler.Profiler):
6890dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)
694e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)  def __init__(self, browser_backend, platform_backend, output_path, state):
7090dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)    super(IprofilerProfiler, self).__init__(
714e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)        browser_backend, platform_backend, output_path, state)
7290dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)    process_output_file_map = self._GetProcessOutputFileMap()
7390dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)    self._process_profilers = []
7490dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)    for pid, output_file in process_output_file_map.iteritems():
75558790d6acca3451cf3a6b497803a5f07d0bec58Ben Murdoch      if '.utility' in output_file:
76558790d6acca3451cf3a6b497803a5f07d0bec58Ben Murdoch        # The utility process may not have been started by Telemetry.
77558790d6acca3451cf3a6b497803a5f07d0bec58Ben Murdoch        # So we won't have permissing to profile it
78558790d6acca3451cf3a6b497803a5f07d0bec58Ben Murdoch        continue
7990dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)      self._process_profilers.append(
8090dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)          _SingleProcessIprofilerProfiler(pid, output_file))
8190dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)
82a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)  @classmethod
83a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)  def name(cls):
84a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)    return 'iprofiler'
85a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)
86a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)  @classmethod
8758537e28ecd584eab876aee8be7156509866d23aTorne (Richard Coles)  def is_supported(cls, browser_type):
88558790d6acca3451cf3a6b497803a5f07d0bec58Ben Murdoch    if sys.platform != 'darwin':
89558790d6acca3451cf3a6b497803a5f07d0bec58Ben Murdoch      return False
9058537e28ecd584eab876aee8be7156509866d23aTorne (Richard Coles)    if browser_type == 'any':
91558790d6acca3451cf3a6b497803a5f07d0bec58Ben Murdoch      return True
9258537e28ecd584eab876aee8be7156509866d23aTorne (Richard Coles)    return (not browser_type.startswith('android') and
9358537e28ecd584eab876aee8be7156509866d23aTorne (Richard Coles)            not browser_type.startswith('cros'))
94a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)
95a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)  def CollectProfile(self):
96558790d6acca3451cf3a6b497803a5f07d0bec58Ben Murdoch    output_files = []
9790dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)    for single_process in self._process_profilers:
98558790d6acca3451cf3a6b497803a5f07d0bec58Ben Murdoch      output_files.append(single_process.CollectProfile())
99558790d6acca3451cf3a6b497803a5f07d0bec58Ben Murdoch    return output_files
100