Home | History | Annotate | Download | only in perf
      1 #!/usr/bin/env python
      2 # Copyright (c) 2012 The Chromium Authors. All rights reserved.
      3 # Use of this source code is governed by a BSD-style license that can be
      4 # found in the LICENSE file.
      5 
      6 """Automate the setup process of Chrome Endure environment.
      7 
      8 Usage:
      9   python endure_setup.py [option]
     10 
     11 We use <ENDURE_DIR> to refer to the root directory in which Chrome Endure
     12 is set up. By default, <ENDURE_DIR> is the current working directory.
     13 
     14 First, run:
     15   >python endure_setup.py
     16 This command will automatically setup Chrome Endure in <ENDURE_DIR>.
     17 
     18 Next, run your first endure test by:
     19   >TEST_LENGTH=30 LOCAL_PERF_DIR="<ENDURE_DIR>/chrome_graph" \\
     20    python <ENDURE_DIR>/src/chrome/test/functional/perf_endure.py \\
     21    perf_endure.ChromeEndureGmailTest.testGmailComposeDiscard \\
     22 The above commands runs a Chrome Endure test for 30 seconds and saves
     23 the results to <ENDURE_DIR>/chrome_graph.
     24 
     25 Last, to view the graphs, run another script endure_server.py
     26 within <ENDURE_DIR> to start a local HTTP server that serves
     27 the graph directory, see endure_server.py for details.
     28 
     29 Use python endure_setup.py --help for more options.
     30 
     31 This script depends on the following modules
     32 (which will be downloaded automatically):
     33   depot_tools
     34   src/chrome/test/pyautolib/fetch_prebuilt_pyauto.py
     35 
     36 Supported platforms: Linux and Linux_x64.
     37 """
     38 
     39 import logging
     40 import optparse
     41 import os
     42 import platform
     43 import shutil
     44 import subprocess
     45 import sys
     46 import urllib
     47 import urllib2
     48 import zipfile
     49 
     50 URLS = {'depot_tools': ('http://src.chromium.org'
     51                         '/chrome/trunk/tools/depot_tools'),
     52         'pyauto': ('https://src.chromium.org/'
     53                    'chrome/trunk/src/chrome/test/functional.DEPS'),
     54         'binary': ('http://commondatastorage.googleapis.com/'
     55                    'chromium-browser-continuous/{os_type}/{revision}'),
     56        }
     57 
     58 
     59 class SetupError(Exception):
     60   """Catch errors in setting up Chrome Endure."""
     61   pass
     62 
     63 
     64 class HelpFormatter(optparse.IndentedHelpFormatter):
     65   """Format the help message of this script."""
     66 
     67   def format_description(self, description):
     68     """Override to keep the original format of the description."""
     69     return description + '\n' if description else ''
     70 
     71 
     72 def Main(argv):
     73   """Fetch Chrome Endure.
     74 
     75   Usage:
     76     python endure_setup.py [options]
     77 
     78   Examples:
     79     >python endure_setup.py
     80       Fetch the latest version of Chrome Endure to the current
     81       working directory.
     82 
     83     >python endure_setup.py --endure-dir=/home/user/endure_dir
     84       Fetch the latest version of Chrome Endure to /home/user/endure_dir.
     85   """
     86   parser = optparse.OptionParser(
     87       formatter=HelpFormatter(), description=Main.__doc__)
     88   parser.add_option(
     89       '-d', '--endure-dir', type='string', default=os.getcwd(),
     90       help='Directory in which to setup or update. ' \
     91            'Default value is the current working directory.')
     92   # TODO(fdeng): remove this option once the Chrome Endure
     93   # graphing code is checked into chrome tree.
     94   parser.add_option(
     95       '-g', '--graph-zip-url', type='string', default=None,
     96       help='URL to a zip file containing the chrome graphs.')
     97   os_type = GetCurrentOSType()
     98   if not os_type.startswith('Linux'):
     99     raise SetupError('Only support Linux or Linux_x64, %s found'
    100                      % os_type)
    101   options, _ = parser.parse_args(argv)
    102   endure_dir = os.path.abspath(options.endure_dir)
    103   depot_dir = os.path.join(endure_dir, 'depot_tools')
    104   gclient = os.path.join(depot_dir, 'gclient')
    105   fetch_py = os.path.join(endure_dir, 'src', 'chrome',
    106                           'test', 'pyautolib',
    107                           'fetch_prebuilt_pyauto.py')
    108   binary_dir = os.path.join(endure_dir, 'src', 'out', 'Release')
    109   graph_zip_url = options.graph_zip_url
    110   graph_dir = os.path.join(endure_dir, 'chrome_graph')
    111 
    112   if not os.path.isdir(endure_dir):
    113     os.makedirs(endure_dir)
    114 
    115   logging.info('Fetching depot tools...')
    116   FetchDepot(depot_dir)
    117   logging.info('Fetching PyAuto (python code)...')
    118   FetchPyAuto(gclient, endure_dir)
    119   logging.info('Fetching binaries(chrome, pyautolib, chrome driver)...')
    120   FetchBinaries(fetch_py, binary_dir, os_type)
    121   # TODO(fdeng): remove this after it is checked into the chrome tree.
    122   logging.info('Fetching chrome graphing files...')
    123   FetchGraph(graph_zip_url, graph_dir)
    124   return 0
    125 
    126 
    127 def FetchDepot(depot_dir):
    128   """Fetch depot_tools.
    129 
    130   Args:
    131     depot_dir: The directory where depot_tools will be checked out.
    132 
    133   Raises:
    134     SetupError: If fail.
    135   """
    136   if subprocess.call(['svn', 'co', URLS['depot_tools'], depot_dir]) != 0:
    137     raise SetupError('Error found when checking out depot_tools.')
    138   if not CheckDepot(depot_dir):
    139     raise SetupError('Could not get depot_tools.')
    140 
    141 
    142 def CheckDepot(depot_dir):
    143   """Check that some expected depot_tools files exist.
    144 
    145   Args:
    146     depot_dir: The directory where depot_tools are checked out.
    147 
    148   Returns:
    149     True if check passes otherwise False.
    150   """
    151   gclient = os.path.join(depot_dir, 'gclient')
    152   gclient_py = os.path.join(depot_dir, 'gclient.py')
    153   files = [gclient, gclient_py]
    154   for f in files:
    155     if not os.path.exists(f):
    156       return False
    157   try:
    158     subprocess.call([gclient, '--version'])
    159   except OSError:
    160     return False
    161   return True
    162 
    163 
    164 def FetchPyAuto(gclient, endure_dir):
    165   """Use gclient to fetch python code.
    166 
    167   Args:
    168     gclient: The path to the gclient executable.
    169     endure_dir: Directory where Chrome Endure and
    170                 its dependencies will be checked out.
    171 
    172   Raises:
    173     SetupError: if fails.
    174   """
    175   cur_dir = os.getcwd()
    176   os.chdir(endure_dir)
    177   config_cmd = [gclient, 'config', URLS['pyauto']]
    178   if subprocess.call(config_cmd) != 0:
    179     raise SetupError('Running "%s" failed.' % ' '.join(config_cmd))
    180   sync_cmd = [gclient, 'sync']
    181   if subprocess.call(sync_cmd) != 0:
    182     raise SetupError('Running "%s" failed.' % ' '.join(sync_cmd))
    183   CheckPyAuto(endure_dir)
    184   logging.info('Sync PyAuto python code done.')
    185   os.chdir(cur_dir)
    186 
    187 
    188 def CheckPyAuto(endure_dir):
    189   """Sanity check for Chrome Endure code.
    190 
    191   Args:
    192     endure_dir: Directory of Chrome Endure and its dependencies.
    193 
    194   Raises:
    195     SetupError: If fails.
    196   """
    197   fetch_py = os.path.join(endure_dir, 'src', 'chrome',
    198                           'test', 'pyautolib',
    199                           'fetch_prebuilt_pyauto.py')
    200   pyauto_py = os.path.join(endure_dir, 'src',
    201                            'chrome', 'test',
    202                            'pyautolib', 'pyauto.py')
    203   files = [fetch_py, pyauto_py]
    204   for f in files:
    205     if not os.path.exists(f):
    206       raise SetupError('Checking %s failed.' % f)
    207 
    208 
    209 def FetchBinaries(fetch_py, binary_dir, os_type):
    210   """Get the prebuilt binaries from continuous build archive.
    211 
    212   Args:
    213     fetch_py: Path to the script which fetches pre-built binaries.
    214     binary_dir: Directory of the pre-built binaries.
    215     os_type: 'Mac', 'Win', 'Linux', 'Linux_x64'.
    216 
    217   Raises:
    218     SetupError: If fails.
    219   """
    220   revision = GetLatestRevision(os_type)
    221   logging.info('Cleaning %s', binary_dir)
    222   if os.path.exists(binary_dir):
    223     shutil.rmtree(binary_dir)
    224   logging.info('Downloading binaries...')
    225   cmd = [fetch_py, '-d', binary_dir,
    226          URLS['binary'].format(
    227              os_type=os_type, revision=revision)]
    228   if subprocess.call(cmd) == 0 and os.path.exists(binary_dir):
    229     logging.info('Binaries at revision %s', revision)
    230   else:
    231     raise SetupError('Running "%s" failed.' % ' '.join(cmd))
    232 
    233 
    234 def FetchGraph(graph_zip_url, graph_dir):
    235   """Fetch graph code.
    236 
    237   Args:
    238     graph_zip_url: The url to a zip file containing the chrome graphs.
    239     graph_dir: Directory of the chrome graphs.
    240 
    241   Raises:
    242     SetupError: if unable to retrive the zip file.
    243   """
    244   # TODO(fdeng): remove this function once chrome graph
    245   # is checked into chrome tree.
    246   if not graph_zip_url:
    247     logging.info(
    248         'Skip fetching chrome graphs' +
    249         ' since --graph-zip-url is not set.')
    250     return
    251   graph_zip = urllib.urlretrieve(graph_zip_url)[0]
    252   if graph_zip is None or not os.path.exists(graph_zip):
    253     raise SetupError('Unable to retrieve %s' % graph_zip_url)
    254   if not os.path.exists(graph_dir):
    255     os.mkdir(graph_dir)
    256   UnzipFilenameToDir(graph_zip, graph_dir)
    257   logging.info('Graph code is downloaded to %s', graph_dir)
    258 
    259 
    260 def GetCurrentOSType():
    261   """Get a string representation for the current OS.
    262 
    263   Returns:
    264     'Mac', 'Win', 'Linux', or 'Linux_64'.
    265 
    266   Raises:
    267     RuntimeError: if OS can't be identified.
    268   """
    269   if sys.platform == 'darwin':
    270     os_type = 'Mac'
    271   if sys.platform == 'win32':
    272     os_type = 'Win'
    273   if sys.platform.startswith('linux'):
    274     os_type = 'Linux'
    275     if platform.architecture()[0] == '64bit':
    276       os_type += '_x64'
    277   else:
    278     raise RuntimeError('Unknown platform')
    279   return os_type
    280 
    281 
    282 def GetLatestRevision(os_type):
    283   """Figure out the latest revision number of the prebuilt binary archive.
    284 
    285   Args:
    286     os_type: 'Mac', 'Win', 'Linux', or 'Linux_64'.
    287 
    288   Returns:
    289     A string of latest revision number.
    290 
    291   Raises:
    292     SetupError: If unable to get the latest revision number.
    293   """
    294   last_change_url = ('http://commondatastorage.googleapis.com/'
    295                      'chromium-browser-continuous/%s/LAST_CHANGE' % os_type)
    296   response = urllib2.urlopen(last_change_url)
    297   last_change = response.read()
    298   if not last_change:
    299     raise SetupError('Unable to get the latest revision number from %s' %
    300                      last_change_url)
    301   return last_change
    302 
    303 
    304 def UnzipFilenameToDir(filename, directory):
    305   """Unzip |filename| to directory |directory|.
    306 
    307   This works with as low as python2.4 (used on win).
    308   (Code is adapted from fetch_prebuilt_pyauto.py)
    309   """
    310   # TODO(fdeng): remove this function as soon as the Chrome Endure
    311   # graphing code is checked into the chrome tree.
    312   zf = zipfile.ZipFile(filename)
    313   pushd = os.getcwd()
    314   if not os.path.isdir(directory):
    315     os.mkdir(directory)
    316   os.chdir(directory)
    317   # Extract files.
    318   for info in zf.infolist():
    319     name = info.filename
    320     if name.endswith('/'):  # dir
    321       if not os.path.isdir(name):
    322         os.makedirs(name)
    323     else:  # file
    324       directory = os.path.dirname(name)
    325       if directory and not os.path.isdir(directory):
    326         os.makedirs(directory)
    327       out = open(name, 'wb')
    328       out.write(zf.read(name))
    329       out.close()
    330     # Set permissions. Permission info in external_attr is shifted 16 bits.
    331     os.chmod(name, info.external_attr >> 16L)
    332   os.chdir(pushd)
    333 
    334 
    335 if '__main__' == __name__:
    336   logging.basicConfig(format='[%(levelname)s] %(message)s', level=logging.DEBUG)
    337   sys.exit(Main(sys.argv[1:]))
    338