#!/usr/bin/env python # Copyright (c) 2011 The Chromium Authors. All rights reserved. # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. """Dromaeo benchmark automation script. Script runs dromaeo tests in browsers specified by --browser switch and saves results to a spreadsheet on docs.google.com. Prerequisites: 1. Install Google Data APIs Python Client Library from http://code.google.com/p/gdata-python-client. 2. Checkout Dromaeo benchmark from http://src.chromium.org/svn/trunk/src/chrome/test/data/dromaeo and provide local path to it in --dromaeo_home switch. 3. Create a spreadsheet at http://docs.google.com and specify its name in --spreadsheet switch Benchmark results are presented in the following format: browser | date time test 1 name|m11|...|m1n|test 1 average mean| |e11|...|e1n|test 1 average error test 2 name|m21|...|m2n|test 2 average mean| |e21|...|e2n|test 2 average error ... Here mij is mean run/s in individual dromaeo test i during benchmark run j, eij is error in individual dromaeo test i during benchmark run j. Example usage: dromaeo_benchmark_runner.py -b "E:\chromium\src\chrome\Release\chrome.exe" -b "C:\Program Files (x86)\Safari\safari.exe" -b "C:\Program Files (x86)\Opera 10.50 pre-alpha\opera.exe" -n 1 -d "E:\chromium\src\chrome\test\data\dromaeo" -f dom -e example@gmail.com """ import getpass import json import os import re import subprocess import time import urlparse from optparse import OptionParser from BaseHTTPServer import HTTPServer import SimpleHTTPServer import gdata.spreadsheet.service max_spreadsheet_columns = 20 test_props = ['mean', 'error'] def ParseArguments(): parser = OptionParser() parser.add_option("-b", "--browser", action="append", dest="browsers", help="list of browsers to test") parser.add_option("-n", "--run_count", dest="run_count", type="int", default=5, help="number of runs") parser.add_option("-d", "--dromaeo_home", dest="dromaeo_home", help="directory with your dromaeo files") parser.add_option("-p", "--port", dest="port", type="int", default=8080, help="http server port") parser.add_option("-f", "--filter", dest="filter", default="dom", help="dromaeo suite filter") parser.add_option("-e", "--email", dest="email", help="your google docs account") parser.add_option("-s", "--spreadsheet", dest="spreadsheet_title", default="dromaeo", help="your google docs spreadsheet name") options = parser.parse_args()[0] if not options.dromaeo_home: raise Exception('please specify dromaeo_home') return options def KillProcessByName(process_name): process = subprocess.Popen('wmic process get processid, executablepath', stdout=subprocess.PIPE) stdout = str(process.communicate()[0]) match = re.search(re.escape(process_name) + '\s+(\d+)', stdout) if match: pid = match.group(1) subprocess.call('taskkill /pid %s' % pid) class SpreadsheetWriter(object): "Utility class for storing benchmarking results in Google spreadsheets." def __init__(self, email, spreadsheet_title): '''Login to google docs and search for spreadsheet''' self.token_file = os.path.expanduser("~/.dromaeo_bot_auth_token") self.gd_client = gdata.spreadsheet.service.SpreadsheetsService() authenticated = False if os.path.exists(self.token_file): token = '' try: file = open(self.token_file, 'r') token = file.read() file.close() self.gd_client.SetClientLoginToken(token) self.gd_client.GetSpreadsheetsFeed() authenticated = True except (IOError, gdata.service.RequestError): pass if not authenticated: self.gd_client.email = email self.gd_client.password = getpass.getpass('Password for %s: ' % email) self.gd_client.source = 'python robot for dromaeo' self.gd_client.ProgrammaticLogin() token = self.gd_client.GetClientLoginToken() try: file = open(self.token_file, 'w') file.write(token) file.close() except (IOError): pass os.chmod(self.token_file, 0600) # Search for the spreadsheet with title = spreadsheet_title. spreadsheet_feed = self.gd_client.GetSpreadsheetsFeed() for spreadsheet in spreadsheet_feed.entry: if spreadsheet.title.text == spreadsheet_title: self.spreadsheet_key = spreadsheet.id.text.rsplit('/', 1)[1] if not self.spreadsheet_key: raise Exception('Spreadsheet %s not found' % spreadsheet_title) # Get the key of the first worksheet in spreadsheet. worksheet_feed = self.gd_client.GetWorksheetsFeed(self.spreadsheet_key) self.worksheet_key = worksheet_feed.entry[0].id.text.rsplit('/', 1)[1] def _InsertRow(self, row): row = dict([('c' + str(i), row[i]) for i in xrange(len(row))]) self.gd_client.InsertRow(row, self.spreadsheet_key, self.worksheet_key) def _InsertBlankRow(self): self._InsertRow('-' * self.columns_count) def PrepareSpreadsheet(self, run_count): """Update cells in worksheet topmost row with service information. Calculate column count corresponding to run_count and create worksheet column titles [c0, c1, ...] in the topmost row to speed up spreadsheet updates (it allows to insert a whole row with a single request) """ # Calculate the number of columns we need to present all test results. self.columns_count = (run_count + 2) * len(test_props) if self.columns_count > max_spreadsheet_columns: # Google spreadsheet has just max_spreadsheet_columns columns. max_run_count = max_spreadsheet_columns / len(test_props) - 2 raise Exception('maximum run count is %i' % max_run_count) # Create worksheet column titles [c0, c1, ..., cn]. for i in xrange(self.columns_count): self.gd_client.UpdateCell(1, i + 1, 'c' + str(i), self.spreadsheet_key, self.worksheet_key) def WriteColumnTitles(self, run_count): "Create titles for test results (mean 1, mean 2, ..., average mean, ...)" row = [] for prop in test_props: row.append('') for i in xrange(run_count): row.append('%s %i' % (prop, i + 1)) row.append('average ' + prop) self._InsertRow(row) def WriteBrowserBenchmarkTitle(self, browser_name): "Create browser benchmark title (browser name, date time)" self._InsertBlankRow() self._InsertRow([browser_name, time.strftime('%d.%m.%Y %H:%M:%S')]) def WriteBrowserBenchmarkResults(self, test_name, test_data): "Insert a row with single test results" row = [] for prop in test_props: if not row: row.append(test_name) else: row.append('') row.extend([str(x) for x in test_data[prop]]) row.append(str(sum(test_data[prop]) / len(test_data[prop]))) self._InsertRow(row) class DromaeoHandler(SimpleHTTPServer.SimpleHTTPRequestHandler): def do_POST(self): self.send_response(200) self.end_headers() self.wfile.write("POST OK.

