Chromium Code Reviews| Index: chrome/test/functional/perf/endure_setup.py |
| diff --git a/chrome/test/functional/perf/endure_setup.py b/chrome/test/functional/perf/endure_setup.py |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..1a87f1f6760d320e0fabf2e94c40a2fa32b7a39b |
| --- /dev/null |
| +++ b/chrome/test/functional/perf/endure_setup.py |
| @@ -0,0 +1,414 @@ |
| +"""Automate the setup process of Chrome Endure environment. |
| + |
| +Usage: |
| + ./endure.py [fetch|server] [option] |
| + |
| +Command "fetch" will automatically setup Chrome Endure environment. |
| +The script will do the following things: |
| + 1) Set up depot_tool if necessary. |
| + 2) Check out python code which Chrome Endure depends on. |
| + 3) Check out pre-built binary of Chrome, PyAuto lib and chrome driver |
| + 4) Check out chrome graphing file which are needed to show the test results. |
| + |
| +After fetching Chrome Endure, you should be able to run the endure test. Try: |
| + TEST_LENGTH=60 LOCAL_GRAPH='./chrome_graph' |
| + python ./src/chrome/test/functional/perf_endure.py |
| + perf_endure.ChromeEndureGmailTest.testGmailComposeDiscard |
| +The above commands runs an Chrome Endure test for 60 seconds and saves |
| +the results to ./chrome_graph. LOCAL_GRAPH is the directory where you have |
| +chrome graphing files. By default, it is <DIR_OF_THIS_SCIRPT>/chrome_endure. |
| + |
| +Command "server" will start a local HTTP server to serve the directory which |
| +contains the result data files. Run this command after you have test results |
| +output to <DIR_OF_THIS_SCRIPT>/chrome_endure and a server will be started with |
| +a port that is automatically picked. You can then view the result graphs |
| +via http://localhost:<GIVEN_PORT>. |
| + |
| +Use ./endure [fetch|server] --help for more options. |
| +""" |
| + |
| +import BaseHTTPServer |
| +import optparse |
| +import os |
| +import platform |
| +import shutil |
| +import SimpleHTTPServer |
| +import subprocess |
| +import sys |
| +import urllib |
| +import urllib2 |
| +import zipfile |
| + |
| + |
| +class SetupError(Exception): |
| + """Catch errors in setting up Chrome Endure.""" |
| + |
| + def __init__(self, label, message): |
| + super(SetupError, self).__init__() |
| + self.label = label |
| + self.message = message |
| + |
| + def __str__(self): |
| + return self.label+'\n'+str(self.message) |
| + |
| + |
| +class PlainHelpFormatter(optparse.IndentedHelpFormatter): |
| + """Format the help message of this script.""" |
| + |
| + def format_description(self, description): |
| + if description: |
| + return description + '\n' |
| + else: |
| + return '' |
| + |
| + |
| +class CmdFetch(object): |
| + """Fetch Chrome Endure. |
| + |
| + Usage: |
| + ./endure.py fetch [options] |
| + Examples: |
| + ./endure.py fetch |
| + Fetch latest version of Chrome Endure to the same directory as this script. |
| + |
| + ./endure.py fetch --endure-dir=/home/user/endure_dir --revision=18043 |
| + Fetch revision 18043 to /home/user/endure_dir |
| + """ |
| + _URLS = {'depot_tools': ('http://src.chromium.org' |
| + '/chrome/trunk/tools/depot_tools'), |
| + 'pyauto': ('https://src.chromium.org/' |
| + 'chrome/trunk/src/chrome/test/functional.DEPS'), |
| + 'binary': ('http://commondatastorage.googleapis.com/' |
| + 'chromium-browser-continuous/{os_type}/{revision}'), |
| + # TODO(fdeng): change to an external address after graph code |
| + # is checked in |
| + 'graph': ('http://www.corp.google.com/' |
| + '~dennisjeffrey/chrome_perf/local_graphs.zip'), |
| + } |
| + NAME = 'fetch' |
| + DESCRIPTION = 'Fetch Chrome Endure.' |
| + |
| + def _ParseArgs(self, argv): |
| + parser = optparse.OptionParser( |
| + usage='%%prog %s [options]' % self.NAME, |
| + formatter=PlainHelpFormatter(), |
| + description=self.__doc__) |
| + parser.add_option( |
| + '-d', '--endure-dir', type='string', default=os.path.dirname(__file__), |
| + help='Directory in which to setup or update.') |
| + parser.add_option( |
| + '-r', '--revision', type='string', default=None, |
| + help='Revision of Chrome tree to get Chrome Endure.') |
| + return parser.parse_args(argv) |
| + |
| + def Run(self, argv): |
| + """Run this command.""" |
| + options, _ = self._ParseArgs(argv) |
| + self._endure_dir = os.path.abspath(options.endure_dir) |
| + self._revision = options.revision |
| + if not self._revision: |
| + self._revision = self._GetLatestRevision(self._GetCurrentOSType()) |
| + self._os_type = self._GetCurrentOSType() |
| + self._depot_dir = os.path.join(self._endure_dir, 'depot_tools') |
| + self._gclient = os.path.join(self._depot_dir, 'gclient') |
| + self._fetch_py = os.path.join(self._endure_dir, 'src', 'chrome', |
| + 'test', 'pyautolib', |
| + 'fetch_prebuilt_pyauto.py') |
| + self._binary_dir = os.path.join(self._endure_dir, 'src', 'out', 'Release') |
| + self._graph_dir = os.path.join(self._endure_dir, 'chrome_graph') |
| + |
| + if not os.path.isdir(self._endure_dir): |
| + os.makedirs(self._endure_dir) |
| + |
| + print 'Checking depot tools...' |
| + self._FetchDepot() |
| + print 'Fetching PyAuto (python code)...' |
| + self._FetchPyAuto(self._revision) |
| + print 'Fetching binaries(chrome, pyautolib, chrome driver)...' |
| + self._FetchBinaries(self._os_type, self._revision) |
| + # TODO(fdeng): remove this after it is check into the chrome tree. |
| + print 'Fetching chrome graphing files...' |
| + self._FetchGraph() |
| + return 0 |
| + |
| + def _FetchDepot(self): |
| + """Fetch depot_tool if not installed in the system.""" |
| + try: |
| + subprocess.call(['gclient', '--version']) |
| + self._gclient = 'gclient' |
| + except OSError: |
| + try: |
| + # TODO(fdeng): how can I know gclient is not installed |
| + # without checking OSError, a better way? |
| + url = self._URLS['depot_tools'] |
| + print 'Fetching depot tools: %s' % url |
| + subprocess.call(['svn', 'co', self._URLS['depot_tools'], |
| + self._depot_dir]) |
| + subprocess.call([self._gclient, '--version']) |
| + except Exception as e: |
| + raise SetupError('Unable to set up depot tools:', str(e)) |
| + |
| + def _FetchPyAuto(self, revision): |
| + """Use gclient to fetch python code.""" |
| + cur_dir = os.getcwd() |
| + try: |
| + os.chdir(self._endure_dir) |
| + # gclient config |
| + config_cmd = [self._gclient, 'config', self._URLS['pyauto']] |
| + code = subprocess.call(config_cmd) |
| + if code != 0: |
| + raise Exception('Running "%s" failed.' % ' '.join(config_cmd)) |
| + # gclient sync |
| + sync_cmd = [self._gclient, 'sync'] |
| + if revision: |
| + sync_cmd.extend(['--revision', 'functional.DEPS@'+(revision)]) |
|
fdeng1
2012/08/07 22:19:45
Here the script tries to run "gclient sync --revis
dennis_jeffrey
2012/08/08 23:41:07
As discussed offline, I think it'll be fine for no
|
| + code = subprocess.call(sync_cmd) |
| + if code != 0: |
| + raise Exception('Running "%s" failed.' % ' '.join(sync_cmd)) |
| + except Exception as e: |
| + raise SetupError('Unable to fetch PyAuto code', str(e)) |
| + finally: |
| + os.chdir(cur_dir) |
| + |
| + def _FetchBinaries(self, os_type, revision): |
| + """Get the prebuilt binaries from continuous build archive.""" |
| + try: |
| + if not os.path.exists(self._fetch_py): |
| + raise Exception( |
| + 'Unable to find %s, did fetching python code succeed?' |
| + % self._fetch_py) |
| + print 'Cleaning %s' % self._binary_dir |
| + if os.path.exists(self._binary_dir): |
| + shutil.rmtree(self._binary_dir) |
| + print 'Downloading...' |
| + cmd = [self._fetch_py, '-d', self._binary_dir, |
| + self._URLS['binary'].format(os_type=os_type, revision=revision)] |
| + code = subprocess.call(cmd) |
| + if code != 0: |
| + raise Exception('Running "%s" failed.' % ' '.join(cmd)) |
| + except Exception as e: |
| + raise SetupError( |
| + 'Unable to fetch binaries for chrome/pyauto/chrome_driver:', str(e)) |
| + |
| + def _FetchGraph(self): |
| + """Fetch graph code.""" |
| + try: |
| + graph_zip = urllib.urlretrieve(self._URLS['graph'])[0] |
| + if os.path.exists(self._graph_dir): |
| + print 'Cleaning %s, ' % self._graph_dir, |
| + print 'data files(.dat) will be preserved.' |
| + files = os.listdir(self._graph_dir) |
| + for f in files: |
| + path = os.path.join(self._graph_dir, f) |
| + if os.path.isdir(path): |
| + shutil.rmtree(path) |
| + elif os.path.splitext(f) != 'dat': |
| + os.remove(path) |
| + else: |
| + os.mkdir(self._graph_dir) |
| + self._UnzipFilenameToDir(graph_zip, self._graph_dir) |
| + except Exception as e: |
| + raise SetupError('Unable to fetch graph files:', str(e)) |
| + |
| + def _GetCurrentOSType(self): |
| + """Get a string representation for the current os. |
| + |
| + Returns: |
| + 'Mac', 'Win', 'Linux', or 'Linux_64' |
| + |
| + Raises: |
| + RuntimeError: if os can't be identified. |
| + """ |
| + if sys.platform == 'darwin': |
| + os_type = 'Mac' |
| + if sys.platform == 'win32': |
| + os_type = 'Win' |
| + if sys.platform.startswith('linux'): |
| + os_type = 'Linux' |
| + if platform.architecture()[0] == '64bit': |
| + os_type += '_x64' |
| + else: |
| + raise RuntimeError('Unknown platform') |
| + return os_type |
| + |
| + def _GetLatestRevision(self, os_type): |
| + """Figure out the latest revision number of the prebuilt binary archive. |
| + |
| + Args: |
| + os_type: 'Mac', 'Win', 'Linux', or 'Linux_64' |
| + |
| + Returns: |
| + A string of latest revision number or None on fail. |
| + """ |
| + last_change_url = ('http://commondatastorage.googleapis.com/' |
| + 'chromium-browser-continuous/%s/LAST_CHANGE' % os_type) |
| + response = urllib2.urlopen(last_change_url) |
| + last_change = response.read() |
| + if not last_change: |
| + print 'Unable to get latest revision number from %s' % last_change_url |
| + return None |
| + return last_change |
| + |
| + @classmethod |
| + def _UnzipFilenameToDir(cls, filename, directory): |
| + """Unzip |filename| to directory |directory|. |
| + |
| + This works with as low as python2.4 (used on win). |
| + """ |
| + zf = zipfile.ZipFile(filename) |
| + pushd = os.getcwd() |
| + if not os.path.isdir(directory): |
| + os.mkdir(directory) |
| + os.chdir(directory) |
| + # Extract files. |
| + for info in zf.infolist(): |
| + name = info.filename |
| + print name |
| + if name.endswith('/'): # dir |
| + if not os.path.isdir(name): |
| + os.makedirs(name) |
| + else: # file |
| + directory = os.path.dirname(name) |
| + if directory and not os.path.isdir(directory): |
| + os.makedirs(directory) |
| + out = open(name, 'wb') |
| + out.write(zf.read(name)) |
| + out.close() |
| + # Set permissions. Permission info in external_attr is shifted 16 bits. |
| + os.chmod(name, info.external_attr >> 16L) |
| + os.chdir(pushd) |
| + |
| + |
| +class CmdServer(object): |
| + """Start an http server which serves the Chrome Endure test results. |
| + |
| + Usage: |
| + ./endure server [options] |
| + Examples: |
| + ./endure.py server |
| + By default, it will serve the directory <DIR_OF_THIS_SCIRPT>/chrome_graph |
| + where DIR_OF_THIS_SCIRPT is the directory of this script (endure.py). |
| + |
| + ./endure.py server --graph-dir=/home/user/Document/graph_dir |
| + Use --graph-dir if you have your graph directory at a different place. |
| + """ |
| + NAME = 'server' |
| + DESCRIPTION = 'Start an http server for viewing the' \ |
| + 'tests results from a browser.' |
| + |
| + def _ParseArgs(self, argv): |
| + parser = optparse.OptionParser( |
| + usage='%%prog %s [options]' % self.NAME, |
| + formatter=PlainHelpFormatter(), |
| + description=self.__doc__) |
| + parser.add_option( |
| + '-g', '--graph-dir', type='string', |
| + default=os.path.join(os.path.dirname(__file__), 'chrome_graph'), |
| + help='The directory that contains graphing files' \ |
| + 'and data files of test results') |
| + return parser.parse_args(argv) |
| + |
| + def Run(self, argv): |
| + """Run this command.""" |
| + options, _ = self._ParseArgs(argv) |
| + self._graph_dir = os.path.abspath(options.graph_dir) |
| + cur_dir = os.getcwd() |
| + os.chdir(self._graph_dir) |
| + httpd = BaseHTTPServer.HTTPServer(('', 0), |
| + EndureHTTPRequestHandler) |
| + try: |
| + print 'Serving %s at port %d' % (self._graph_dir, httpd.server_port) |
| + print 'View test results at http://localhost:%d' % httpd.server_port |
| + print 'Press Ctrl-C to stop the server.' |
| + httpd.serve_forever() |
| + except KeyboardInterrupt: |
| + print 'Shutting down ...' |
| + httpd.shutdown() |
| + finally: |
| + os.chdir(cur_dir) |
| + return 0 |
| + |
| + |
| +class EndureHTTPRequestHandler(SimpleHTTPServer.SimpleHTTPRequestHandler): |
|
fdeng1
2012/08/07 22:09:43
I tried to reuse the TestServer in pyautolib.py. I
dennis_jeffrey
2012/08/08 23:41:07
I think it's fine to have our own basic test serve
|
| + """A simple HTTP request handler for showing Chrome Endure test results.""" |
| + |
| + def send_head(self): |
|
fdeng1
2012/08/07 22:09:43
I wish I didn't have to override this function. Bu
dennis_jeffrey
2012/08/08 23:41:07
As discussed offline, let's no longer override thi
fdeng1
2012/08/09 03:26:16
I am able to solve the problem by modifying the se
dennis_jeffrey
2012/08/09 17:16:27
Great! I made the change on the live chrome endur
|
| + """Override send_head so it won't redirect if path is not ended with "/". |
| + |
| + Common code for GET and HEAD commands. This sends the response |
| + code and MIME headers. |
| + |
| + Returns: |
| + either a file object (which has to be copied |
| + to the outputfile by the caller unless the command was HEAD, |
| + and must be closed by the caller under all circumstances), or |
| + None, in which case the caller has nothing further to do. |
| + """ |
| + path = self.translate_path(self.path) |
| + f = None |
| + if os.path.isdir(path): |
| + for index in 'index.html', 'index.htm': |
| + index = os.path.join(path, index) |
| + if os.path.exists(index): |
| + path = index |
| + break |
| + else: |
| + return self.list_directory(path) |
| + ctype = self.guess_type(path) |
| + try: |
| + # Always read in binary mode. Opening files in text mode may cause |
| + # newline translations, making the actual size of the content |
| + # transmitted *less* than the content-length! |
| + f = open(path, 'rb') |
| + except IOError: |
| + self.send_error(404, 'File not found') |
| + return None |
| + self.send_response(200) |
| + self.send_header('Content-type', ctype) |
| + fs = os.fstat(f.fileno()) |
| + self.send_header('Content-Length', str(fs[6])) |
| + self.send_header('Last-Modified', self.date_time_string(fs.st_mtime)) |
| + self.end_headers() |
| + return f |
| + |
| + |
| +class CmdHelp(object): |
| + """Print a list of commands or help for a specific command.""" |
| + NAME = 'help' |
| + DESCRIPTION = __doc__ |
| + |
| + def __init__(self, cmds): |
| + """Initialize help command. |
| + |
| + Args: |
| + cmds: Commands for which help information will be printed. |
| + """ |
| + self.cmds = cmds |
| + |
| + def Run(self, args): |
| + """Run this command.""" |
| + if len(args) == 1: |
| + return Main(args + ['--help']) |
| + print 'Usage:\n ./endure.py [options] command' |
| + print 'Commands are:' |
| + for cmd in self.cmds: |
| + print '\t%s\t\t%s' % (cmd.NAME, cmd.DESCRIPTION) |
| + |
| + print 'Examples:' |
| + print ' ./endure.py %s' % CmdFetch.NAME |
| + return 0 |
| + |
| + |
| +def Main(argv): |
| + if argv and argv[0] == CmdFetch.NAME: |
| + command = CmdFetch() |
| + elif argv and argv[0] == CmdServer.NAME: |
| + command = CmdServer() |
| + else: |
| + cmds = [CmdFetch, CmdServer, CmdHelp] |
| + command = CmdHelp(cmds) |
| + return command.Run(argv[1:]) |
| + |
| + |
| +if '__main__' == __name__: |
| + sys.exit(Main(sys.argv[1:])) |