| Index: tools/testing/perf_testing/run_perf_tests.py
|
| diff --git a/tools/testing/perf_testing/run_perf_tests.py b/tools/testing/perf_testing/run_perf_tests.py
|
| deleted file mode 100755
|
| index 194bbbcb3f19122905ab3ccdabb1a2d3b17dc7bd..0000000000000000000000000000000000000000
|
| --- a/tools/testing/perf_testing/run_perf_tests.py
|
| +++ /dev/null
|
| @@ -1,1087 +0,0 @@
|
| -#!/usr/bin/python
|
| -
|
| -# Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
|
| -# for details. All rights reserved. Use of this source code is governed by a
|
| -# BSD-style license that can be found in the LICENSE file.
|
| -
|
| -import datetime
|
| -import math
|
| -import optparse
|
| -import os
|
| -from os.path import dirname, abspath
|
| -import pickle
|
| -import platform
|
| -import random
|
| -import re
|
| -import shutil
|
| -import stat
|
| -import subprocess
|
| -import sys
|
| -import time
|
| -
|
| -TOOLS_PATH = os.path.join(dirname(dirname(dirname(abspath(__file__)))))
|
| -TOP_LEVEL_DIR = abspath(os.path.join(dirname(abspath(__file__)), '..', '..',
|
| - '..'))
|
| -DART_REPO_LOC = abspath(os.path.join(dirname(abspath(__file__)), '..', '..',
|
| - '..', '..', '..',
|
| - 'dart_checkout_for_perf_testing',
|
| - 'dart'))
|
| -# How far back in time we want to test.
|
| -EARLIEST_REVISION = 33076
|
| -sys.path.append(TOOLS_PATH)
|
| -sys.path.append(os.path.join(TOP_LEVEL_DIR, 'internal', 'tests'))
|
| -import post_results
|
| -import utils
|
| -
|
| -"""This script runs to track performance and size progress of
|
| -different svn revisions. It tests to see if there a newer version of the code on
|
| -the server, and will sync and run the performance tests if so."""
|
| -class TestRunner(object):
|
| -
|
| - def __init__(self):
|
| - self.verbose = False
|
| - self.has_shell = False
|
| - if platform.system() == 'Windows':
|
| - # On Windows, shell must be true to get the correct environment variables.
|
| - self.has_shell = True
|
| - self.current_revision_num = None
|
| -
|
| - def RunCmd(self, cmd_list, outfile=None, append=False, std_in=''):
|
| - """Run the specified command and print out any output to stdout.
|
| -
|
| - Args:
|
| - cmd_list: a list of strings that make up the command to run
|
| - outfile: a string indicating the name of the file that we should write
|
| - stdout to
|
| - append: True if we want to append to the file instead of overwriting it
|
| - std_in: a string that should be written to the process executing to
|
| - interact with it (if needed)"""
|
| - if self.verbose:
|
| - print ' '.join(cmd_list)
|
| - out = subprocess.PIPE
|
| - if outfile:
|
| - mode = 'w'
|
| - if append:
|
| - mode = 'a+'
|
| - out = open(outfile, mode)
|
| - if append:
|
| - # Annoying Windows "feature" -- append doesn't actually append unless
|
| - # you explicitly go to the end of the file.
|
| - # http://mail.python.org/pipermail/python-list/2009-October/1221859.html
|
| - out.seek(0, os.SEEK_END)
|
| - p = subprocess.Popen(cmd_list, stdout = out, stderr=subprocess.PIPE,
|
| - stdin=subprocess.PIPE, shell=self.has_shell)
|
| - output, stderr = p.communicate(std_in)
|
| - if output:
|
| - print output
|
| - if stderr:
|
| - print stderr
|
| - return output, stderr
|
| -
|
| - def RunBrowserPerfRunnerCmd(self, browser, url_path, file_path_to_test_code,
|
| - trace_file, code_root=''):
|
| - command_list = [os.path.join(DART_REPO_LOC, utils.GetBuildRoot(
|
| - utils.GuessOS(), 'release', 'ia32'), 'dart-sdk', 'bin', 'dart'),
|
| - '--package-root=%s' % os.path.join(file_path_to_test_code, 'packages'),
|
| - os.path.join(file_path_to_test_code, 'packages',
|
| - 'browser_controller', 'browser_perf_testing.dart'), '--browser',
|
| - browser, '--test_path=%s' % url_path]
|
| - if code_root != '':
|
| - command_list += ['--code_root=%s' % code_root]
|
| -
|
| - if browser == 'dartium':
|
| - dartium_path = os.path.join(DART_REPO_LOC, 'client', 'tests', 'dartium')
|
| - if platform.system() == 'Windows':
|
| - dartium_path = os.path.join(dartium_path, 'chrome.exe');
|
| - elif platform.system() == 'Darwin':
|
| - dartium_path = os.path.join(dartium_path, 'Chromium.app', 'Contents',
|
| - 'MacOS', 'Chromium')
|
| - else:
|
| - dartium_path = os.path.join(dartium_path, 'chrome')
|
| - command_list += ['--executable=%s' % dartium_path]
|
| -
|
| - self.RunCmd(command_list, trace_file, append=True)
|
| -
|
| - def TimeCmd(self, cmd):
|
| - """Determine the amount of (real) time it takes to execute a given
|
| - command."""
|
| - start = time.time()
|
| - self.RunCmd(cmd)
|
| - return time.time() - start
|
| -
|
| - def ClearOutUnversionedFiles(self):
|
| - """Remove all files that are unversioned by svn."""
|
| - if os.path.exists(DART_REPO_LOC):
|
| - os.chdir(DART_REPO_LOC)
|
| - results, _ = self.RunCmd(['svn', 'st'])
|
| - for line in results.split('\n'):
|
| - if line.startswith('?'):
|
| - to_remove = line.split()[1]
|
| - if os.path.isdir(to_remove):
|
| - shutil.rmtree(to_remove, onerror=TestRunner._OnRmError)
|
| - else:
|
| - os.remove(to_remove)
|
| - elif any(line.startswith(status) for status in ['A', 'M', 'C', 'D']):
|
| - self.RunCmd(['svn', 'revert', line.split()[1]])
|
| -
|
| - def GetArchive(self, archive_name):
|
| - """Wrapper around the pulling down a specific archive from Google Storage.
|
| - Adds a specific revision argument as needed.
|
| - Returns: A tuple of a boolean (True if we successfully downloaded the
|
| - binary), and the stdout and stderr from running this command."""
|
| - num_fails = 0
|
| - while True:
|
| - cmd = ['python', os.path.join(DART_REPO_LOC, 'tools', 'get_archive.py'),
|
| - archive_name]
|
| - if int(self.current_revision_num) != -1:
|
| - cmd += ['-r', str(self.current_revision_num)]
|
| - stdout, stderr = self.RunCmd(cmd)
|
| - if 'Please try again later' in stdout and num_fails < 20:
|
| - time.sleep(100)
|
| - num_fails += 1
|
| - else:
|
| - break
|
| - return (num_fails < 20, stdout, stderr)
|
| -
|
| - def _Sync(self, revision_num=None):
|
| - """Update the repository to the latest or specified revision."""
|
| - os.chdir(dirname(DART_REPO_LOC))
|
| - self.ClearOutUnversionedFiles()
|
| - if not revision_num:
|
| - self.RunCmd(['gclient', 'sync'])
|
| - else:
|
| - self.RunCmd(['gclient', 'sync', '-r', str(revision_num), '-t'])
|
| -
|
| - shutil.copytree(os.path.join(TOP_LEVEL_DIR, 'internal'),
|
| - os.path.join(DART_REPO_LOC, 'internal'))
|
| - shutil.rmtree(os.path.join(DART_REPO_LOC, 'third_party', 'gsutil'),
|
| - onerror=TestRunner._OnRmError)
|
| - shutil.copytree(os.path.join(TOP_LEVEL_DIR, 'third_party', 'gsutil'),
|
| - os.path.join(DART_REPO_LOC, 'third_party', 'gsutil'))
|
| - shutil.copy(os.path.join(TOP_LEVEL_DIR, 'tools', 'get_archive.py'),
|
| - os.path.join(DART_REPO_LOC, 'tools', 'get_archive.py'))
|
| - shutil.copy(
|
| - os.path.join(TOP_LEVEL_DIR, 'tools', 'testing', 'run_selenium.py'),
|
| - os.path.join(DART_REPO_LOC, 'tools', 'testing', 'run_selenium.py'))
|
| -
|
| - @staticmethod
|
| - def _OnRmError(func, path, exc_info):
|
| - """On Windows, the output directory is marked as "Read Only," which causes
|
| - an error to be thrown when we use shutil.rmtree. This helper function
|
| - changes the permissions so we can still delete the directory."""
|
| - if os.path.exists(path):
|
| - os.chmod(path, stat.S_IWRITE)
|
| - os.unlink(path)
|
| -
|
| - def SyncAndBuild(self, suites, revision_num=None):
|
| - """Make sure we have the latest version of of the repo, and build it. We
|
| - begin and end standing in DART_REPO_LOC.
|
| -
|
| - Args:
|
| - suites: The set of suites that we wish to build.
|
| -
|
| - Returns:
|
| - err_code = 1 if there was a problem building."""
|
| - self._Sync(revision_num)
|
| - if not revision_num:
|
| - revision_num = SearchForRevision()
|
| -
|
| - self.current_revision_num = revision_num
|
| - success, stdout, stderr = self.GetArchive('sdk')
|
| - if (not os.path.exists(os.path.join(
|
| - DART_REPO_LOC, 'tools', 'get_archive.py')) or not success
|
| - or 'InvalidUriError' in stderr or "Couldn't download" in stdout or
|
| - 'Unable to download' in stdout):
|
| - # Couldn't find the SDK on Google Storage. Build it locally.
|
| -
|
| - # TODO(efortuna): Currently always building ia32 architecture because we
|
| - # don't have test statistics for what's passing on x64. Eliminate arch
|
| - # specification when we have tests running on x64, too.
|
| - shutil.rmtree(os.path.join(os.getcwd(),
|
| - utils.GetBuildRoot(utils.GuessOS())),
|
| - onerror=TestRunner._OnRmError)
|
| - lines = self.RunCmd(['python', os.path.join('tools', 'build.py'), '-m',
|
| - 'release', '--arch=ia32', 'create_sdk'])
|
| -
|
| - for line in lines:
|
| - if 'BUILD FAILED' in line:
|
| - # Someone checked in a broken build! Stop trying to make it work
|
| - # and wait to try again.
|
| - print 'Broken Build'
|
| - return 1
|
| - return 0
|
| -
|
| - def EnsureOutputDirectory(self, dir_name):
|
| - """Test that the listed directory name exists, and if not, create one for
|
| - our output to be placed.
|
| -
|
| - Args:
|
| - dir_name: the directory we will create if it does not exist."""
|
| - dir_path = os.path.join(TOP_LEVEL_DIR, 'tools',
|
| - 'testing', 'perf_testing', dir_name)
|
| - if not os.path.exists(dir_path):
|
| - os.makedirs(dir_path)
|
| - print 'Creating output directory ', dir_path
|
| -
|
| - def HasInterestingCode(self, revision_num=None):
|
| - """Tests if there are any versions of files that might change performance
|
| - results on the server.
|
| -
|
| - Returns:
|
| - (False, None): There is no interesting code to run.
|
| - (True, revisionNumber): There is interesting code to run at revision
|
| - revisionNumber.
|
| - (True, None): There is interesting code to run by syncing to the
|
| - tip-of-tree."""
|
| - if not os.path.exists(DART_REPO_LOC):
|
| - self._Sync()
|
| - os.chdir(DART_REPO_LOC)
|
| - no_effect = ['dart/client', 'dart/compiler', 'dart/editor',
|
| - 'dart/lib/html/doc', 'dart/pkg', 'dart/tests', 'dart/samples',
|
| - 'dart/lib/dartdoc', 'dart/lib/i18n', 'dart/lib/unittest',
|
| - 'dart/tools/dartc', 'dart/tools/get_archive.py',
|
| - 'dart/tools/test.py', 'dart/tools/testing',
|
| - 'dart/tools/utils', 'dart/third_party', 'dart/utils']
|
| - definitely_yes = ['dart/samples/third_party/dromaeo',
|
| - 'dart/lib/html/dart2js', 'dart/lib/html/dartium',
|
| - 'dart/lib/scripts', 'dart/lib/src',
|
| - 'dart/third_party/WebCore']
|
| - def GetFileList(revision):
|
| - """Determine the set of files that were changed for a particular
|
| - revision."""
|
| - # TODO(efortuna): This assumes you're using svn. Have a git fallback as
|
| - # well. Pass 'p' in if we have a new certificate for the svn server, we
|
| - # want to (p)ermanently accept it.
|
| - results, _ = self.RunCmd([
|
| - 'svn', 'log', 'http://dart.googlecode.com/svn/branches/bleeding_edge',
|
| - '-v', '-r', str(revision)], std_in='p\r\n')
|
| - results = results.split('\n')
|
| - if len(results) <= 3:
|
| - return []
|
| - else:
|
| - # Trim off the details about revision number and commit message. We're
|
| - # only interested in the files that are changed.
|
| - results = results[3:]
|
| - changed_files = []
|
| - for result in results:
|
| - if len(result) <= 1:
|
| - break
|
| - tokens = result.split()
|
| - if len(tokens) > 1:
|
| - changed_files += [tokens[1].replace('/branches/bleeding_edge/', '')]
|
| - return changed_files
|
| -
|
| - def HasPerfAffectingResults(files_list):
|
| - """Determine if this set of changed files might effect performance
|
| - tests."""
|
| - def IsSafeFile(f):
|
| - if not any(f.startswith(prefix) for prefix in definitely_yes):
|
| - return any(f.startswith(prefix) for prefix in no_effect)
|
| - return False
|
| - return not all(IsSafeFile(f) for f in files_list)
|
| -
|
| - if revision_num:
|
| - return (HasPerfAffectingResults(GetFileList(
|
| - revision_num)), revision_num)
|
| - else:
|
| - latest_interesting_server_rev = None
|
| - while not latest_interesting_server_rev:
|
| - results, _ = self.RunCmd(['svn', 'st', '-u'], std_in='p\r\n')
|
| - if len(results.split('\n')) >= 2:
|
| - latest_interesting_server_rev = int(
|
| - results.split('\n')[-2].split()[-1])
|
| - if self.backfill:
|
| - done_cls = list(UpdateSetOfDoneCls())
|
| - done_cls.sort()
|
| - if done_cls:
|
| - last_done_cl = int(done_cls[-1])
|
| - else:
|
| - last_done_cl = EARLIEST_REVISION
|
| - while latest_interesting_server_rev >= last_done_cl:
|
| - file_list = GetFileList(latest_interesting_server_rev)
|
| - if HasPerfAffectingResults(file_list):
|
| - return (True, latest_interesting_server_rev)
|
| - else:
|
| - UpdateSetOfDoneCls(latest_interesting_server_rev)
|
| - latest_interesting_server_rev -= 1
|
| - else:
|
| - last_done_cl = int(SearchForRevision(DART_REPO_LOC)) + 1
|
| - while last_done_cl <= latest_interesting_server_rev:
|
| - file_list = GetFileList(last_done_cl)
|
| - if HasPerfAffectingResults(file_list):
|
| - return (True, last_done_cl)
|
| - else:
|
| - UpdateSetOfDoneCls(last_done_cl)
|
| - last_done_cl += 1
|
| - return (False, None)
|
| -
|
| - def GetOsDirectory(self):
|
| - """Specifies the name of the directory for the testing build of dart, which
|
| - has yet a different naming convention from utils.getBuildRoot(...)."""
|
| - if platform.system() == 'Windows':
|
| - return 'windows'
|
| - elif platform.system() == 'Darwin':
|
| - return 'macos'
|
| - else:
|
| - return 'linux'
|
| -
|
| - def ParseArgs(self):
|
| - parser = optparse.OptionParser()
|
| - parser.add_option('--suites', '-s', dest='suites', help='Run the specified '
|
| - 'comma-separated test suites from set: %s' % \
|
| - ','.join(TestBuilder.AvailableSuiteNames()),
|
| - action='store', default=None)
|
| - parser.add_option('--forever', '-f', dest='continuous', help='Run this scri'
|
| - 'pt forever, always checking for the next svn checkin',
|
| - action='store_true', default=False)
|
| - parser.add_option('--nobuild', '-n', dest='no_build', action='store_true',
|
| - help='Do not sync with the repository and do not '
|
| - 'rebuild.', default=False)
|
| - parser.add_option('--noupload', '-u', dest='no_upload', action='store_true',
|
| - help='Do not post the results of the run.', default=False)
|
| - parser.add_option('--notest', '-t', dest='no_test', action='store_true',
|
| - help='Do not run the tests.', default=False)
|
| - parser.add_option('--verbose', '-v', dest='verbose',
|
| - help='Print extra debug output', action='store_true',
|
| - default=False)
|
| - parser.add_option('--backfill', '-b', dest='backfill',
|
| - help='Backfill earlier CLs with additional results when '
|
| - 'there is idle time.', action='store_true',
|
| - default=False)
|
| -
|
| - args, ignored = parser.parse_args()
|
| -
|
| - if not args.suites:
|
| - suites = TestBuilder.AvailableSuiteNames()
|
| - else:
|
| - suites = []
|
| - suitelist = args.suites.split(',')
|
| - for name in suitelist:
|
| - if name in TestBuilder.AvailableSuiteNames():
|
| - suites.append(name)
|
| - else:
|
| - print ('Error: Invalid suite %s not in ' % name) + \
|
| - '%s' % ','.join(TestBuilder.AvailableSuiteNames())
|
| - sys.exit(1)
|
| - self.suite_names = suites
|
| - self.no_build = args.no_build
|
| - self.no_upload = args.no_upload
|
| - self.no_test = args.no_test
|
| - self.verbose = args.verbose
|
| - self.backfill = args.backfill
|
| - return args.continuous
|
| -
|
| - def RunTestSequence(self, revision_num=None, num_reruns=1):
|
| - """Run the set of commands to (possibly) build, run, and post the results
|
| - of our tests. Returns 0 on a successful run, 1 if we fail to post results or
|
| - the run failed, -1 if the build is broken.
|
| - """
|
| - suites = []
|
| - success = True
|
| - if not self.no_build and self.SyncAndBuild(suites, revision_num) == 1:
|
| - return -1 # The build is broken.
|
| -
|
| - if not self.current_revision_num:
|
| - self.current_revision_num = SearchForRevision(DART_REPO_LOC)
|
| -
|
| - for name in self.suite_names:
|
| - for run in range(num_reruns):
|
| - suites += [TestBuilder.MakeTest(name, self)]
|
| -
|
| - for test in suites:
|
| - success = success and test.Run()
|
| - if success:
|
| - return 0
|
| - else:
|
| - return 1
|
| -
|
| -
|
| -class Test(object):
|
| - """The base class to provide shared code for different tests we will run and
|
| - post. At a high level, each test has three visitors (the tester and the
|
| - file_processor) that perform operations on the test object."""
|
| -
|
| - def __init__(self, result_folder_name, platform_list, variants,
|
| - values_list, test_runner, tester, file_processor,
|
| - extra_metrics=['Geo-Mean']):
|
| - """Args:
|
| - result_folder_name: The name of the folder where a tracefile of
|
| - performance results will be stored.
|
| - platform_list: A list containing the platform(s) that our data has been
|
| - run on. (command line, firefox, chrome, etc)
|
| - variants: A list specifying whether we hold data about Frog
|
| - generated code, plain JS code, or a combination of both, or
|
| - Dart depending on the test.
|
| - values_list: A list containing the type of data we will be graphing
|
| - (benchmarks, percentage passing, etc).
|
| - test_runner: Reference to the parent test runner object that notifies a
|
| - test when to run.
|
| - tester: The visitor that actually performs the test running mechanics.
|
| - file_processor: The visitor that processes files in the format
|
| - appropriate for this test.
|
| - extra_metrics: A list of any additional measurements we wish to keep
|
| - track of (such as the geometric mean of a set, the sum, etc)."""
|
| - self.result_folder_name = result_folder_name
|
| - # cur_time is used as a timestamp of when this performance test was run.
|
| - self.cur_time = str(time.mktime(datetime.datetime.now().timetuple()))
|
| - self.values_list = values_list
|
| - self.platform_list = platform_list
|
| - self.test_runner = test_runner
|
| - self.tester = tester
|
| - self.file_processor = file_processor
|
| - self.revision_dict = dict()
|
| - self.values_dict = dict()
|
| - self.extra_metrics = extra_metrics
|
| - # Initialize our values store.
|
| - for platform in platform_list:
|
| - self.revision_dict[platform] = dict()
|
| - self.values_dict[platform] = dict()
|
| - for f in variants:
|
| - self.revision_dict[platform][f] = dict()
|
| - self.values_dict[platform][f] = dict()
|
| - for val in values_list:
|
| - self.revision_dict[platform][f][val] = []
|
| - self.values_dict[platform][f][val] = []
|
| - for extra_metric in extra_metrics:
|
| - self.revision_dict[platform][f][extra_metric] = []
|
| - self.values_dict[platform][f][extra_metric] = []
|
| -
|
| - def IsValidCombination(self, platform, variant):
|
| - """Check whether data should be captured for this platform/variant
|
| - combination.
|
| - """
|
| - if variant == 'dart_html' and platform != 'dartium':
|
| - return False
|
| - if platform == 'dartium' and (variant == 'js' or variant == 'dart2js_html'):
|
| - # Testing JavaScript performance on Dartium is a waste of time. Should be
|
| - # same as Chrome.
|
| - return False
|
| - if (platform == 'safari' and variant == 'dart2js' and
|
| - int(self.test_runner.current_revision_num) < 10193):
|
| - # In revision 10193 we fixed a bug that allows Safari 6 to run dart2js
|
| - # code. Since we can't change the Safari version on the machine, we're
|
| - # just not running
|
| - # for this case.
|
| - return False
|
| - return True
|
| -
|
| - def Run(self):
|
| - """Run the benchmarks/tests from the command line and plot the
|
| - results.
|
| - """
|
| - for visitor in [self.tester, self.file_processor]:
|
| - visitor.Prepare()
|
| -
|
| - os.chdir(TOP_LEVEL_DIR)
|
| - self.test_runner.EnsureOutputDirectory(self.result_folder_name)
|
| - self.test_runner.EnsureOutputDirectory(os.path.join(
|
| - 'old', self.result_folder_name))
|
| - os.chdir(DART_REPO_LOC)
|
| - if not self.test_runner.no_test:
|
| - self.tester.RunTests()
|
| -
|
| - os.chdir(os.path.join(TOP_LEVEL_DIR, 'tools', 'testing', 'perf_testing'))
|
| -
|
| - files = os.listdir(self.result_folder_name)
|
| - post_success = True
|
| - for afile in files:
|
| - if not afile.startswith('.'):
|
| - should_move_file = self.file_processor.ProcessFile(afile, True)
|
| - if should_move_file:
|
| - shutil.move(os.path.join(self.result_folder_name, afile),
|
| - os.path.join('old', self.result_folder_name, afile))
|
| - else:
|
| - post_success = False
|
| -
|
| - return post_success
|
| -
|
| -
|
| -class Tester(object):
|
| - """The base level visitor class that runs tests. It contains convenience
|
| - methods that many Tester objects use. Any class that would like to be a
|
| - TesterVisitor must implement the RunTests() method."""
|
| -
|
| - def __init__(self, test):
|
| - self.test = test
|
| -
|
| - def Prepare(self):
|
| - """Perform any initial setup required before the test is run."""
|
| - pass
|
| -
|
| - def AddSvnRevisionToTrace(self, outfile, browser = None):
|
| - """Add the svn version number to the provided tracefile."""
|
| - def get_dartium_revision():
|
| - version_file_name = os.path.join(DART_REPO_LOC, 'client', 'tests',
|
| - 'dartium', 'LAST_VERSION')
|
| - try:
|
| - version_file = open(version_file_name, 'r')
|
| - version = version_file.read().split('.')[-3].split('-')[-1]
|
| - version_file.close()
|
| - return version
|
| - except IOError as e:
|
| - dartium_dir = os.path.join(DART_REPO_LOC, 'client', 'tests', 'dartium')
|
| - if (os.path.exists(os.path.join(dartium_dir, 'Chromium.app', 'Contents',
|
| - 'MacOS', 'Chromium') or os.path.exists(os.path.join(dartium_dir,
|
| - 'chrome.exe'))) or
|
| - os.path.exists(os.path.join(dartium_dir, 'chrome'))):
|
| - print "Error: VERSION file wasn't found."
|
| - return SearchForRevision()
|
| - else:
|
| - raise
|
| -
|
| - if browser and browser == 'dartium':
|
| - revision = get_dartium_revision()
|
| - self.test.test_runner.RunCmd(['echo', 'Revision: ' + revision], outfile)
|
| - else:
|
| - revision = SearchForRevision()
|
| - self.test.test_runner.RunCmd(['echo', 'Revision: ' + revision], outfile)
|
| -
|
| -
|
| -class Processor(object):
|
| - """The base level vistor class that processes tests. It contains convenience
|
| - methods that many File Processor objects use. Any class that would like to be
|
| - a ProcessorVisitor must implement the ProcessFile() method."""
|
| -
|
| - SCORE = 'Score'
|
| - COMPILE_TIME = 'CompileTime'
|
| - CODE_SIZE = 'CodeSize'
|
| -
|
| - def __init__(self, test):
|
| - self.test = test
|
| -
|
| - def Prepare(self):
|
| - """Perform any initial setup required before the test is run."""
|
| - pass
|
| -
|
| - def OpenTraceFile(self, afile, not_yet_uploaded):
|
| - """Find the correct location for the trace file, and open it.
|
| - Args:
|
| - afile: The tracefile name.
|
| - not_yet_uploaded: True if this file is to be found in a directory that
|
| - contains un-uploaded data.
|
| - Returns: A file object corresponding to the given file name."""
|
| - file_path = os.path.join(self.test.result_folder_name, afile)
|
| - if not not_yet_uploaded:
|
| - file_path = os.path.join('old', file_path)
|
| - return open(file_path)
|
| -
|
| - def ReportResults(self, benchmark_name, score, platform, variant,
|
| - revision_number, metric):
|
| - """Store the results of the benchmark run.
|
| - Args:
|
| - benchmark_name: The name of the individual benchmark.
|
| - score: The numerical value of this benchmark.
|
| - platform: The platform the test was run on (firefox, command line, etc).
|
| - variant: Specifies whether the data was about generated Frog, js, a
|
| - combination of both, or Dart depending on the test.
|
| - revision_number: The revision of the code (and sometimes the revision of
|
| - dartium).
|
| -
|
| - Returns: True if the post was successful file."""
|
| - return post_results.report_results(benchmark_name, score, platform, variant,
|
| - revision_number, metric)
|
| -
|
| - def CalculateGeometricMean(self, platform, variant, svn_revision):
|
| - """Calculate the aggregate geometric mean for JS and dart2js benchmark sets,
|
| - given two benchmark dictionaries."""
|
| - geo_mean = 0
|
| - if self.test.IsValidCombination(platform, variant):
|
| - for benchmark in self.test.values_list:
|
| - if not self.test.values_dict[platform][variant][benchmark]:
|
| - print 'Error determining mean for %s %s %s' % (platform, variant,
|
| - benchmark)
|
| - continue
|
| - geo_mean += math.log(
|
| - self.test.values_dict[platform][variant][benchmark][-1])
|
| -
|
| - self.test.values_dict[platform][variant]['Geo-Mean'] += \
|
| - [math.pow(math.e, geo_mean / len(self.test.values_list))]
|
| - self.test.revision_dict[platform][variant]['Geo-Mean'] += [svn_revision]
|
| -
|
| - def GetScoreType(self, benchmark_name):
|
| - """Determine the type of score for posting -- default is 'Score' (aka
|
| - Runtime), other options are CompileTime and CodeSize."""
|
| - return self.SCORE
|
| -
|
| -
|
| -class RuntimePerformanceTest(Test):
|
| - """Super class for all runtime performance testing."""
|
| -
|
| - def __init__(self, result_folder_name, platform_list, platform_type,
|
| - versions, benchmarks, test_runner, tester, file_processor):
|
| - """Args:
|
| - result_folder_name: The name of the folder where a tracefile of
|
| - performance results will be stored.
|
| - platform_list: A list containing the platform(s) that our data has been
|
| - run on. (command line, firefox, chrome, etc)
|
| - variants: A list specifying whether we hold data about Frog
|
| - generated code, plain JS code, or a combination of both, or
|
| - Dart depending on the test.
|
| - values_list: A list containing the type of data we will be graphing
|
| - (benchmarks, percentage passing, etc).
|
| - test_runner: Reference to the parent test runner object that notifies a
|
| - test when to run.
|
| - tester: The visitor that actually performs the test running mechanics.
|
| - file_processor: The visitor that processes files in the format
|
| - appropriate for this test.
|
| - extra_metrics: A list of any additional measurements we wish to keep
|
| - track of (such as the geometric mean of a set, the sum, etc)."""
|
| - super(RuntimePerformanceTest, self).__init__(result_folder_name,
|
| - platform_list, versions, benchmarks, test_runner, tester,
|
| - file_processor)
|
| - self.platform_list = platform_list
|
| - self.platform_type = platform_type
|
| - self.versions = versions
|
| - self.benchmarks = benchmarks
|
| -
|
| -
|
| -class BrowserTester(Tester):
|
| - @staticmethod
|
| - def GetBrowsers(add_dartium=True):
|
| - browsers = ['ff', 'chrome']
|
| - if add_dartium:
|
| - browsers += ['dartium']
|
| - has_shell = False
|
| - if platform.system() == 'Darwin':
|
| - browsers += ['safari']
|
| - if platform.system() == 'Windows':
|
| - browsers += ['ie']
|
| - has_shell = True
|
| - return browsers
|
| -
|
| -
|
| -class DromaeoTester(Tester):
|
| - DROMAEO_BENCHMARKS = {
|
| - 'attr': ('attributes', [
|
| - 'getAttribute',
|
| - 'element.property',
|
| - 'setAttribute',
|
| - 'element.property = value']),
|
| - 'modify': ('modify', [
|
| - 'createElement',
|
| - 'createTextNode',
|
| - 'innerHTML',
|
| - 'cloneNode',
|
| - 'appendChild',
|
| - 'insertBefore']),
|
| - 'query': ('query', [
|
| - 'getElementById',
|
| - 'getElementById (not in document)',
|
| - 'getElementsByTagName(div)',
|
| - 'getElementsByTagName(p)',
|
| - 'getElementsByTagName(a)',
|
| - 'getElementsByTagName(*)',
|
| - 'getElementsByTagName (not in document)',
|
| - 'getElementsByName',
|
| - 'getElementsByName (not in document)']),
|
| - 'traverse': ('traverse', [
|
| - 'firstChild',
|
| - 'lastChild',
|
| - 'nextSibling',
|
| - 'previousSibling',
|
| - 'childNodes'])
|
| - }
|
| -
|
| - # Use filenames that don't have unusual characters for benchmark names.
|
| - @staticmethod
|
| - def LegalizeFilename(str):
|
| - remap = {
|
| - ' ': '_',
|
| - '(': '_',
|
| - ')': '_',
|
| - '*': 'ALL',
|
| - '=': 'ASSIGN',
|
| - }
|
| - for (old, new) in remap.iteritems():
|
| - str = str.replace(old, new)
|
| - return str
|
| -
|
| - # TODO(vsm): This is a hack to skip breaking tests. Triage this
|
| - # failure properly. The modify suite fails on 32-bit chrome, which
|
| - # is the default on mac and win.
|
| - @staticmethod
|
| - def GetValidDromaeoTags():
|
| - tags = [tag for (tag, _) in DromaeoTester.DROMAEO_BENCHMARKS.values()]
|
| - if platform.system() == 'Darwin' or platform.system() == 'Windows':
|
| - tags.remove('modify')
|
| - return tags
|
| -
|
| - @staticmethod
|
| - def GetDromaeoBenchmarks():
|
| - valid = DromaeoTester.GetValidDromaeoTags()
|
| - benchmarks = reduce(lambda l1,l2: l1+l2,
|
| - [tests for (tag, tests) in
|
| - DromaeoTester.DROMAEO_BENCHMARKS.values()
|
| - if tag in valid])
|
| - return map(DromaeoTester.LegalizeFilename, benchmarks)
|
| -
|
| - @staticmethod
|
| - def GetDromaeoVersions():
|
| - return ['js', 'dart2js_html', 'dart_html']
|
| -
|
| -
|
| -class DromaeoTest(RuntimePerformanceTest):
|
| - """Runs Dromaeo tests, in the browser."""
|
| - def __init__(self, test_runner):
|
| - super(DromaeoTest, self).__init__(
|
| - self.Name(),
|
| - BrowserTester.GetBrowsers(True),
|
| - 'browser',
|
| - DromaeoTester.GetDromaeoVersions(),
|
| - DromaeoTester.GetDromaeoBenchmarks(), test_runner,
|
| - self.DromaeoPerfTester(self),
|
| - self.DromaeoFileProcessor(self))
|
| -
|
| - @staticmethod
|
| - def Name():
|
| - return 'dromaeo'
|
| -
|
| - class DromaeoPerfTester(DromaeoTester):
|
| - def RunTests(self):
|
| - """Run dromaeo in the browser."""
|
| - success, _, _ = self.test.test_runner.GetArchive('dartium')
|
| - if not success:
|
| - # Unable to download dartium. Try later.
|
| - return
|
| -
|
| - # Build tests.
|
| - current_path = os.getcwd()
|
| - os.chdir(os.path.join(DART_REPO_LOC, 'samples', 'third_party',
|
| - 'dromaeo'))
|
| - # Note: This uses debug on purpose, so that we can also run performance
|
| - # tests on pure Dart applications in Dartium. Pub --debug simply also
|
| - # moves the .dart files to the build directory. To ensure effective
|
| - # comparison, though, ensure that minify: true is set in your transformer
|
| - # compilation step in your pubspec.
|
| - stdout, _ = self.test.test_runner.RunCmd([os.path.join(DART_REPO_LOC,
|
| - utils.GetBuildRoot(utils.GuessOS(), 'release', 'ia32'),
|
| - 'dart-sdk', 'bin', 'pub'), 'build', '--mode=debug'])
|
| - os.chdir(current_path)
|
| - if 'failed' in stdout:
|
| - return
|
| -
|
| - versions = DromaeoTester.GetDromaeoVersions()
|
| -
|
| - for browser in BrowserTester.GetBrowsers():
|
| - for version_name in versions:
|
| - if not self.test.IsValidCombination(browser, version_name):
|
| - continue
|
| - version = DromaeoTest.DromaeoPerfTester.GetDromaeoUrlQuery(
|
| - browser, version_name)
|
| - self.test.trace_file = os.path.join(TOP_LEVEL_DIR,
|
| - 'tools', 'testing', 'perf_testing', self.test.result_folder_name,
|
| - 'dromaeo-%s-%s-%s' % (self.test.cur_time, browser, version_name))
|
| - self.AddSvnRevisionToTrace(self.test.trace_file, browser)
|
| - url_path = '/'.join(['/code_root', 'build', 'web', 'index%s.html?%s'%(
|
| - '-dart' if version_name == 'dart_html' else '-js',
|
| - version)])
|
| -
|
| - self.test.test_runner.RunBrowserPerfRunnerCmd(browser, url_path,
|
| - os.path.join(DART_REPO_LOC, 'samples', 'third_party', 'dromaeo'),
|
| - self.test.trace_file)
|
| -
|
| - @staticmethod
|
| - def GetDromaeoUrlQuery(browser, version):
|
| - version = version.replace('_','AND')
|
| - tags = DromaeoTester.GetValidDromaeoTags()
|
| - return 'OR'.join([ '%sAND%s' % (version, tag) for tag in tags])
|
| -
|
| -
|
| - class DromaeoFileProcessor(Processor):
|
| - def ProcessFile(self, afile, should_post_file):
|
| - """Comb through the html to find the performance results.
|
| - Returns: True if we successfully posted our data to storage."""
|
| - parts = afile.split('-')
|
| - browser = parts[2]
|
| - version = parts[3]
|
| -
|
| - bench_dict = self.test.values_dict[browser][version]
|
| -
|
| - f = self.OpenTraceFile(afile, should_post_file)
|
| - lines = f.readlines()
|
| - i = 0
|
| - revision_num = 0
|
| - revision_pattern = r'Revision: (\d+)'
|
| - suite_pattern = r'<div class="result-item done">(.+?)</ol></div>'
|
| - result_pattern = r'<b>(.+?)</b>(.+?)<small> runs/s(.+)'
|
| -
|
| - upload_success = True
|
| - for line in lines:
|
| - rev = re.match(revision_pattern, line.strip().replace('"', ''))
|
| - if rev:
|
| - revision_num = int(rev.group(1))
|
| - continue
|
| -
|
| - suite_results = re.findall(suite_pattern, line)
|
| - if suite_results:
|
| - for suite_result in suite_results:
|
| - results = re.findall(r'<li>(.*?)</li>', suite_result)
|
| - if results:
|
| - for result in results:
|
| - r = re.match(result_pattern, result)
|
| - name = DromaeoTester.LegalizeFilename(r.group(1).strip(':'))
|
| - score = float(r.group(2))
|
| - bench_dict[name] += [float(score)]
|
| - self.test.revision_dict[browser][version][name] += \
|
| - [revision_num]
|
| - if not self.test.test_runner.no_upload and should_post_file:
|
| - upload_success = upload_success and self.ReportResults(
|
| - name, score, browser, version, revision_num,
|
| - self.GetScoreType(name))
|
| - else:
|
| - upload_success = False
|
| -
|
| - f.close()
|
| - self.CalculateGeometricMean(browser, version, revision_num)
|
| - return upload_success
|
| -
|
| -class TodoMVCTester(BrowserTester):
|
| - @staticmethod
|
| - def GetVersions():
|
| - return ['js', 'dart2js_html', 'dart_html']
|
| -
|
| - @staticmethod
|
| - def GetBenchmarks():
|
| - return ['TodoMVCstartup']
|
| -
|
| -class TodoMVCStartupTest(RuntimePerformanceTest):
|
| - """Start up TodoMVC and see how long it takes to start."""
|
| - def __init__(self, test_runner):
|
| - super(TodoMVCStartupTest, self).__init__(
|
| - self.Name(),
|
| - BrowserTester.GetBrowsers(True),
|
| - 'browser',
|
| - TodoMVCTester.GetVersions(),
|
| - TodoMVCTester.GetBenchmarks(), test_runner,
|
| - self.TodoMVCStartupTester(self),
|
| - self.TodoMVCFileProcessor(self))
|
| -
|
| - @staticmethod
|
| - def Name():
|
| - return 'todoMvcStartup'
|
| -
|
| - class TodoMVCStartupTester(BrowserTester):
|
| - def RunTests(self):
|
| - """Run dromaeo in the browser."""
|
| - success, _, _ = self.test.test_runner.GetArchive('dartium')
|
| - if not success:
|
| - # Unable to download dartium. Try later.
|
| - return
|
| -
|
| - dromaeo_path = os.path.join('samples', 'third_party', 'dromaeo')
|
| - current_path = os.getcwd()
|
| -
|
| - os.chdir(os.path.join(DART_REPO_LOC, 'samples', 'third_party',
|
| - 'todomvc_performance'))
|
| - self.test.test_runner.RunCmd([os.path.join(DART_REPO_LOC,
|
| - utils.GetBuildRoot(utils.GuessOS(), 'release', 'ia32'),
|
| - 'dart-sdk', 'bin', 'pub'), 'build', '--mode=debug'])
|
| - os.chdir('js_todomvc');
|
| - self.test.test_runner.RunCmd([os.path.join(DART_REPO_LOC,
|
| - utils.GetBuildRoot(utils.GuessOS(), 'release', 'ia32'),
|
| - 'dart-sdk', 'bin', 'pub'), 'get'])
|
| -
|
| - versions = TodoMVCTester.GetVersions()
|
| -
|
| - for browser in BrowserTester.GetBrowsers():
|
| - for version_name in versions:
|
| - if not self.test.IsValidCombination(browser, version_name):
|
| - continue
|
| - self.test.trace_file = os.path.join(TOP_LEVEL_DIR,
|
| - 'tools', 'testing', 'perf_testing', self.test.result_folder_name,
|
| - 'todoMvcStartup-%s-%s-%s' % (self.test.cur_time, browser,
|
| - version_name))
|
| - self.AddSvnRevisionToTrace(self.test.trace_file, browser)
|
| -
|
| - if version_name == 'js':
|
| - code_root = os.path.join(DART_REPO_LOC, 'samples', 'third_party',
|
| - 'todomvc_performance', 'js_todomvc')
|
| - self.test.test_runner.RunBrowserPerfRunnerCmd(browser,
|
| - '/code_root/index.html', code_root, self.test.trace_file,
|
| - code_root)
|
| - else:
|
| - self.test.test_runner.RunBrowserPerfRunnerCmd(browser,
|
| - '/code_root/build/web/startup-performance.html', os.path.join(
|
| - DART_REPO_LOC, 'samples', 'third_party', 'todomvc_performance'),
|
| - self.test.trace_file)
|
| -
|
| - class TodoMVCFileProcessor(Processor):
|
| - def ProcessFile(self, afile, should_post_file):
|
| - """Comb through the html to find the performance results.
|
| - Returns: True if we successfully posted our data to storage."""
|
| - parts = afile.split('-')
|
| - browser = parts[2]
|
| - version = parts[3]
|
| -
|
| - bench_dict = self.test.values_dict[browser][version]
|
| -
|
| - f = self.OpenTraceFile(afile, should_post_file)
|
| - lines = f.readlines()
|
| - i = 0
|
| - revision_num = 0
|
| - revision_pattern = r'Revision: (\d+)'
|
| - result_pattern = r'The startup time is (\d+)'
|
| -
|
| - upload_success = True
|
| - for line in lines:
|
| - rev = re.match(revision_pattern, line.strip().replace('"', ''))
|
| - if rev:
|
| - revision_num = int(rev.group(1))
|
| - continue
|
| -
|
| - results = re.search(result_pattern, line)
|
| - if results:
|
| - score = float(results.group(1))
|
| - name = TodoMVCTester.GetBenchmarks()[0]
|
| - bench_dict[name] += [float(score)]
|
| - self.test.revision_dict[browser][version][name] += \
|
| - [revision_num]
|
| - if not self.test.test_runner.no_upload and should_post_file:
|
| - upload_success = upload_success and self.ReportResults(
|
| - name, score, browser, version, revision_num,
|
| - self.GetScoreType(name))
|
| -
|
| - f.close()
|
| - self.CalculateGeometricMean(browser, version, revision_num)
|
| - return upload_success
|
| -
|
| -
|
| -class TestBuilder(object):
|
| - """Construct the desired test object."""
|
| - available_suites = dict((suite.Name(), suite) for suite in [
|
| - DromaeoTest, TodoMVCStartupTest])
|
| -
|
| - @staticmethod
|
| - def MakeTest(test_name, test_runner):
|
| - return TestBuilder.available_suites[test_name](test_runner)
|
| -
|
| - @staticmethod
|
| - def AvailableSuiteNames():
|
| - return TestBuilder.available_suites.keys()
|
| -
|
| -
|
| -def SearchForRevision(directory = None):
|
| - """Find the current revision number in the desired directory. If directory is
|
| - None, find the revision number in the current directory."""
|
| - def FindRevision(svn_info_command):
|
| - p = subprocess.Popen(svn_info_command, stdout = subprocess.PIPE,
|
| - stderr = subprocess.STDOUT,
|
| - shell = (platform.system() == 'Windows'))
|
| - output, _ = p.communicate()
|
| - for line in output.split('\n'):
|
| - if 'Revision' in line:
|
| - return int(line.split()[1])
|
| - return -1
|
| -
|
| - cwd = os.getcwd()
|
| - if not directory:
|
| - directory = cwd
|
| - os.chdir(directory)
|
| - revision_num = int(FindRevision(['svn', 'info']))
|
| - if revision_num == -1:
|
| - revision_num = int(FindRevision(['git', 'svn', 'info']))
|
| - os.chdir(cwd)
|
| - return str(revision_num)
|
| -
|
| -
|
| -def UpdateSetOfDoneCls(revision_num=None):
|
| - """Update the set of CLs that do not need additional performance runs.
|
| - Args:
|
| - revision_num: an additional number to be added to the 'done set'
|
| - """
|
| - filename = os.path.join(TOP_LEVEL_DIR, 'cached_results.txt')
|
| - if not os.path.exists(filename):
|
| - f = open(filename, 'w')
|
| - results = set()
|
| - pickle.dump(results, f)
|
| - f.close()
|
| - f = open(filename, 'r+')
|
| - result_set = pickle.load(f)
|
| - if revision_num:
|
| - f.seek(0)
|
| - result_set.add(revision_num)
|
| - pickle.dump(result_set, f)
|
| - f.close()
|
| - return result_set
|
| -
|
| -
|
| -def FillInBackHistory(results_set, runner):
|
| - """Fill in back history performance data. This is done one of two ways, with
|
| - equal probability of trying each way (falling back on the sequential version
|
| - as our data becomes more densely populated)."""
|
| - revision_num = int(SearchForRevision(DART_REPO_LOC))
|
| - has_run_extra = False
|
| -
|
| - def TryToRunAdditional(revision_number):
|
| - """Determine the number of results we have stored for a particular revision
|
| - number, and if it is less than 10, run some extra tests.
|
| - Args:
|
| - - revision_number: the revision whose performance we want to potentially
|
| - test.
|
| - Returns: True if we successfully ran some additional tests."""
|
| - if not runner.HasInterestingCode(revision_number)[0]:
|
| - results_set = UpdateSetOfDoneCls(revision_number)
|
| - return False
|
| - a_test = TestBuilder.MakeTest(runner.suite_names[0], runner)
|
| - benchmark_name = a_test.values_list[0]
|
| - platform_name = a_test.platform_list[0]
|
| - variant = a_test.values_dict[platform_name].keys()[0]
|
| - num_results = post_results.get_num_results(benchmark_name,
|
| - platform_name, variant, revision_number,
|
| - a_test.file_processor.GetScoreType(benchmark_name))
|
| - if num_results < 10:
|
| - # Run at most two more times.
|
| - if num_results > 8:
|
| - reruns = 10 - num_results
|
| - else:
|
| - reruns = 2
|
| - run = runner.RunTestSequence(revision_num=str(revision_number),
|
| - num_reruns=reruns)
|
| - if num_results >= 10 or run == 0 and num_results + reruns >= 10:
|
| - results_set = UpdateSetOfDoneCls(revision_number)
|
| - elif run != 0:
|
| - return False
|
| - return True
|
| -
|
| - # Try to get up to 10 runs of each CL, starting with the most recent
|
| - # CL that does not yet have 10 runs. But only perform a set of extra
|
| - # runs at most 2 at a time before checking to see if new code has been
|
| - # checked in.
|
| - while revision_num > EARLIEST_REVISION and not has_run_extra:
|
| - if revision_num not in results_set:
|
| - has_run_extra = TryToRunAdditional(revision_num)
|
| - revision_num -= 1
|
| - if not has_run_extra:
|
| - # No more extra back-runs to do (for now). Wait for new code.
|
| - time.sleep(200)
|
| - return results_set
|
| -
|
| -
|
| -def main():
|
| - runner = TestRunner()
|
| - continuous = runner.ParseArgs()
|
| -
|
| - if not os.path.exists(DART_REPO_LOC):
|
| - os.mkdir(dirname(DART_REPO_LOC))
|
| - os.chdir(dirname(DART_REPO_LOC))
|
| - p = subprocess.Popen('gclient config https://dart.googlecode.com/svn/' +
|
| - 'branches/bleeding_edge/deps/all.deps',
|
| - stdout=subprocess.PIPE, stderr=subprocess.PIPE,
|
| - shell=True)
|
| - p.communicate()
|
| - if continuous:
|
| - while True:
|
| - results_set = UpdateSetOfDoneCls()
|
| - (is_interesting, interesting_rev_num) = runner.HasInterestingCode()
|
| - if is_interesting:
|
| - runner.RunTestSequence(interesting_rev_num)
|
| - else:
|
| - if runner.backfill:
|
| - results_set = FillInBackHistory(results_set, runner)
|
| - else:
|
| - time.sleep(200)
|
| - else:
|
| - runner.RunTestSequence()
|
| -
|
| -if __name__ == '__main__':
|
| - main()
|
|
|