"); length = int(self.headers.getheader('content-length')) parameters = urlparse.parse_qs(self.rfile.read(length)) self.server.got_post = True self.server.post_data = parameters['data'] class BenchmarkResults(object): "Storage class for dromaeo benchmark results" def __init__(self): self.data = {} def ProcessBrowserPostData(self, data): "Convert dromaeo test results in internal format" tests = json.loads(data[0]) for test in tests: test_name = test['name'] if test_name not in self.data: # Test is encountered for the first time. self.data[test_name] = dict([(prop, []) for prop in test_props]) # Append current run results. for prop in test_props: value = -1 if prop in test: value = test[prop] # workaround for Opera 10.5 self.data[test_name][prop].append(value) def main(): options = ParseArguments() # Start sever with dromaeo. os.chdir(options.dromaeo_home) server = HTTPServer(('', options.port), DromaeoHandler) # Open and prepare spreadsheet on google docs. spreadsheet_writer = SpreadsheetWriter(options.email, options.spreadsheet_title) spreadsheet_writer.PrepareSpreadsheet(options.run_count) spreadsheet_writer.WriteColumnTitles(options.run_count) for browser in options.browsers: browser_name = os.path.splitext(os.path.basename(browser))[0] spreadsheet_writer.WriteBrowserBenchmarkTitle(browser_name) benchmark_results = BenchmarkResults() for run_number in xrange(options.run_count): print '%s run %i' % (browser_name, run_number + 1) # Run browser. test_page = 'http://localhost:%i/index.html?%s&automated&post_json' % ( options.port, options.filter) browser_process = subprocess.Popen('%s "%s"' % (browser, test_page)) server.got_post = False server.post_data = None # Wait until POST request from browser. while not server.got_post: server.handle_request() benchmark_results.ProcessBrowserPostData(server.post_data) # Kill browser. KillProcessByName(browser) browser_process.wait() # Insert test results into spreadsheet. for (test_name, test_data) in benchmark_results.data.iteritems(): spreadsheet_writer.WriteBrowserBenchmarkResults(test_name, test_data) server.socket.close() return 0 if __name__ == '__main__': sys.exit(main())