1# Copyright 2014 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 glob
6import os
7import subprocess
8import sys
9
10from telemetry.core.platform import profiler
11
12_PGOSWEEP_EXECUTABLE = 'pgosweep.exe'
13
14
15class WinPGOProfiler(profiler.Profiler):
16  """A profiler that run the Visual Studio PGO utility 'pgosweep.exe' before
17  terminating a browser or a renderer process.
18  """
19
20  def __init__(self, browser_backend, platform_backend, output_path, state):
21    super(WinPGOProfiler, self).__init__(
22        browser_backend, platform_backend, output_path, state)
23
24    pgosweep_is_in_path = False
25    for entry in os.environ['PATH'].split(os.pathsep):
26      if os.path.exists(os.path.join(entry, _PGOSWEEP_EXECUTABLE)):
27        pgosweep_is_in_path = True
28        break
29    if not pgosweep_is_in_path:
30      raise IOError(2, '%s isn\'t in the current path, run vcvarsall.bat to fix'
31           ' this.' % _PGOSWEEP_EXECUTABLE)
32
33    self._browser_dir = browser_backend.browser_directory
34    self._chrome_pgc_counter = self._GetNextProfileIndex('chrome')
35    self._chrome_child_pgc_counter = self._GetNextProfileIndex('chrome_child')
36
37  def _GetNextProfileIndex(self, dll_name):
38    """Scan the directory containing the DLL |dll_name| to find the next index
39    to use for the profile data files.
40
41    Args:
42      dll_name: The name of the DLL for which we want to get the next index to
43          to use.
44    """
45    max_index = 0
46    pgc_files = glob.glob(os.path.join(self._browser_dir,
47                                       '%s!*.pgc' % dll_name))
48    for pgc_file in pgc_files:
49      max_index = max(max_index,
50          int(os.path.splitext(os.path.split(pgc_file)[1])[0].split('!')[1]))
51    return max_index + 1
52
53  def _RunPGOSweep(self, pid, dll_name, index):
54    """Run the pgosweep utility to gather the profile data of a given process.
55
56    Args:
57      pid: The PID of the process we're interested in.
58      dll_name: The name of the DLL for which we want the profile data.
59      index: The index to use for the profile data file.
60
61    Returns the name of the profile data file.
62    """
63    pgc_filename = '%s\\%s!%d.pgc' % (self._browser_dir, dll_name, index)
64    subprocess.Popen([_PGOSWEEP_EXECUTABLE,
65                      '/pid:%d' % pid,
66                      '%s.dll' % dll_name,
67                      pgc_filename]
68                    ).wait()
69    return pgc_filename
70
71  @classmethod
72  def name(cls):
73    return 'win_pgo_profiler'
74
75  @classmethod
76  def is_supported(cls, browser_type):
77    # This profiler only make sense when doing a Windows build with Visual
78    # Studio (minimal supported version is 2013 Update 2).
79    return sys.platform.startswith('win')
80
81  @classmethod
82  def CustomizeBrowserOptions(cls, browser_type, options):
83    # The sandbox need to be disabled if we want to be able to gather the
84    # profile data.
85    options.AppendExtraBrowserArgs('--no-sandbox')
86
87  def CollectProfile(self):
88    """Collect the profile data for the current processes."""
89    output_files = []
90    for pid, output_file in self._GetProcessOutputFileMap().iteritems():
91      if 'renderer' in output_file:
92        output_files.append(self._RunPGOSweep(pid,
93                                              'chrome_child',
94                                              self._chrome_child_pgc_counter))
95        self._chrome_child_pgc_counter += 1
96      elif 'browser0' in output_file:
97        output_files.append(self._RunPGOSweep(pid,
98                                              'chrome',
99                                              self._chrome_pgc_counter))
100        self._chrome_pgc_counter += 1
101    return output_files
102