253597427c
Review URL: https://chromiumcodereview.appspot.com//10095016 git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart@6696 260f80e4-7a28-3924-810f-c04153c831b5
1275 lines
49 KiB
Python
Executable File
1275 lines
49 KiB
Python
Executable File
#!/usr/bin/python
|
|
|
|
# Copyright (c) 2011, 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 getpass
|
|
import math
|
|
try:
|
|
from matplotlib.font_manager import FontProperties
|
|
import matplotlib.pyplot as plt
|
|
except ImportError:
|
|
pass # Only needed if we want to make graphs.
|
|
import optparse
|
|
import os
|
|
from os.path import dirname, abspath
|
|
import platform
|
|
import re
|
|
import shutil
|
|
import stat
|
|
import subprocess
|
|
import sys
|
|
import time
|
|
import traceback
|
|
|
|
TOOLS_PATH = os.path.join(dirname(dirname(dirname(abspath(__file__)))))
|
|
DART_INSTALL_LOCATION = abspath(os.path.join(dirname(abspath(__file__)),
|
|
'..', '..', '..'))
|
|
sys.path.append(TOOLS_PATH)
|
|
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
|
|
|
|
def run_cmd(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
|
|
|
|
def time_cmd(self, cmd):
|
|
"""Determine the amount of (real) time it takes to execute a given
|
|
command."""
|
|
start = time.time()
|
|
self.run_cmd(cmd)
|
|
return time.time() - start
|
|
|
|
@staticmethod
|
|
def get_build_targets(suites):
|
|
"""Loop through a set of tests that we want to run and find the build
|
|
targets that are necessary.
|
|
|
|
Args:
|
|
suites: The test suites that we wish to run."""
|
|
build_targets = set()
|
|
for test in suites:
|
|
if test.build_targets is not None:
|
|
for target in test.build_targets:
|
|
build_targets.add(target)
|
|
return build_targets
|
|
|
|
def sync_and_build(self, suites):
|
|
"""Make sure we have the latest version of of the repo, and build it. We
|
|
begin and end standing in DART_INSTALL_LOCATION.
|
|
|
|
Args:
|
|
suites: The set of suites that we wish to build.
|
|
|
|
Returns:
|
|
err_code = 1 if there was a problem building."""
|
|
os.chdir(DART_INSTALL_LOCATION)
|
|
|
|
self.run_cmd(['gclient', 'sync'])
|
|
|
|
# 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.
|
|
def on_rm_error(func, path, exc_info):
|
|
if os.path.exists(path):
|
|
os.chmod(path, stat.S_IWRITE)
|
|
os.unlink(path)
|
|
# TODO(efortuna): building the sdk locally is a band-aid until all build
|
|
# platform SDKs are hosted in Google storage. Pull from https://sandbox.
|
|
# google.com/storage/?arg=dart-dump-render-tree#dart-dump-render-tree%2Fsdk
|
|
# eventually.
|
|
# 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(), 'release', 'ia32')),
|
|
onerror=on_rm_error)
|
|
|
|
for target in TestRunner.get_build_targets(suites):
|
|
lines = self.run_cmd([os.path.join('.', 'tools', 'build.py'), '-m',
|
|
'release', '--arch=ia32', target])
|
|
|
|
for line in lines:
|
|
if 'BUILD FAILED' in lines:
|
|
# 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 ensure_output_directory(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(DART_INSTALL_LOCATION, 'tools',
|
|
'testing', 'perf_testing', dir_name)
|
|
if not os.path.exists(dir_path):
|
|
os.mkdir(dir_path)
|
|
print 'Creating output directory ', dir_path
|
|
|
|
def has_new_code(self):
|
|
"""Tests if there are any newer versions of files on the server."""
|
|
os.chdir(DART_INSTALL_LOCATION)
|
|
# Pass 'p' in if we have a new certificate for the svn server, we want to
|
|
# (p)ermanently accept it.
|
|
results = self.run_cmd(['svn', 'st', '-u'], std_in='p')
|
|
for line in results:
|
|
if '*' in line:
|
|
return True
|
|
return False
|
|
|
|
def get_os_directory(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 upload_to_app_engine(self, suite_names):
|
|
"""Upload our results to our appengine server.
|
|
Arguments:
|
|
suite_names: Directories to upload data from (should match directory
|
|
names)."""
|
|
# TODO(efortuna): This is the most basic way to get the data up
|
|
# for others to view. Revisit this once we're serving nicer graphs (Google
|
|
# Chart Tools) and from multiple perfbots and once we're in a position to
|
|
# organize the data in a useful manner(!!).
|
|
os.chdir(os.path.join(DART_INSTALL_LOCATION, 'tools', 'testing',
|
|
'perf_testing'))
|
|
for data in suite_names:
|
|
path = os.path.join('appengine', 'static', 'data', data, utils.GuessOS())
|
|
shutil.rmtree(path, ignore_errors=True)
|
|
os.makedirs(path)
|
|
files = []
|
|
# Copy the 1000 most recent trace files to be uploaded.
|
|
for f in os.listdir(data):
|
|
files += [(os.path.getmtime(os.path.join(data, f)), f)]
|
|
files.sort()
|
|
for f in files[-1000:]:
|
|
shutil.copyfile(os.path.join(data, f[1]),
|
|
os.path.join(path, f[1]+'.txt'))
|
|
# Generate directory listing.
|
|
for data in suite_names:
|
|
path = os.path.join('appengine', 'static', 'data', data, utils.GuessOS())
|
|
out = open(os.path.join('appengine', 'static',
|
|
'%s-%s.html' % (data, utils.GuessOS())), 'w')
|
|
out.write('<html>\n <body>\n <ul>\n')
|
|
for f in os.listdir(path):
|
|
if not f.startswith('.'):
|
|
out.write(' <li><a href=data' + \
|
|
'''/%(data)s/%(os)s/%(file)s>%(file)s</a></li>\n''' % \
|
|
{'data': data, 'os': utils.GuessOS(), 'file': f})
|
|
out.write(' </ul>\n </body>\n</html>')
|
|
out.close()
|
|
|
|
shutil.rmtree(os.path.join('appengine', 'static', 'graphs'),
|
|
ignore_errors=True)
|
|
shutil.copytree('graphs', os.path.join('appengine', 'static', 'graphs'))
|
|
shutil.copyfile('index.html', os.path.join('appengine', 'static',
|
|
'index.html'))
|
|
shutil.copyfile('dromaeo.html', os.path.join('appengine', 'static',
|
|
'dromaeo.html'))
|
|
shutil.copyfile('data.html', os.path.join('appengine', 'static',
|
|
'data.html'))
|
|
self.run_cmd([os.path.join('..', '..', '..', 'third_party',
|
|
'appengine-python', 'appcfg.py'), '--oauth2',
|
|
'update', 'appengine/'])
|
|
|
|
def parse_args(self):
|
|
parser = optparse.OptionParser()
|
|
parser.add_option('--suites', '-s', dest='suites', help='Run the specified '
|
|
'comma-separated test suites from set: %s' % \
|
|
','.join(TestBuilder.available_suite_names()),
|
|
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('--graph-only', '-g', dest='graph_only', default=False,
|
|
help='Do not run tests, only regenerate graphs',
|
|
action='store_true')
|
|
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('--upload', '-u', dest='upload', help='Upload data to '
|
|
'app engine (will require authentication).',
|
|
action='store_true', default=False)
|
|
parser.add_option('--verbose', '-v', dest='verbose', help='Print extra '
|
|
'debug output', action='store_true', default=False)
|
|
|
|
args, ignored = parser.parse_args()
|
|
|
|
if not args.suites:
|
|
suites = TestBuilder.available_suite_names()
|
|
else:
|
|
suites = []
|
|
suitelist = args.suites.split(',')
|
|
for name in suitelist:
|
|
if name in TestBuilder.available_suite_names():
|
|
suites.append(name)
|
|
else:
|
|
print ('Error: Invalid suite %s not in ' % name) + \
|
|
'%s' % ','.join(TestBuilder.available_suite_names())
|
|
sys.exit(1)
|
|
self.suite_names = suites
|
|
self.no_build = args.no_build
|
|
self.graph_only = args.graph_only
|
|
self.upload = args.upload
|
|
self.verbose = args.verbose
|
|
return args.continuous
|
|
|
|
def run_test_sequence(self):
|
|
"""Run the set of commands to (possibly) build, run, and graph the results
|
|
of our tests.
|
|
|
|
Args:
|
|
suite_names: The "display name" the user enters to specify which
|
|
benchmark(s) to run.
|
|
no_build: True if we should not check the repository and build the latest
|
|
version.
|
|
graph_only: True if we should not run the tests, just (re)generate graphs.
|
|
upload: True if we should upload our results to appengine."""
|
|
suites = []
|
|
for name in self.suite_names:
|
|
suites += [TestBuilder.make_test(name, self)]
|
|
|
|
if not self.no_build and self.sync_and_build(suites) == 1:
|
|
return # The build is broken.
|
|
|
|
for test in suites:
|
|
test.run(self.graph_only)
|
|
|
|
if self.upload:
|
|
self.upload_to_app_engine(TestBuilder.available_suite_names())
|
|
|
|
|
|
class Test(object):
|
|
"""The base class to provide shared code for different tests we will run and
|
|
graph. At a high level, each test has three visitors (the tester, the
|
|
file_processor, and the grapher) that perform operations on the test
|
|
object."""
|
|
|
|
def __init__(self, result_folder_name, platform_list, variants,
|
|
values_list, test_runner, tester, file_processor, grapher,
|
|
extra_metrics=['Geo-Mean'], build_targets=['create_sdk']):
|
|
"""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.
|
|
grapher: The visitor that generates graphs given our test result data.
|
|
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).
|
|
build_targets: The targets necessary to build to run these tests
|
|
(default target is create_sdk)."""
|
|
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.revision_dict = dict()
|
|
self.values_dict = dict()
|
|
self.test_runner = test_runner
|
|
self.tester = tester
|
|
self.file_processor = file_processor
|
|
self.grapher = grapher
|
|
self.extra_metrics = extra_metrics
|
|
self.build_targets = build_targets
|
|
# 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 is_valid_combination(self, platform, variant):
|
|
"""Check whether data should be captured for this platform/variant
|
|
combination.
|
|
"""
|
|
return True
|
|
|
|
def run(self, graph_only):
|
|
"""Run the benchmarks/tests from the command line and plot the
|
|
results.
|
|
|
|
Args:
|
|
graph_only: True if we should just graph the results instead of also
|
|
running tests."""
|
|
for visitor in [self.tester, self.file_processor, self.grapher]:
|
|
visitor.prepare()
|
|
|
|
os.chdir(DART_INSTALL_LOCATION)
|
|
self.test_runner.ensure_output_directory(self.result_folder_name)
|
|
if not graph_only:
|
|
self.tester.run_tests()
|
|
|
|
os.chdir(os.path.join('tools', 'testing', 'perf_testing'))
|
|
|
|
# TODO(efortuna): You will want to make this only use a subset of the files
|
|
# eventually.
|
|
files = os.listdir(self.result_folder_name)
|
|
|
|
for afile in files:
|
|
if not afile.startswith('.'):
|
|
self.file_processor.process_file(afile)
|
|
|
|
if 'plt' in globals():
|
|
# Only run Matplotlib if it is installed.
|
|
self.grapher.plot_results('%s.png' % self.result_folder_name)
|
|
|
|
|
|
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 run_tests() method."""
|
|
|
|
def __init__(self, test):
|
|
self.test = test
|
|
|
|
def prepare(self):
|
|
"""Perform any initial setup required before the test is run."""
|
|
pass
|
|
|
|
def add_svn_revision_to_trace(self, outfile, browser = None):
|
|
"""Add the svn version number to the provided tracefile."""
|
|
def search_for_revision(svn_info_command):
|
|
p = subprocess.Popen(svn_info_command, stdout = subprocess.PIPE,
|
|
stderr = subprocess.STDOUT, shell =
|
|
self.test.test_runner.has_shell)
|
|
output, _ = p.communicate()
|
|
for line in output.split('\n'):
|
|
if 'Revision' in line:
|
|
self.test.test_runner.run_cmd(['echo', line.strip()], outfile)
|
|
return True
|
|
return False
|
|
|
|
def get_dartium_revision():
|
|
version_file_name = os.path.join(DART_INSTALL_LOCATION, 'client', 'tests',
|
|
'dartium', 'LAST_VERSION')
|
|
version_file = open(version_file_name, 'r')
|
|
version = version_file.read().split('.')[-2]
|
|
version_file.close()
|
|
return version
|
|
|
|
if browser and browser == 'dartium':
|
|
revision = get_dartium_revision()
|
|
self.test.test_runner.run_cmd(['echo', 'Revision: ' + revision], outfile)
|
|
elif not search_for_revision(['svn', 'info']):
|
|
if not search_for_revision(['git', 'svn', 'info']):
|
|
self.test.test_runner.run_cmd(['echo', 'Revision: unknown'], 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 process_file() method."""
|
|
|
|
def __init__(self, test):
|
|
self.test = test
|
|
|
|
def prepare(self):
|
|
"""Perform any initial setup required before the test is run."""
|
|
pass
|
|
|
|
def calculate_geometric_mean(self, platform, variant, svn_revision):
|
|
"""Calculate the aggregate geometric mean for JS and frog benchmark sets,
|
|
given two benchmark dictionaries."""
|
|
geo_mean = 0
|
|
# TODO(vsm): Suppress graphing this combination altogether. For
|
|
# now, we feed a geomean of 0.
|
|
if self.test.is_valid_combination(platform, variant):
|
|
for benchmark in self.test.values_list:
|
|
geo_mean += math.log(
|
|
self.test.values_dict[platform][variant][benchmark][
|
|
len(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]
|
|
|
|
|
|
class Grapher(object):
|
|
"""The base level visitor class that generates graphs for data. It contains
|
|
convenience methods that many Grapher objects use. Any class that would like
|
|
to be a GrapherVisitor must implement the plot_results() method."""
|
|
|
|
graph_out_dir = 'graphs'
|
|
|
|
def __init__(self, test):
|
|
self.color_index = 0
|
|
self.test = test
|
|
|
|
def prepare(self):
|
|
"""Perform any initial setup required before the test is run."""
|
|
if 'plt' in globals():
|
|
plt.cla() # cla = clear current axes
|
|
else:
|
|
print 'Unable to import Matplotlib and therefore unable to generate ' + \
|
|
'graphs. Please install it for this version of Python.'
|
|
self.test.test_runner.ensure_output_directory(Grapher.graph_out_dir)
|
|
|
|
def style_and_save_perf_plot(self, chart_title, y_axis_label, size_x, size_y,
|
|
legend_loc, filename, platform_list, variants,
|
|
values_list, should_clear_axes=True):
|
|
"""Sets style preferences for chart boilerplate that is consistent across
|
|
all charts, and saves the chart as a png.
|
|
|
|
Args:
|
|
size_x: the size of the printed chart, in inches, in the horizontal
|
|
direction
|
|
size_y: the size of the printed chart, in inches in the vertical direction
|
|
legend_loc: the location of the legend in on the chart. See suitable
|
|
arguments for the loc argument in matplotlib
|
|
filename: the filename that we want to save the resulting chart as
|
|
platform_list: a list containing the platform(s) that our data has been
|
|
run on. (command line, firefox, chrome, etc)
|
|
values_list: a list containing the type of data we will be graphing
|
|
(performance, percentage passing, etc)
|
|
should_clear_axes: True if we want to create a fresh graph, instead of
|
|
plotting additional lines on the current graph."""
|
|
if should_clear_axes:
|
|
plt.cla() # cla = clear current axes
|
|
for platform in platform_list:
|
|
for f in variants:
|
|
for val in values_list:
|
|
plt.plot(self.test.revision_dict[platform][f][val],
|
|
self.test.values_dict[platform][f][val],
|
|
color=self.get_color(), label='%s-%s-%s' % (platform, f, val))
|
|
|
|
plt.xlabel('Revision Number')
|
|
plt.ylabel(y_axis_label)
|
|
plt.title(chart_title)
|
|
fontP = FontProperties()
|
|
fontP.set_size('small')
|
|
plt.legend(loc=legend_loc, prop = fontP)
|
|
|
|
fig = plt.gcf()
|
|
fig.set_size_inches(size_x, size_y)
|
|
fig.savefig(os.path.join(Grapher.graph_out_dir, filename))
|
|
|
|
def get_color(self):
|
|
# Just a bunch of distinct colors for a potentially large number of values
|
|
# we wish to graph.
|
|
colors = [
|
|
'blue', 'green', 'red', 'cyan', 'magenta', 'black', '#3366CC',
|
|
'#DC3912', '#FF9900', '#109618', '#990099', '#0099C6', '#DD4477',
|
|
'#66AA00', '#B82E2E', '#316395', '#994499', '#22AA99', '#AAAA11',
|
|
'#6633CC', '#E67300', '#8B0707', '#651067', '#329262', '#5574A6',
|
|
'#3B3EAC', '#B77322', '#16D620', '#B91383', '#F4359E', '#9C5935',
|
|
'#A9C413', '#2A778D', '#668D1C', '#BEA413', '#0C5922', '#743411',
|
|
'#45AFE2', '#FF3300', '#FFCC00', '#14C21D', '#DF51FD', '#15CBFF',
|
|
'#FF97D2', '#97FB00', '#DB6651', '#518BC6', '#BD6CBD', '#35D7C2',
|
|
'#E9E91F', '#9877DD', '#FF8F20', '#D20B0B', '#B61DBA', '#40BD7E',
|
|
'#6AA7C4', '#6D70CD', '#DA9136', '#2DEA36', '#E81EA6', '#F558AE',
|
|
'#C07145', '#D7EE53', '#3EA7C6', '#97D129', '#E9CA1D', '#149638',
|
|
'#C5571D']
|
|
color = colors[self.color_index]
|
|
self.color_index = (self.color_index + 1) % len(colors)
|
|
return color
|
|
|
|
|
|
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,
|
|
build_targets=['create_sdk']):
|
|
"""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.
|
|
grapher: The visitor that generates graphs given our test result data.
|
|
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).
|
|
build_targets: The targets necessary to build to run these tests
|
|
(default target is create_sdk)."""
|
|
super(RuntimePerformanceTest, self).__init__(result_folder_name,
|
|
platform_list, versions, benchmarks, test_runner, tester,
|
|
file_processor, self.RuntimePerfGrapher(self),
|
|
build_targets=build_targets)
|
|
self.platform_list = platform_list
|
|
self.platform_type = platform_type
|
|
self.versions = versions
|
|
self.benchmarks = benchmarks
|
|
|
|
class RuntimePerfGrapher(Grapher):
|
|
def plot_all_perf(self, png_filename):
|
|
"""Create a plot that shows the performance changes of individual
|
|
benchmarks run by JS and generated by frog, over svn history."""
|
|
for benchmark in self.test.benchmarks:
|
|
self.style_and_save_perf_plot(
|
|
'Performance of %s over time on the %s on %s' % (benchmark,
|
|
self.test.platform_type, utils.GuessOS()),
|
|
'Speed (bigger = better)', 16, 14, 'lower left',
|
|
benchmark + png_filename, self.test.platform_list,
|
|
self.test.versions, [benchmark])
|
|
|
|
def plot_avg_perf(self, png_filename):
|
|
"""Generate a plot that shows the performance changes of the geomentric
|
|
mean of JS and frog benchmark performance over svn history."""
|
|
(title, y_axis, size_x, size_y, loc, filename) = \
|
|
('Geometric Mean of benchmark %s performance on %s ' %
|
|
(self.test.platform_type, utils.GuessOS()), 'Speed (bigger = better)',
|
|
16, 5, 'lower left', 'avg'+png_filename)
|
|
clear_axis = True
|
|
for platform in self.test.platform_list:
|
|
for version in self.test.versions:
|
|
if self.test.is_valid_combination(platform, version):
|
|
for metric in self.test.extra_metrics:
|
|
self.style_and_save_perf_plot(title, y_axis, size_x, size_y, loc,
|
|
filename, [platform], [version],
|
|
[metric], clear_axis)
|
|
clear_axis = False
|
|
|
|
def plot_results(self, png_filename):
|
|
self.plot_all_perf(png_filename)
|
|
self.plot_avg_perf('2' + png_filename)
|
|
|
|
|
|
class CommonCommandLineTest(RuntimePerformanceTest):
|
|
"""Run the basic performance tests (Benchpress, some V8 benchmarks) from the
|
|
command line."""
|
|
|
|
def __init__(self, test_runner):
|
|
"""Args:
|
|
test_runner: Reference to the object that notfies this test when to
|
|
run."""
|
|
super(CommonCommandLineTest, self).__init__(
|
|
self.name(), ['commandline'],
|
|
'command line', ['js', 'frog'], self.get_standalone_benchmarks(),
|
|
test_runner, self.CommonCommandLineTester(self),
|
|
self.CommonCommandLineFileProcessor(self),
|
|
build_targets=['create_sdk', 'dart2js'])
|
|
|
|
@staticmethod
|
|
def name():
|
|
return 'cl-perf'
|
|
|
|
@staticmethod
|
|
def get_standalone_benchmarks():
|
|
return ['Mandelbrot', 'DeltaBlue', 'Richards', 'NBody', 'BinaryTrees',
|
|
'Fannkuch', 'Meteor', 'BubbleSort', 'Fibonacci', 'Loop', 'Permute',
|
|
'Queens', 'QuickSort', 'Recurse', 'Sieve', 'Sum', 'Tak', 'Takl', 'Towers',
|
|
'TreeSort']
|
|
|
|
class CommonCommandLineTester(Tester):
|
|
def run_tests(self):
|
|
"""Run a performance test on our updated system."""
|
|
os.chdir('frog')
|
|
self.test.trace_file = os.path.join(
|
|
'..', 'tools', 'testing', 'perf_testing',
|
|
self.test.result_folder_name, 'result' + self.test.cur_time)
|
|
self.test.test_runner.run_cmd(['python', os.path.join('benchmarks',
|
|
'perf_tests.py')], self.test.trace_file)
|
|
os.chdir('..')
|
|
|
|
class CommonCommandLineFileProcessor(Processor):
|
|
def process_file(self, afile):
|
|
"""Pull all the relevant information out of a given tracefile.
|
|
|
|
Args:
|
|
afile: The filename string we will be processing."""
|
|
os.chdir(os.path.join(DART_INSTALL_LOCATION, 'tools',
|
|
'testing', 'perf_testing'))
|
|
f = open(os.path.join(self.test.result_folder_name, afile))
|
|
tabulate_data = False
|
|
revision_num = 0
|
|
for line in f.readlines():
|
|
if 'Revision' in line:
|
|
revision_num = int(line.split()[1])
|
|
elif 'Benchmark' in line:
|
|
tabulate_data = True
|
|
elif tabulate_data:
|
|
tokens = line.split()
|
|
if len(tokens) < 4 or tokens[0] not in self.test.benchmarks:
|
|
#Done tabulating data.
|
|
break
|
|
js_value = float(tokens[1])
|
|
frog_value = float(tokens[3])
|
|
if js_value == 0 or frog_value == 0:
|
|
#Then there was an error when this performance test was run. Do not
|
|
#count it in our numbers.
|
|
return
|
|
benchmark = tokens[0]
|
|
self.test.revision_dict['commandline']['js'][benchmark] += \
|
|
[revision_num]
|
|
self.test.values_dict['commandline']['js'][benchmark] += [js_value]
|
|
self.test.revision_dict['commandline']['frog'][benchmark] += \
|
|
[revision_num]
|
|
self.test.values_dict['commandline']['frog'][benchmark] += \
|
|
[frog_value]
|
|
f.close()
|
|
|
|
self.calculate_geometric_mean('commandline', 'frog', revision_num)
|
|
self.calculate_geometric_mean('commandline', 'js', revision_num)
|
|
|
|
|
|
class BrowserTester(Tester):
|
|
@staticmethod
|
|
def get_browsers():
|
|
browsers = ['dartium', 'ff', 'chrome']
|
|
has_shell = False
|
|
if platform.system() == 'Darwin':
|
|
browsers += ['safari']
|
|
if platform.system() == 'Windows':
|
|
browsers += ['ie']
|
|
has_shell = True
|
|
if 'dartium' in browsers:
|
|
# Fetch it if necessary.
|
|
get_dartium = ['python',
|
|
os.path.join(DART_INSTALL_LOCATION, 'tools', 'get_drt.py'),
|
|
'--dartium']
|
|
# TODO(vsm): It's inconvenient that run_cmd isn't in scope here.
|
|
# Perhaps there is a better place to put that or this.
|
|
subprocess.Popen(get_dartium, shell=has_shell)
|
|
return browsers
|
|
|
|
|
|
class CommonBrowserTest(RuntimePerformanceTest):
|
|
"""Runs this basic performance tests (Benchpress, some V8 benchmarks) in the
|
|
browser."""
|
|
|
|
def __init__(self, test_runner):
|
|
"""Args:
|
|
test_runner: Reference to the object that notifies us when to run."""
|
|
super(CommonBrowserTest, self).__init__(
|
|
self.name(), BrowserTester.get_browsers(),
|
|
'browser', ['js', 'frog'],
|
|
self.get_standalone_benchmarks(), test_runner,
|
|
self.CommonBrowserTester(self),
|
|
self.CommonBrowserFileProcessor(self))
|
|
|
|
@staticmethod
|
|
def name():
|
|
return 'browser-perf'
|
|
|
|
@staticmethod
|
|
def get_standalone_benchmarks():
|
|
return ['Mandelbrot', 'DeltaBlue', 'Richards', 'NBody', 'BinaryTrees',
|
|
'Fannkuch', 'Meteor', 'BubbleSort', 'Fibonacci', 'Loop', 'Permute',
|
|
'Queens', 'QuickSort', 'Recurse', 'Sieve', 'Sum', 'Tak', 'Takl', 'Towers',
|
|
'TreeSort']
|
|
|
|
class CommonBrowserTester(BrowserTester):
|
|
def run_tests(self):
|
|
"""Run a performance test in the browser."""
|
|
os.chdir('frog')
|
|
self.test.test_runner.run_cmd(['python', os.path.join('benchmarks',
|
|
'make_web_benchmarks.py')])
|
|
os.chdir('..')
|
|
|
|
for browser in BrowserTester.get_browsers():
|
|
for version in self.test.versions:
|
|
if not self.test.is_valid_combination(browser, version):
|
|
continue
|
|
self.test.trace_file = os.path.join(
|
|
'tools', 'testing', 'perf_testing', self.test.result_folder_name,
|
|
'perf-%s-%s-%s' % (self.test.cur_time, browser, version))
|
|
self.add_svn_revision_to_trace(self.test.trace_file, browser)
|
|
file_path = os.path.join(
|
|
os.getcwd(), 'internal', 'browserBenchmarks',
|
|
'benchmark_page_%s.html' % version)
|
|
self.test.test_runner.run_cmd(
|
|
['python', os.path.join('tools', 'testing', 'run_selenium.py'),
|
|
'--out', file_path, '--browser', browser,
|
|
'--timeout', '600', '--mode', 'perf'], self.test.trace_file,
|
|
append=True)
|
|
|
|
class CommonBrowserFileProcessor(Processor):
|
|
def process_file(self, afile):
|
|
"""Comb through the html to find the performance results."""
|
|
os.chdir(os.path.join(DART_INSTALL_LOCATION, 'tools',
|
|
'testing', 'perf_testing'))
|
|
parts = afile.split('-')
|
|
browser = parts[2]
|
|
version = parts[3]
|
|
f = open(os.path.join(self.test.result_folder_name, afile))
|
|
lines = f.readlines()
|
|
line = ''
|
|
i = 0
|
|
revision_num = 0
|
|
while '<div id="results">' not in line and i < len(lines):
|
|
if 'Revision' in line:
|
|
revision_num = int(line.split()[1].strip('"'))
|
|
line = lines[i]
|
|
i += 1
|
|
|
|
if i >= len(lines) or revision_num == 0:
|
|
# Then this run did not complete. Ignore this tracefile.
|
|
return
|
|
|
|
line = lines[i]
|
|
i += 1
|
|
results = []
|
|
if line.find('<br>') > -1:
|
|
results = line.split('<br>')
|
|
else:
|
|
results = line.split('<br />')
|
|
for result in results:
|
|
name_and_score = result.split(':')
|
|
if len(name_and_score) < 2:
|
|
break
|
|
name = name_and_score[0].strip()
|
|
score = name_and_score[1].strip()
|
|
if version == 'js' or version == 'v8':
|
|
version = 'js'
|
|
bench_dict = self.test.values_dict[browser]['js']
|
|
else:
|
|
bench_dict = self.test.values_dict[browser]['frog']
|
|
bench_dict[name] += [float(score)]
|
|
self.test.revision_dict[browser][version][name] += [revision_num]
|
|
|
|
f.close()
|
|
self.calculate_geometric_mean(browser, version, revision_num)
|
|
|
|
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 legal appengine filenames for benchmark names.
|
|
@staticmethod
|
|
def legalize_filename(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 on
|
|
# the mac.
|
|
@staticmethod
|
|
def get_valid_dromaeo_tags():
|
|
tags = [tag for (tag, _) in DromaeoTester.DROMAEO_BENCHMARKS.values()]
|
|
if platform.system() == 'Darwin':
|
|
tags.remove('modify')
|
|
return tags
|
|
|
|
@staticmethod
|
|
def get_dromaeo_benchmarks():
|
|
valid = DromaeoTester.get_valid_dromaeo_tags()
|
|
benchmarks = reduce(lambda l1,l2: l1+l2,
|
|
[tests for (tag, tests) in
|
|
DromaeoTester.DROMAEO_BENCHMARKS.values()
|
|
if tag in valid])
|
|
return map(DromaeoTester.legalize_filename, benchmarks)
|
|
|
|
@staticmethod
|
|
def get_dromaeo_versions():
|
|
return ['js', 'frog_dom', 'frog_html']
|
|
|
|
|
|
class DromaeoTest(RuntimePerformanceTest):
|
|
"""Runs Dromaeo tests, in the browser."""
|
|
def __init__(self, test_runner):
|
|
super(DromaeoTest, self).__init__(
|
|
self.name(), BrowserTester.get_browsers(), 'browser',
|
|
DromaeoTester.get_dromaeo_versions(),
|
|
DromaeoTester.get_dromaeo_benchmarks(), test_runner,
|
|
self.DromaeoPerfTester(self),
|
|
self.DromaeoFileProcessor(self))
|
|
|
|
@staticmethod
|
|
def name():
|
|
return 'dromaeo'
|
|
|
|
def is_valid_combination(self, browser, version):
|
|
# TODO(vsm): This avoids a bug in 32-bit Chrome (dartium)
|
|
# running JS dromaeo.
|
|
if browser == 'dartium' and version == 'js':
|
|
return False
|
|
return True
|
|
|
|
class DromaeoPerfTester(DromaeoTester):
|
|
def run_tests(self):
|
|
"""Run dromaeo in the browser."""
|
|
|
|
# Build tests.
|
|
dromaeo_path = os.path.join('samples', 'third_party', 'dromaeo')
|
|
current_path = os.getcwd()
|
|
os.chdir(dromaeo_path)
|
|
self.test.test_runner.run_cmd(['python', 'generate_frog_tests.py'])
|
|
os.chdir(current_path)
|
|
|
|
versions = DromaeoTester.get_dromaeo_versions()
|
|
|
|
for browser in BrowserTester.get_browsers():
|
|
for version_name in versions:
|
|
if not self.test.is_valid_combination(browser, version):
|
|
continue
|
|
version = DromaeoTest.DromaeoPerfTester.get_dromaeo_url_query(
|
|
browser, version_name)
|
|
self.test.trace_file = os.path.join(
|
|
'tools', 'testing', 'perf_testing', self.test.result_folder_name,
|
|
'dromaeo-%s-%s-%s' % (self.test.cur_time, browser, version_name))
|
|
self.add_svn_revision_to_trace(self.test.trace_file, browser)
|
|
file_path = '"%s"' % os.path.join(os.getcwd(), dromaeo_path,
|
|
'index-js.html?%s' % version)
|
|
self.test.test_runner.run_cmd(
|
|
['python', os.path.join('tools', 'testing', 'run_selenium.py'),
|
|
'--out', file_path, '--browser', browser,
|
|
'--timeout', '600', '--mode', 'dromaeo'], self.test.trace_file,
|
|
append=True)
|
|
|
|
@staticmethod
|
|
def get_dromaeo_url_query(browser, version):
|
|
if browser == 'dartium':
|
|
version = version.replace('frog', 'dart')
|
|
version = version.replace('_','&')
|
|
tags = DromaeoTester.get_valid_dromaeo_tags()
|
|
return '|'.join([ '%s&%s' % (version, tag) for tag in tags])
|
|
|
|
|
|
class DromaeoFileProcessor(Processor):
|
|
def process_file(self, afile):
|
|
"""Comb through the html to find the performance results."""
|
|
parts = afile.split('-')
|
|
browser = parts[2]
|
|
version = parts[3]
|
|
|
|
bench_dict = self.test.values_dict[browser][version]
|
|
|
|
f = open(os.path.join(self.test.result_folder_name, afile))
|
|
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(.+)'
|
|
|
|
for line in lines:
|
|
rev = re.match(revision_pattern, line.strip())
|
|
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.legalize_filename(
|
|
r.group(1).strip(':'))
|
|
score = float(r.group(2))
|
|
bench_dict[name] += [float(score)]
|
|
self.test.revision_dict[browser][version][name] += \
|
|
[revision_num]
|
|
|
|
f.close()
|
|
self.calculate_geometric_mean(browser, version, revision_num)
|
|
|
|
|
|
class DromaeoSizeTest(Test):
|
|
"""Run tests to determine the compiled file output size of Dromaeo."""
|
|
def __init__(self, test_runner):
|
|
super(DromaeoSizeTest, self).__init__(
|
|
self.name(),
|
|
['browser'], ['dart', 'frog_dom', 'frog_html', 'frog_htmlidiomatic'],
|
|
DromaeoTester.DROMAEO_BENCHMARKS.keys(), test_runner,
|
|
self.DromaeoSizeTester(self),
|
|
self.DromaeoSizeProcessor(self),
|
|
self.DromaeoSizeGrapher(self), extra_metrics=['sum'])
|
|
|
|
@staticmethod
|
|
def name():
|
|
return 'dromaeo-size'
|
|
|
|
|
|
class DromaeoSizeTester(DromaeoTester):
|
|
def run_tests(self):
|
|
# Build tests.
|
|
dromaeo_path = os.path.join('samples', 'third_party', 'dromaeo')
|
|
current_path = os.getcwd()
|
|
os.chdir(dromaeo_path)
|
|
self.test.test_runner.run_cmd(
|
|
['python', os.path.join('generate_frog_tests.py')])
|
|
os.chdir(current_path)
|
|
|
|
self.test.trace_file = os.path.join(
|
|
'tools', 'testing', 'perf_testing', self.test.result_folder_name,
|
|
self.test.result_folder_name + self.test.cur_time)
|
|
self.add_svn_revision_to_trace(self.test.trace_file)
|
|
|
|
variants = [
|
|
('frog_dom', ''),
|
|
('frog_html', '-html'),
|
|
('frog_htmlidiomatic', '-htmlidiomatic')]
|
|
|
|
test_path = os.path.join(dromaeo_path, 'tests')
|
|
frog_path = os.path.join(test_path, 'frog')
|
|
total_size = {}
|
|
for (variant, _) in variants:
|
|
total_size[variant] = 0
|
|
total_dart_size = 0
|
|
for suite in DromaeoTester.DROMAEO_BENCHMARKS.keys():
|
|
dart_size = 0
|
|
try:
|
|
dart_size = os.path.getsize(os.path.join(test_path,
|
|
'dom-%s.dart' % suite))
|
|
except OSError:
|
|
pass #If compilation failed, continue on running other tests.
|
|
|
|
total_dart_size += dart_size
|
|
self.test.test_runner.run_cmd(
|
|
['echo', 'Size (dart, %s): %s' % (suite, str(dart_size))],
|
|
self.test.trace_file, append=True)
|
|
|
|
for (variant, suffix) in variants:
|
|
name = 'dom-%s%s.dart.js' % (suite, suffix)
|
|
js_size = 0
|
|
try:
|
|
# TODO(vsm): Strip comments at least. Consider compression.
|
|
js_size = os.path.getsize(os.path.join(frog_path, name))
|
|
except OSError:
|
|
pass #If compilation failed, continue on running other tests.
|
|
|
|
total_size[variant] += js_size
|
|
self.test.test_runner.run_cmd(
|
|
['echo', 'Size (%s, %s): %s' % (variant, suite, str(js_size))],
|
|
self.test.trace_file, append=True)
|
|
|
|
self.test.test_runner.run_cmd(
|
|
['echo', 'Size (dart, %s): %s' % (total_dart_size,
|
|
self.test.extra_metrics[0])],
|
|
self.test.trace_file, append=True)
|
|
for (variant, _) in variants:
|
|
self.test.test_runner.run_cmd(
|
|
['echo', 'Size (%s, %s): %s' % (variant, self.test.extra_metrics[0],
|
|
total_size[variant])],
|
|
self.test.trace_file, append=True)
|
|
|
|
class DromaeoSizeProcessor(Processor):
|
|
def process_file(self, afile):
|
|
"""Pull all the relevant information out of a given tracefile.
|
|
|
|
Args:
|
|
afile: is the filename string we will be processing."""
|
|
os.chdir(os.path.join(DART_INSTALL_LOCATION, 'tools',
|
|
'testing', 'perf_testing'))
|
|
f = open(os.path.join(self.test.result_folder_name, afile))
|
|
tabulate_data = False
|
|
revision_num = 0
|
|
revision_pattern = r'Revision: (\d+)'
|
|
result_pattern = r'Size \((\w+), ([a-zA-Z0-9-]+)\): (\d+)'
|
|
|
|
for line in f.readlines():
|
|
rev = re.match(revision_pattern, line.strip())
|
|
if rev:
|
|
revision_num = int(rev.group(1))
|
|
continue
|
|
|
|
result = re.match(result_pattern, line.strip())
|
|
if result:
|
|
variant = result.group(1)
|
|
metric = result.group(2)
|
|
num = result.group(3)
|
|
if num.find('.') == -1:
|
|
num = int(num)
|
|
else:
|
|
num = float(num)
|
|
self.test.values_dict['browser'][variant][metric] += [num]
|
|
self.test.revision_dict['browser'][variant][metric] += [revision_num]
|
|
|
|
f.close()
|
|
class DromaeoSizeGrapher(Grapher):
|
|
def plot_results(self, png_filename):
|
|
self.style_and_save_perf_plot(
|
|
'Compiled Dromaeo Sizes',
|
|
'Size (in bytes)', 10, 10, 'lower left', png_filename,
|
|
['browser'], ['dart', 'frog_dom', 'frog_html', 'frog_htmlidiomatic'],
|
|
DromaeoTester.DROMAEO_BENCHMARKS.keys())
|
|
|
|
self.style_and_save_perf_plot(
|
|
'Compiled Dromaeo Sizes',
|
|
'Size (in bytes)', 10, 10, 'lower left', '2' + png_filename,
|
|
['browser'], ['dart', 'frog_dom', 'frog_html', 'frog_htmlidiomatic'],
|
|
[self.test.extra_metrics[0]])
|
|
|
|
|
|
class CompileTimeAndSizeTest(Test):
|
|
"""Run tests to determine how long minfrog takes to compile, and the compiled
|
|
file output size of some benchmarking files."""
|
|
def __init__(self, test_runner):
|
|
"""Reference to the test_runner object that notifies us when to begin
|
|
testing."""
|
|
super(CompileTimeAndSizeTest, self).__init__(
|
|
self.name(), ['commandline'], ['frog'],
|
|
['Compiling on Dart VM', 'Bootstrapping', 'minfrog', 'swarm', 'total'],
|
|
test_runner, self.CompileTester(self),
|
|
self.CompileProcessor(self), self.CompileGrapher(self))
|
|
self.dart_compiler = os.path.join(
|
|
DART_INSTALL_LOCATION, utils.GetBuildRoot(utils.GuessOS(),
|
|
'release', 'ia32'), 'dart-sdk', 'bin', 'frogc')
|
|
_suffix = ''
|
|
if platform.system() == 'Windows':
|
|
_suffix = '.exe'
|
|
self.dart_vm = os.path.join(
|
|
DART_INSTALL_LOCATION, utils.GetBuildRoot(utils.GuessOS(),
|
|
'release', 'ia32'), 'dart-sdk', 'bin','dart' + _suffix)
|
|
self.failure_threshold = {
|
|
'Compiling on Dart VM' : 1, 'Bootstrapping' : .5, 'minfrog' : 100,
|
|
'swarm' : 100, 'total' : 100}
|
|
|
|
@staticmethod
|
|
def name():
|
|
return 'time-size'
|
|
|
|
class CompileTester(Tester):
|
|
def run_tests(self):
|
|
os.chdir('frog')
|
|
self.test.trace_file = os.path.join(
|
|
'..', 'tools', 'testing', 'perf_testing',
|
|
self.test.result_folder_name,
|
|
self.test.result_folder_name + self.test.cur_time)
|
|
|
|
self.add_svn_revision_to_trace(self.test.trace_file)
|
|
|
|
elapsed = self.test.test_runner.time_cmd(
|
|
[self.test.dart_vm, os.path.join('.', 'minfrogc.dart'),
|
|
'--out=minfrog', 'minfrog.dart'])
|
|
self.test.test_runner.run_cmd(
|
|
['echo', '%f Compiling on Dart VM in production mode in seconds'
|
|
% elapsed], self.test.trace_file, append=True)
|
|
elapsed = self.test.test_runner.time_cmd(
|
|
[os.path.join('.', 'minfrog'), '--out=minfrog', 'minfrog.dart',
|
|
os.path.join('tests', 'hello.dart')])
|
|
if elapsed < self.test.failure_threshold['Bootstrapping']:
|
|
#minfrog didn't compile correctly. Stop testing now, because subsequent
|
|
#numbers will be meaningless.
|
|
return
|
|
size = os.path.getsize('minfrog')
|
|
self.test.test_runner.run_cmd(
|
|
['echo', '%f Bootstrapping time in seconds in production mode' %
|
|
elapsed], self.test.trace_file, append=True)
|
|
self.test.test_runner.run_cmd(
|
|
['echo', '%d Generated checked minfrog size' % size],
|
|
self.test.trace_file, append=True)
|
|
|
|
self.test.test_runner.run_cmd(
|
|
[self.test.dart_compiler, '--out=swarm-result',
|
|
os.path.join('..', 'samples', 'swarm',
|
|
'swarm.dart')])
|
|
|
|
swarm_size = 0
|
|
try:
|
|
swarm_size = os.path.getsize('swarm-result')
|
|
except OSError:
|
|
pass #If compilation failed, continue on running other tests.
|
|
|
|
self.test.test_runner.run_cmd(
|
|
[self.test.dart_compiler, '--out=total-result',
|
|
os.path.join('..', 'samples', 'total',
|
|
'client', 'Total.dart')])
|
|
total_size = 0
|
|
try:
|
|
total_size = os.path.getsize('total-result')
|
|
except OSError:
|
|
pass #If compilation failed, continue on running other tests.
|
|
|
|
self.test.test_runner.run_cmd(
|
|
['echo', '%d Generated checked swarm size' % swarm_size],
|
|
self.test.trace_file, append=True)
|
|
|
|
self.test.test_runner.run_cmd(
|
|
['echo', '%d Generated checked total size' % total_size],
|
|
self.test.trace_file, append=True)
|
|
|
|
#Revert our newly built minfrog to prevent conflicts when we update
|
|
self.test.test_runner.run_cmd(
|
|
['svn', 'revert', os.path.join(os.getcwd(), 'frog', 'minfrog')])
|
|
|
|
os.chdir('..')
|
|
|
|
class CompileProcessor(Processor):
|
|
def process_file(self, afile):
|
|
"""Pull all the relevant information out of a given tracefile.
|
|
|
|
Args:
|
|
afile: is the filename string we will be processing."""
|
|
os.chdir(os.path.join(DART_INSTALL_LOCATION, 'tools',
|
|
'testing', 'perf_testing'))
|
|
f = open(os.path.join(self.test.result_folder_name, afile))
|
|
tabulate_data = False
|
|
revision_num = 0
|
|
for line in f.readlines():
|
|
tokens = line.split()
|
|
if 'Revision' in line:
|
|
revision_num = int(line.split()[1])
|
|
else:
|
|
for metric in self.test.values_list:
|
|
if metric in line:
|
|
num = tokens[0]
|
|
if num.find('.') == -1:
|
|
num = int(num)
|
|
else:
|
|
num = float(num)
|
|
self.test.values_dict['commandline']['frog'][metric] += [num]
|
|
self.test.revision_dict['commandline']['frog'][metric] += \
|
|
[revision_num]
|
|
|
|
if revision_num != 0:
|
|
for metric in self.test.values_list:
|
|
self.test.revision_dict['commandline']['frog'][metric].pop()
|
|
self.test.revision_dict['commandline']['frog'][metric] += \
|
|
[revision_num]
|
|
# Fill in 0 if compilation failed.
|
|
if self.test.values_dict['commandline']['frog'][metric][-1] < \
|
|
self.test.failure_threshold[metric]:
|
|
self.test.values_dict['commandline']['frog'][metric] += [0]
|
|
self.test.revision_dict['commandline']['frog'][metric] += \
|
|
[revision_num]
|
|
|
|
f.close()
|
|
|
|
class CompileGrapher(Grapher):
|
|
|
|
def plot_results(self, png_filename):
|
|
self.style_and_save_perf_plot(
|
|
'Compiled minfrog Sizes', 'Size (in bytes)', 10, 10, 'lower left',
|
|
png_filename, ['commandline'], ['frog'],
|
|
['swarm', 'total', 'minfrog'])
|
|
|
|
self.style_and_save_perf_plot(
|
|
'Time to compile and bootstrap',
|
|
'Seconds', 10, 10, 'lower left', '2' + png_filename, ['commandline'],
|
|
['frog'], ['Bootstrapping', 'Compiling on Dart VM'])
|
|
|
|
|
|
class TestBuilder(object):
|
|
"""Construct the desired test object."""
|
|
available_suites = dict((suite.name(), suite) for suite in [
|
|
CommonCommandLineTest, CompileTimeAndSizeTest,
|
|
CommonBrowserTest, DromaeoTest, DromaeoSizeTest])
|
|
|
|
@staticmethod
|
|
def make_test(test_name, test_runner):
|
|
return TestBuilder.available_suites[test_name](test_runner)
|
|
|
|
@staticmethod
|
|
def available_suite_names():
|
|
return TestBuilder.available_suites.keys()
|
|
|
|
|
|
def main():
|
|
runner = TestRunner()
|
|
continuous = runner.parse_args()
|
|
if continuous:
|
|
while True:
|
|
if runner.has_new_code():
|
|
runner.run_test_sequence()
|
|
else:
|
|
time.sleep(200)
|
|
else:
|
|
runner.run_test_sequence()
|
|
|
|
if __name__ == '__main__':
|
|
main()
|