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