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