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 logging
6import os
7import subprocess
8import sys
9import tempfile
10
11from telemetry.core.platform import profiler
12from telemetry.core.platform.profiler import android_profiling_helper
13
14
15class _SingleProcessVTuneProfiler(object):
16  """An internal class for using vtune for a given process."""
17  def __init__(self, pid, output_file, browser_backend, platform_backend):
18    self._pid = pid
19    self._browser_backend = browser_backend
20    self._platform_backend = platform_backend
21    self._output_file = output_file
22    self._tmp_output_file = tempfile.NamedTemporaryFile('w', 0)
23    cmd = ['amplxe-cl', '-collect', 'hotspots',
24           '-target-pid', str(pid), '-r', self._output_file]
25    self._is_android = platform_backend.GetOSName() == 'android'
26    if self._is_android:
27      cmd += ['-target-system', 'android']
28
29    self._proc = subprocess.Popen(
30        cmd, stdout=self._tmp_output_file, stderr=subprocess.STDOUT)
31
32  def CollectProfile(self):
33    if ('renderer' in self._output_file and
34        not self._platform_backend.GetCommandLine(self._pid)):
35      logging.warning('Renderer was swapped out during profiling. '
36                      'To collect a full profile rerun with '
37                      '"--extra-browser-args=--single-process"')
38    subprocess.call(['amplxe-cl', '-command', 'stop', '-r', self._output_file])
39
40    exit_code = self._proc.wait()
41    try:
42      # 1: amplxe: Error: Cannot find a running process with the specified ID.
43      #    Provide a valid PID.
44      if exit_code not in (0, 1):
45        raise Exception(
46            'amplxe-cl failed with exit code %d. Output:\n%s' % (exit_code,
47            self._GetStdOut()))
48    finally:
49      self._tmp_output_file.close()
50
51    if exit_code:
52      # The renderer process was swapped out. Now that we made sure VTune has
53      # stopped, return without further processing the invalid profile.
54      return self._output_file
55
56    if self._is_android:
57      required_libs = \
58          android_profiling_helper.GetRequiredLibrariesForVTuneProfile(
59              self._output_file)
60
61      device = self._browser_backend.adb.device()
62      symfs_root = os.path.dirname(self._output_file)
63      android_profiling_helper.CreateSymFs(device,
64                                           symfs_root,
65                                           required_libs,
66                                           use_symlinks=True)
67      logging.info('Resolving symbols in profile.')
68      subprocess.call(['amplxe-cl', '-finalize', '-r', self._output_file,
69                       '-search-dir', symfs_root])
70
71    print 'To view the profile, run:'
72    print '  amplxe-gui %s' % self._output_file
73
74    return self._output_file
75
76  def _GetStdOut(self):
77    self._tmp_output_file.flush()
78    try:
79      with open(self._tmp_output_file.name) as f:
80        return f.read()
81    except IOError:
82      return ''
83
84
85class VTuneProfiler(profiler.Profiler):
86
87  def __init__(self, browser_backend, platform_backend, output_path, state):
88    super(VTuneProfiler, self).__init__(
89        browser_backend, platform_backend, output_path, state)
90    process_output_file_map = self._GetProcessOutputFileMap()
91    self._process_profilers = []
92
93    has_renderer = False
94    for pid, output_file in process_output_file_map.iteritems():
95      if 'renderer' in output_file:
96        has_renderer = True
97        break
98
99    for pid, output_file in process_output_file_map.iteritems():
100      if has_renderer:
101        if not 'renderer' in output_file:
102          continue
103      elif not 'browser0' in output_file:
104        continue
105
106      self._process_profilers.append(
107          _SingleProcessVTuneProfiler(pid, output_file, browser_backend,
108                                      platform_backend))
109
110  @classmethod
111  def name(cls):
112    return 'vtune'
113
114  @classmethod
115  def is_supported(cls, browser_type):
116    if sys.platform != 'linux2':
117      return False
118    if browser_type.startswith('cros'):
119      return False
120    try:
121      proc = subprocess.Popen(['amplxe-cl', '-version'],
122                              stderr=subprocess.STDOUT,
123                              stdout=subprocess.PIPE)
124      proc.communicate()
125      if proc.returncode != 0:
126        return False
127
128      if browser_type.startswith('android'):
129        # VTune checks if 'su' is available on the device.
130        proc = subprocess.Popen(['adb', 'shell', 'su', '-c', 'id'],
131                                stderr=subprocess.STDOUT,
132                                stdout=subprocess.PIPE)
133        return 'not found' not in proc.communicate()[0]
134
135      return True
136    except OSError:
137      return False
138
139  @classmethod
140  def CustomizeBrowserOptions(cls, browser_type, options):
141    options.AppendExtraBrowserArgs([
142        '--no-sandbox',
143        '--allow-sandbox-debugging',
144    ])
145
146  def CollectProfile(self):
147    print 'Processing profile, this will take a few minutes...'
148
149    output_files = []
150    for single_process in self._process_profilers:
151      output_files.append(single_process.CollectProfile())
152    return output_files
153