Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(330)

Side by Side Diff: tools/testing/perf_testing/run_perf_tests.py

Issue 10027026: Major refactoring for run_perf_tests.py. (Closed) Base URL: http://dart.googlecode.com/svn/branches/bleeding_edge/dart/
Patch Set: Created 8 years, 8 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « no previous file | no next file » | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 #!/usr/bin/python 1 #!/usr/bin/python
2 2
3 # Copyright (c) 2011, the Dart project authors. Please see the AUTHORS file 3 # Copyright (c) 2011, the Dart project authors. Please see the AUTHORS file
4 # for details. All rights reserved. Use of this source code is governed by a 4 # for details. All rights reserved. Use of this source code is governed by a
5 # BSD-style license that can be found in the LICENSE file. 5 # BSD-style license that can be found in the LICENSE file.
6 6
7 import datetime 7 import datetime
8 import getpass 8 import getpass
9 import math 9 import math
10 try: 10 try:
11 from matplotlib.font_manager import FontProperties 11 from matplotlib.font_manager import FontProperties
12 import matplotlib.pyplot as plt 12 import matplotlib.pyplot as plt
13 except ImportError: 13 except ImportError:
14 pass # Only needed if we want to make graphs. 14 pass # Only needed if we want to make graphs.
15 import optparse 15 import optparse
16 import os 16 import os
17 from os.path import dirname, abspath 17 from os.path import dirname, abspath
18 import platform 18 import platform
19 import re 19 import re
20 import shutil 20 import shutil
21 import stat 21 import stat
22 import subprocess 22 import subprocess
23 import sys 23 import sys
24 import time 24 import time
25 import traceback 25 import traceback
26 26
27 TOOLS_PATH = os.path.join(dirname(dirname(dirname(abspath(__file__))))) 27 TOOLS_PATH = os.path.join(dirname(dirname(dirname(abspath(__file__)))))
28 DART_INSTALL_LOCATION = abspath(os.path.join(dirname(abspath(__file__)),
29 '..', '..', '..'))
28 sys.path.append(TOOLS_PATH) 30 sys.path.append(TOOLS_PATH)
29 import utils 31 import utils
30 32
31 """This script runs to track performance and size progress of 33 """This script runs to track performance and size progress of
32 different svn revisions. It tests to see if there a newer version of the code on 34 different svn revisions. It tests to see if there a newer version of the code on
33 the server, and will sync and run the performance tests if so.""" 35 the server, and will sync and run the performance tests if so."""
34
35 DART_INSTALL_LOCATION = abspath(os.path.join(dirname(abspath(__file__)),
36 '..', '..', '..'))
37 _suffix = ''
38 if platform.system() == 'Windows':
39 _suffix = '.exe'
40 DART_VM = os.path.join(DART_INSTALL_LOCATION,
41 utils.GetBuildRoot(utils.GuessOS(), 'release', 'ia32'),
42 'dart-sdk',
43 'bin',
44 'dart' + _suffix)
45 DART_COMPILER = os.path.join(DART_INSTALL_LOCATION,
46 utils.GetBuildRoot(utils.GuessOS(),
47 'release', 'ia32'),
48 'dart-sdk',
49 'bin',
50 'frogc')
51
52 GEO_MEAN = 'Geo-Mean'
53 COMMAND_LINE = 'commandline'
54 JS = 'js'
55 FROG = 'frog'
56 JS_AND_FROG = [JS, FROG]
57 COLORS = ['blue', 'green', 'red', 'cyan', 'magenta', 'black']
58 GRAPH_OUT_DIR = 'graphs'
59
60 BROWSER_PERF = 'browser-perf'
61 TIME_SIZE = 'time-size'
62 CL_PERF = 'cl-perf'
63 # TODO(vsm): Merge these?
64 DROMAEO = 'dromaeo'
65 DROMAEO_SIZE = 'dromaeo-size'
66
67 SLEEP_TIME = 200
68 VERBOSE = False
69 HAS_SHELL = False
70 if platform.system() == 'Windows':
71 # On Windows, shell must be true to get the correct environment variables.
72 HAS_SHELL = True
73
74 """First, some utility methods."""
75
76 def run_cmd(cmd_list, outfile=None, append=False, std_in=''):
77 """Run the specified command and print out any output to stdout.
78
79 Args:
80 cmd_list: a list of strings that make up the command to run
81 outfile: a string indicating the name of the file that we should write
82 stdout to
83 append: True if we want to append to the file instead of overwriting it"""
84 if VERBOSE:
85 print ' '.join(cmd_list)
86 out = subprocess.PIPE
87 if outfile:
88 mode = 'w'
89 if append:
90 mode = 'a'
91 out = open(outfile, mode)
92 if append:
93 # Annoying Windows "feature" -- append doesn't actually append unless you
94 # explicitly go to the end of the file.
95 # http://mail.python.org/pipermail/python-list/2009-October/1221859.html
96 out.seek(0, os.SEEK_END)
97 p = subprocess.Popen(cmd_list, stdout = out, stderr=subprocess.PIPE,
98 stdin=subprocess.PIPE, shell=HAS_SHELL)
99 output, not_used = p.communicate(std_in);
100 if output:
101 print output
102 return output
103
104 def time_cmd(cmd):
105 """Determine the amount of (real) time it takes to execute a given command."""
106 start = time.time()
107 run_cmd(cmd)
108 return time.time() - start
109
110 def sync_and_build():
111 """Make sure we have the latest version of of the repo, and build it. We
112 begin and end standing in DART_INSTALL_LOCATION.
113
114 Returns:
115 err_code = 1 if there was a problem building."""
116 os.chdir(DART_INSTALL_LOCATION)
117 #Revert our newly built minfrog to prevent conflicts when we update
118 run_cmd(['svn', 'revert', os.path.join(os.getcwd(), 'frog', 'minfrog')])
119
120 run_cmd(['gclient', 'sync'])
121
122 # On Windows, the output directory is marked as "Read Only," which causes an
123 # error to be thrown when we use shutil.rmtree. This helper function changes
124 # the permissions so we can still delete the directory.
125 def on_rm_error(func, path, exc_info):
126 if os.path.exists(path):
127 os.chmod(path, stat.S_IWRITE)
128 os.unlink(path)
129 # TODO(efortuna): building the sdk locally is a band-aid until all build
130 # platform SDKs are hosted in Google storage. Pull from https://sandbox.
131 # google.com/storage/?arg=dart-dump-render-tree#dart-dump-render-tree%2Fsdk
132 # eventually.
133 # TODO(efortuna): Currently always building ia32 architecture because we don't
134 # have test statistics for what's passing on x64. Eliminate arch specification
135 # when we have tests running on x64, too.
136 shutil.rmtree(os.path.join(os.getcwd(),
137 utils.GetBuildRoot(utils.GuessOS(), 'release', 'ia32')),
138 onerror=on_rm_error)
139 lines = run_cmd([os.path.join('.', 'tools', 'build.py'), '-m', 'release',
140 '--arch=ia32', 'create_sdk'])
141 lines = run_cmd([os.path.join('.', 'tools', 'build.py'), '-m', 'release',
142 '--arch=ia32', 'dart2js']) #Built only for the v8 target for CL tests.
143
144 for line in lines:
145 if 'BUILD FAILED' in lines:
146 # Someone checked in a broken build! Just stop trying to make it work
147 # and wait to try again.
148 print 'Broken Build'
149 return 1
150 return 0
151
152 def ensure_output_directory(dir_name):
153 """Test that the listed directory name exists, and if not, create one for
154 our output to be placed.
155
156 Args:
157 dir_name: the directory we will create if it does not exist."""
158 dir_path = os.path.join(DART_INSTALL_LOCATION, 'tools', 'testing',
159 'perf_testing', dir_name)
160 if not os.path.exists(dir_path):
161 os.mkdir(dir_path)
162 print 'Creating output directory ', dir_path
163
164 def has_new_code():
165 """Tests if there are any newer versions of files on the server."""
166 os.chdir(DART_INSTALL_LOCATION)
167 # Pass 'p' in if we have a new certificate for the svn server, we want to
168 # (p)ermanently accept it.
169 results = run_cmd(['svn', 'st', '-u'], std_in='p')
170 for line in results:
171 if '*' in line:
172 return True
173 return False
174
175 # TODO(vsm): Add Dartium.
176 def get_browsers():
177 browsers = ['ff', 'chrome']
178 if platform.system() == 'Darwin':
179 browsers += ['safari']
180 if platform.system() == 'Windows':
181 browsers += ['ie']
182 return browsers
183
184 # TODO(vsm): Factor benchmark specific code to a better location.
185 def get_standalone_benchmarks():
186 return ['Mandelbrot', 'DeltaBlue', 'Richards', 'NBody', 'BinaryTrees',
187 'Fannkuch', 'Meteor', 'BubbleSort', 'Fibonacci', 'Loop', 'Permute',
188 'Queens', 'QuickSort', 'Recurse', 'Sieve', 'Sum', 'Tak', 'Takl', 'Towers',
189 'TreeSort']
190
191 def get_os_directory():
192 """Specifies the name of the directory for the testing build of dart, which
193 has yet a different naming convention from utils.getBuildRoot(...)."""
194 if platform.system() == 'Windows':
195 return 'windows'
196 elif platform.system() == 'Darwin':
197 return 'macos'
198 else:
199 return 'linux'
200
201 def upload_to_app_engine(suite_names):
202 """Upload our results to our appengine server.
203 Arguments:
204 suite_names: Directories to upload data from (should match suite names)
205 """
206 # TODO(efortuna): This is the most basic way to get the data up
207 # for others to view. Revisit this once we're serving nicer graphs (Google
208 # Chart Tools) and from multiple perfbots and once we're in a position to
209 # organize the data in a useful manner(!!).
210 os.chdir(os.path.join(DART_INSTALL_LOCATION, 'tools', 'testing',
211 'perf_testing'))
212 for data in suite_names:
213 path = os.path.join('appengine', 'static', 'data', data, utils.GuessOS())
214 shutil.rmtree(path, ignore_errors=True)
215 os.makedirs(path)
216 files = []
217 # Copy the 1000 most recent trace files to be uploaded.
218 for f in os.listdir(data):
219 files += [(os.path.getmtime(os.path.join(data, f)), f)]
220 files.sort()
221 for f in files[-1000:]:
222 shutil.copyfile(os.path.join(data, f[1]),
223 os.path.join(path, f[1]+'.txt'))
224 # Generate directory listing.
225 for data in suite_names:
226 path = os.path.join('appengine', 'static', 'data', data, utils.GuessOS())
227 out = open(os.path.join('appengine', 'static',
228 '%s-%s.html' % (data, utils.GuessOS())), 'w')
229 out.write('<html>\n <body>\n <ul>\n')
230 for f in os.listdir(path):
231 if not f.startswith('.'):
232 out.write(' <li><a href=data' + \
233 '''/%(data)s/%(os)s/%(file)s>%(file)s</a></li>\n''' % \
234 {'data': data, 'os': utils.GuessOS(), 'file': f})
235 out.write(' </ul>\n </body>\n</html>')
236 out.close()
237
238 shutil.rmtree(os.path.join('appengine', 'static', 'graphs'),
239 ignore_errors=True)
240 shutil.copytree('graphs', os.path.join('appengine', 'static', 'graphs'))
241 shutil.copyfile('index.html', os.path.join('appengine', 'static',
242 'index.html'))
243 shutil.copyfile('dromaeo.html', os.path.join('appengine', 'static',
244 'dromaeo.html'))
245 shutil.copyfile('data.html', os.path.join('appengine', 'static',
246 'data.html'))
247 run_cmd([os.path.join('..', '..', '..', 'third_party',
248 'appengine-python', 'appcfg.py'), '--oauth2', 'update',
249 'appengine/'])
250
251
252 class TestRunner(object): 36 class TestRunner(object):
37
38 def __init__(self):
39 self.verbose = False
40 self.has_shell = False
41 if platform.system() == 'Windows':
42 # On Windows, shell must be true to get the correct environment variables.
43 self.has_shell = True
44
45 def run_cmd(self, cmd_list, outfile=None, append=False, std_in=''):
46 """Run the specified command and print out any output to stdout.
47
48 Args:
49 cmd_list: a list of strings that make up the command to run
50 outfile: a string indicating the name of the file that we should write
51 stdout to
52 append: True if we want to append to the file instead of overwriting it
53 std_in: a string that should be written to the process executing to
54 interact with it (if needed)"""
55 if self.verbose:
56 print ' '.join(cmd_list)
57 out = subprocess.PIPE
58 if outfile:
59 mode = 'w'
60 if append:
61 mode = 'a'
62 out = open(outfile, mode)
63 if append:
64 # Annoying Windows "feature" -- append doesn't actually append unless
65 # you explicitly go to the end of the file.
66 # http://mail.python.org/pipermail/python-list/2009-October/1221859.html
67 out.seek(0, os.SEEK_END)
68 p = subprocess.Popen(cmd_list, stdout = out, stderr=subprocess.PIPE,
69 stdin=subprocess.PIPE, shell=self.has_shell)
70 output, _ = p.communicate(std_in);
71 if output:
72 print output
73 return output
74
75 def time_cmd(self, cmd):
76 """Determine the amount of (real) time it takes to execute a given
77 command."""
78 start = time.time()
79 self.run_cmd(cmd)
80 return time.time() - start
81
82 @staticmethod
83 def get_build_targets(suites):
84 """Loop through a set of tests that we want to run and find the build
85 targets that are necessary.
86
87 Args:
88 suites: The test suites that we wish to run."""
89 build_targets = set()
90 for test in suites:
91 if test.build_targets is not None:
92 for target in test.build_targets:
93 build_targets.add(target)
94 return build_targets
95
96 def sync_and_build(self, suites):
97 """Make sure we have the latest version of of the repo, and build it. We
98 begin and end standing in DART_INSTALL_LOCATION.
99
100 Args:
101 suites: The set of suites that we wish to build.
102
103 Returns:
104 err_code = 1 if there was a problem building."""
105 os.chdir(DART_INSTALL_LOCATION)
106
107 self.run_cmd(['gclient', 'sync'])
108
109 # On Windows, the output directory is marked as "Read Only," which causes an
110 # error to be thrown when we use shutil.rmtree. This helper function changes
111 # the permissions so we can still delete the directory.
112 def on_rm_error(func, path, exc_info):
113 if os.path.exists(path):
114 os.chmod(path, stat.S_IWRITE)
115 os.unlink(path)
116 # TODO(efortuna): building the sdk locally is a band-aid until all build
117 # platform SDKs are hosted in Google storage. Pull from https://sandbox.
118 # google.com/storage/?arg=dart-dump-render-tree#dart-dump-render-tree%2Fsdk
119 # eventually.
120 # TODO(efortuna): Currently always building ia32 architecture because we
121 # don't have test statistics for what's passing on x64. Eliminate arch
122 # specification when we have tests running on x64, too.
123 shutil.rmtree(os.path.join(os.getcwd(),
124 utils.GetBuildRoot(utils.GuessOS(), 'release', 'ia32')),
125 onerror=on_rm_error)
126
127 for target in TestRunner.get_build_targets(suites):
128 lines = self.run_cmd([os.path.join('.', 'tools', 'build.py'), '-m',
129 'release', '--arch=ia32', target])
130
131 for line in lines:
132 if 'BUILD FAILED' in lines:
133 # Someone checked in a broken build! Stop trying to make it work
134 # and wait to try again.
135 print 'Broken Build'
136 return 1
137 return 0
138
139 def ensure_output_directory(self, dir_name):
140 """Test that the listed directory name exists, and if not, create one for
141 our output to be placed.
142
143 Args:
144 dir_name: the directory we will create if it does not exist."""
145 dir_path = os.path.join(DART_INSTALL_LOCATION, 'tools',
146 'testing', 'perf_testing', dir_name)
147 if not os.path.exists(dir_path):
148 os.mkdir(dir_path)
149 print 'Creating output directory ', dir_path
150
151 def has_new_code(self):
152 """Tests if there are any newer versions of files on the server."""
153 os.chdir(DART_INSTALL_LOCATION)
154 # Pass 'p' in if we have a new certificate for the svn server, we want to
155 # (p)ermanently accept it.
156 results = self.run_cmd(['svn', 'st', '-u'], std_in='p')
157 for line in results:
158 if '*' in line:
159 return True
160 return False
161
162 def get_os_directory(self):
163 """Specifies the name of the directory for the testing build of dart, which
164 has yet a different naming convention from utils.getBuildRoot(...)."""
165 if platform.system() == 'Windows':
166 return 'windows'
167 elif platform.system() == 'Darwin':
168 return 'macos'
169 else:
170 return 'linux'
171
172 def upload_to_app_engine(self, suite_names):
173 """Upload our results to our appengine server.
174 Arguments:
175 suite_names: Directories to upload data from (should match directory
176 names)."""
177 # TODO(efortuna): This is the most basic way to get the data up
178 # for others to view. Revisit this once we're serving nicer graphs (Google
179 # Chart Tools) and from multiple perfbots and once we're in a position to
180 # organize the data in a useful manner(!!).
181 os.chdir(os.path.join(DART_INSTALL_LOCATION, 'tools', 'testing',
182 'perf_testing'))
183 for data in suite_names:
184 path = os.path.join('appengine', 'static', 'data', data, utils.GuessOS())
185 shutil.rmtree(path, ignore_errors=True)
186 os.makedirs(path)
187 files = []
188 # Copy the 1000 most recent trace files to be uploaded.
189 for f in os.listdir(data):
190 files += [(os.path.getmtime(os.path.join(data, f)), f)]
191 files.sort()
192 for f in files[-1000:]:
193 shutil.copyfile(os.path.join(data, f[1]),
194 os.path.join(path, f[1]+'.txt'))
195 # Generate directory listing.
196 for data in suite_names:
197 path = os.path.join('appengine', 'static', 'data', data, utils.GuessOS())
198 out = open(os.path.join('appengine', 'static',
199 '%s-%s.html' % (data, utils.GuessOS())), 'w')
200 out.write('<html>\n <body>\n <ul>\n')
201 for f in os.listdir(path):
202 if not f.startswith('.'):
203 out.write(' <li><a href=data' + \
204 '''/%(data)s/%(os)s/%(file)s>%(file)s</a></li>\n''' % \
205 {'data': data, 'os': utils.GuessOS(), 'file': f})
206 out.write(' </ul>\n </body>\n</html>')
207 out.close()
208
209 shutil.rmtree(os.path.join('appengine', 'static', 'graphs'),
210 ignore_errors=True)
211 shutil.copytree('graphs', os.path.join('appengine', 'static', 'graphs'))
212 shutil.copyfile('index.html', os.path.join('appengine', 'static',
213 'index.html'))
214 shutil.copyfile('dromaeo.html', os.path.join('appengine', 'static',
215 'dromaeo.html'))
216 shutil.copyfile('data.html', os.path.join('appengine', 'static',
217 'data.html'))
218 self.run_cmd([os.path.join('..', '..', '..', 'third_party',
219 'appengine-python', 'appcfg.py'), '--oauth2',
220 'update', 'appengine/'])
221
222 def parse_args(self):
223 parser = optparse.OptionParser()
224 parser.add_option('--suites', '-s', dest='suites', help='Run the specified '
225 'comma-separated test suites from set: %s' % \
226 ','.join(TestBuilder.available_suite_names()),
227 action='store', default=None)
228 parser.add_option('--forever', '-f', dest='continuous', help='Run this scri'
229 'pt forever, always checking for the next svn checkin',
230 action='store_true', default=False)
231 parser.add_option('--graph-only', '-g', dest='graph_only', default=False,
232 help='Do not run tests, only regenerate graphs',
233 action='store_true')
234 parser.add_option('--nobuild', '-n', dest='no_build', action='store_true',
235 help='Do not sync with the repository and do not '
236 'rebuild.', default=False)
237 parser.add_option('--upload', '-u', dest='upload', help='Upload data to '
238 'app engine (will require authentication).',
239 action='store_true', default=False)
240 parser.add_option('--verbose', '-v', dest='verbose', help='Print extra '
241 'debug output', action='store_true', default=False)
242
243 args, ignored = parser.parse_args()
244
245 if not args.suites:
246 suites = TestBuilder.available_suite_names()
247 else:
248 suites = []
249 suitelist = args.suites.split(',')
250 for name in suitelist:
251 if name in TestBuilder.available_suite_names():
252 suites.append(name)
253 else:
254 print ('Error: Invalid suite %s not in ' % name) + \
255 '%s' % ','.join(TestBuilder.available_suite_names())
256 sys.exit(1)
257 self.suite_names = suites
258 self.no_build = args.no_build
259 self.graph_only = args.graph_only
260 self.upload = args.upload
261 self.verbose = args.verbose
262 return args.continuous
263
264 def run_test_sequence(self):
265 """Run the set of commands to (possibly) build, run, and graph the results
266 of our tests.
267
268 Args:
269 suite_names: The "display name" the user enters to specify which
270 benchmark(s) to run.
271 no_build: True if we should not check the repository and build the latest
272 version.
273 graph_only: True if we should not run the tests, just (re)generate graphs.
274 upload: True if we should upload our results to appengine."""
275 suites = []
276 for name in self.suite_names:
277 suites += [TestBuilder.make_test(name, self)]
278
279 if not self.no_build and self.sync_and_build(suites) == 1:
280 return # The build is broken.
281
282 for test in suites:
283 test.run(self.graph_only)
284
285 if self.upload:
286 self.upload_to_app_engine(TestBuilder.available_site_names())
287
288
289 class Test(object):
253 """The base class to provide shared code for different tests we will run and 290 """The base class to provide shared code for different tests we will run and
254 graph.""" 291 graph. At a high level, each test has three visitors (the tester, the
292 file_processor, and the grapher) that perform operations on the test
293 object."""
255 294
256 def __init__(self, result_folder_name, platform_list, variants, 295 def __init__(self, result_folder_name, platform_list, variants,
257 values_list): 296 values_list, test_runner, tester, file_processor, grapher,
297 extra_metrics=['Geo-Mean'], build_targets=['create_sdk']):
258 """Args: 298 """Args:
259 result_folder_name the name of the folder where a tracefile of 299 result_folder_name: The name of the folder where a tracefile of
260 performance results will be stored. 300 performance results will be stored.
261 platform_list a list containing the platform(s) that our data has been 301 platform_list: A list containing the platform(s) that our data has been
262 run on. (command line, firefox, chrome, etc) 302 run on. (command line, firefox, chrome, etc)
263 variants a list specifying whether we hold data about Frog 303 variants: A list specifying whether we hold data about Frog
264 generated code, plain JS code (js), or a combination of both. 304 generated code, plain JS code, or a combination of both, or
265 values_list a list containing the type of data we will be graphing 305 Dart depending on the test.
266 (benchmarks, percentage passing, etc)""" 306 values_list: A list containing the type of data we will be graphing
307 (benchmarks, percentage passing, etc).
308 test_runner: Reference to the parent test runner object that notifies a
309 test when to run.
310 tester: The visitor that actually performs the test running mechanics.
311 file_processor: The visitor that processes files in the format
312 appropriate for this test.
313 grapher: The visitor that generates graphs given our test result data.
314 extra_metrics: A list of any additional measurements we wish to keep
315 track of (such as the geometric mean of a set, the sum, etc).
316 build_targets: The targets necessary to build to run these tests
317 (default target is create_sdk)."""
267 self.result_folder_name = result_folder_name 318 self.result_folder_name = result_folder_name
268 # cur_time is used as a timestamp of when this performance test was run. 319 # cur_time is used as a timestamp of when this performance test was run.
269 self.cur_time = str(time.mktime(datetime.datetime.now().timetuple())) 320 self.cur_time = str(time.mktime(datetime.datetime.now().timetuple()))
270 # TODO(vsm): Factor out.
271 self.browser_color = {'chrome': 'green', 'ie': 'blue', 'ff': 'red',
272 'safari':'black'}
273 self.values_list = values_list 321 self.values_list = values_list
274 self.platform_list = platform_list 322 self.platform_list = platform_list
275 self.revision_dict = dict() 323 self.revision_dict = dict()
276 self.values_dict = dict() 324 self.values_dict = dict()
277 self.color_index = 0 325 self.test_runner = test_runner
326 self.tester = tester
327 self.file_processor = file_processor
328 self.grapher = grapher
329 self.extra_metrics = extra_metrics
330 self.build_targets = build_targets
331 # Initialize our values store.
278 for platform in platform_list: 332 for platform in platform_list:
279 self.revision_dict[platform] = dict() 333 self.revision_dict[platform] = dict()
280 self.values_dict[platform] = dict() 334 self.values_dict[platform] = dict()
281 for f in variants: 335 for f in variants:
282 self.revision_dict[platform][f] = dict() 336 self.revision_dict[platform][f] = dict()
283 self.values_dict[platform][f] = dict() 337 self.values_dict[platform][f] = dict()
284 for val in values_list: 338 for val in values_list:
285 self.revision_dict[platform][f][val] = [] 339 self.revision_dict[platform][f][val] = []
286 self.values_dict[platform][f][val] = [] 340 self.values_dict[platform][f][val] = []
287 self.revision_dict[platform][f][GEO_MEAN] = [] 341 for extra_metric in extra_metrics:
288 self.values_dict[platform][f][GEO_MEAN] = [] 342 self.revision_dict[platform][f][extra_metric] = []
343 self.values_dict[platform][f][extra_metric] = []
344
345 def run(self, graph_only):
346 """Run the benchmarks/tests from the command line and plot the
347 results.
348
349 Args:
350 graph_only: True if we should just graph the results instead of also
351 running tests."""
352 for visitor in [self.tester, self.file_processor, self.grapher]:
353 visitor.prepare()
354
355 os.chdir(DART_INSTALL_LOCATION)
356 self.test_runner.ensure_output_directory(self.result_folder_name)
357 if not graph_only:
358 self.tester.run_tests()
289 359
290 def get_color(self): 360 os.chdir(os.path.join('tools', 'testing', 'perf_testing'))
291 color = COLORS[self.color_index] 361
292 self.color_index = (self.color_index + 1) % len(COLORS) 362 # TODO(efortuna): You will want to make this only use a subset of the files
293 return color 363 # eventually.
364 files = os.listdir(self.result_folder_name)
365
366 for afile in files:
367 if not afile.startswith('.'):
368 self.file_processor.process_file(afile)
369
370 if 'plt' in globals():
371 # Only run Matplotlib if it is installed.
372 self.grapher.plot_results('%s.png' % self.result_folder_name)
373
374
375 class Tester(object):
376 """The base level visitor class that runs tests. It contains convenience
377 methods that many Tester objects use. Any class that would like to be a
378 TesterVisitor must implement the run_tests() method."""
379
380 def __init__(self, test):
381 self.test = test
382
383 def prepare(self):
384 """Perform any initial setup required before the test is run."""
385 pass
386
387 def add_svn_revision_to_trace(self, outfile):
388 """Add the svn version number to the provided tracefile."""
389 def search_for_revision(svn_info_command):
390 p = subprocess.Popen(svn_info_command, stdout = subprocess.PIPE,
391 stderr = subprocess.STDOUT, shell =
392 self.test.test_runner.has_shell)
393 output, _ = p.communicate()
394 for line in output.split('\n'):
395 if 'Revision' in line:
396 self.test.test_runner.run_cmd(['echo', line.strip()], outfile)
397 return True
398 return False
399
400 if not search_for_revision(['svn', 'info']):
401 if not search_for_revision(['git', 'svn', 'info']):
402 self.test.test_runner.run_cmd(['echo', 'Revision: unknown'], outfile)
403
404
405 class Processor(object):
406 """The base level vistor class that processes tests. It contains convenience
407 methods that many File Processor objects use. Any class that would like to be
408 a ProcessorVisitor must implement the process_file() method."""
409
410 def __init__(self, test):
411 self.test = test
412
413 def prepare(self):
414 """Perform any initial setup required before the test is run."""
415 pass
416
417 def calculate_geometric_mean(self, platform, variant, svn_revision):
418 """Calculate the aggregate geometric mean for JS and frog benchmark sets,
419 given two benchmark dictionaries."""
420 geo_mean = 0
421 for benchmark in self.test.values_list:
422 geo_mean += math.log(self.test.values_dict[platform][variant][benchmark][
423 len(self.test.values_dict[platform][variant][benchmark]) - 1])
424
425 self.test.values_dict[platform][variant]['Geo-Mean'] += \
426 [math.pow(math.e, geo_mean / len(self.test.values_list))]
427 self.test.revision_dict[platform][variant]['Geo-Mean'] += [svn_revision]
428
429
430 class Grapher(object):
431 """The base level visitor class that generates graphs for data. It contains
432 convenience methods that many Grapher objects use. Any class that would like
433 to be a GrapherVisitor must implement the plot_results() method."""
434
435 graph_out_dir = 'graphs'
436
437 def __init__(self, test):
438 self.color_index = 0
439 self.test = test
440
441 def prepare(self):
442 """Perform any initial setup required before the test is run."""
443 if 'plt' in globals():
444 plt.cla() # cla = clear current axes
445 else:
446 print 'Unable to import Matplotlib and therefore unable to generate ' + \
447 'graphs. Please install it for this version of Python.'
448 self.test.test_runner.ensure_output_directory(Grapher.graph_out_dir)
294 449
295 def style_and_save_perf_plot(self, chart_title, y_axis_label, size_x, size_y, 450 def style_and_save_perf_plot(self, chart_title, y_axis_label, size_x, size_y,
296 legend_loc, filename, platform_list, variants, values_list, 451 legend_loc, filename, platform_list, variants,
297 should_clear_axes=True): 452 values_list, should_clear_axes=True):
298 """Sets style preferences for chart boilerplate that is consistent across 453 """Sets style preferences for chart boilerplate that is consistent across
299 all charts, and saves the chart as a png. 454 all charts, and saves the chart as a png.
300 455
301 Args: 456 Args:
302 size_x: the size of the printed chart, in inches, in the horizontal 457 size_x: the size of the printed chart, in inches, in the horizontal
303 direction 458 direction
304 size_y: the size of the printed chart, in inches in the vertical direction 459 size_y: the size of the printed chart, in inches in the vertical direction
305 legend_loc: the location of the legend in on the chart. See suitable 460 legend_loc: the location of the legend in on the chart. See suitable
306 arguments for the loc argument in matplotlib 461 arguments for the loc argument in matplotlib
307 filename: the filename that we want to save the resulting chart as 462 filename: the filename that we want to save the resulting chart as
308 platform_list: a list containing the platform(s) that our data has been 463 platform_list: a list containing the platform(s) that our data has been
309 run on. (command line, firefox, chrome, etc) 464 run on. (command line, firefox, chrome, etc)
310 values_list: a list containing the type of data we will be graphing 465 values_list: a list containing the type of data we will be graphing
311 (performance, percentage passing, etc) 466 (performance, percentage passing, etc)
312 should_clear_axes: True if we want to create a fresh graph, instead of 467 should_clear_axes: True if we want to create a fresh graph, instead of
313 plotting additional lines on the current graph.""" 468 plotting additional lines on the current graph."""
314 if should_clear_axes: 469 if should_clear_axes:
315 plt.cla() # cla = clear current axes 470 plt.cla() # cla = clear current axes
316 for platform in platform_list: 471 for platform in platform_list:
317 for f in variants: 472 for f in variants:
318 for val in values_list: 473 for val in values_list:
319 plt.plot(self.revision_dict[platform][f][val], 474 plt.plot(self.test.revision_dict[platform][f][val],
320 self.values_dict[platform][f][val], 475 self.test.values_dict[platform][f][val],
321 color=self.get_color(), label='%s-%s-%s' % (platform, f, val)) 476 color=self.get_color(), label='%s-%s-%s' % (platform, f, val))
322 477
323 plt.xlabel('Revision Number') 478 plt.xlabel('Revision Number')
324 plt.ylabel(y_axis_label) 479 plt.ylabel(y_axis_label)
325 plt.title(chart_title) 480 plt.title(chart_title)
326 fontP = FontProperties() 481 fontP = FontProperties()
327 fontP.set_size('small') 482 fontP.set_size('small')
328 plt.legend(loc=legend_loc, prop = fontP) 483 plt.legend(loc=legend_loc, prop = fontP)
329 484
330 fig = plt.gcf() 485 fig = plt.gcf()
331 fig.set_size_inches(size_x, size_y) 486 fig.set_size_inches(size_x, size_y)
332 fig.savefig(os.path.join(GRAPH_OUT_DIR, filename)) 487 fig.savefig(os.path.join(Grapher.graph_out_dir, filename))
333 488
334 def add_svn_revision_to_trace(self, outfile): 489 def get_color(self):
335 """Add the svn version number to the provided tracefile.""" 490 # Just a bunch of distinct colors for a potentially large number of values
336 def search_for_revision(svn_info_command): 491 # we wish to graph.
337 p = subprocess.Popen(svn_info_command, stdout = subprocess.PIPE, 492 colors = [
338 stderr = subprocess.STDOUT, shell = HAS_SHELL) 493 '#3366CC', '#DC3912', '#FF9900', '#109618', '#990099', '#0099C6',
339 output, _ = p.communicate() 494 '#DD4477', '#66AA00', '#B82E2E', '#316395', '#994499', '#22AA99',
340 for line in output.split('\n'): 495 '#AAAA11', '#6633CC', '#E67300', '#8B0707', '#651067', '#329262',
341 if 'Revision' in line: 496 '#5574A6', '#3B3EAC', '#B77322', '#16D620', '#B91383', '#F4359E',
342 run_cmd(['echo', line.strip()], outfile) 497 '#9C5935', '#A9C413', '#2A778D', '#668D1C', '#BEA413', '#0C5922',
343 return True 498 '#743411', '#45AFE2', '#FF3300', '#FFCC00', '#14C21D', '#DF51FD',
344 return False 499 '#15CBFF', '#FF97D2', '#97FB00', '#DB6651', '#518BC6', '#BD6CBD',
345 500 '#35D7C2', '#E9E91F', '#9877DD', '#FF8F20', '#D20B0B', '#B61DBA',
346 if not search_for_revision(['svn', 'info']): 501 '#40BD7E', '#6AA7C4', '#6D70CD', '#DA9136', '#2DEA36', '#E81EA6',
347 if not search_for_revision(['git', 'svn', 'info']): 502 '#F558AE', '#C07145', '#D7EE53', '#3EA7C6', '#97D129', '#E9CA1D',
348 run_cmd(['echo', 'Revision: unknown'], outfile) 503 '#149638', '#C5571D']
349 504 color = colors[self.color_index]
350 def calculate_geometric_mean(self, platform, variant, svn_revision): 505 self.color_index = (self.color_index + 1) % len(colors)
351 """Calculate the aggregate geometric mean for JS and frog benchmark sets, 506 return color
352 given two benchmark dictionaries.""" 507
353 geo_mean = 0 508
354 for benchmark in self.values_list: 509 class RuntimePerformanceTest(Test):
355 geo_mean += math.log(self.values_dict[platform][variant][benchmark][ 510 """Super class for all runtime performance testing."""
356 len(self.values_dict[platform][variant][benchmark]) - 1]) 511
357
358 self.values_dict[platform][variant][GEO_MEAN] += \
359 [math.pow(math.e, geo_mean / len(self.values_list))]
360 self.revision_dict[platform][variant][GEO_MEAN] += [svn_revision]
361
362 def run(self, graph_only):
363 """Run the benchmarks/tests from the command line and plot the
364 results."""
365 plt.cla() # cla = clear current axes
366 os.chdir(DART_INSTALL_LOCATION)
367 ensure_output_directory(self.result_folder_name)
368 ensure_output_directory(GRAPH_OUT_DIR)
369 if not graph_only:
370 self.run_tests()
371
372 os.chdir(os.path.join('tools', 'testing', 'perf_testing'))
373
374 # TODO(efortuna): You will want to make this only use a subset of the files
375 # eventually.
376 files = os.listdir(self.result_folder_name)
377
378 for afile in files:
379 if not afile.startswith('.'):
380 self.process_file(afile)
381
382 if 'plt' in globals():
383 # Only run Matplotlib if it is installed.
384 self.plot_results('%s.png' % self.result_folder_name)
385 else:
386 print 'Unable to import Matplotlib and therefore unable to generate ' + \
387 'graphs. Please install it for this version of Python.'
388
389 class PerformanceTest(TestRunner):
390 """Super class for all performance testing."""
391 def __init__(self, result_folder_name, platform_list, platform_type, 512 def __init__(self, result_folder_name, platform_list, platform_type,
392 versions, benchmarks): 513 versions, benchmarks, test_runner, tester, file_processor,
393 super(PerformanceTest, self).__init__(result_folder_name, 514 build_targets=['create_sdk']):
394 platform_list, versions, benchmarks) 515 """Args:
516 result_folder_name: The name of the folder where a tracefile of
517 performance results will be stored.
518 platform_list: A list containing the platform(s) that our data has been
519 run on. (command line, firefox, chrome, etc)
520 variants: A list specifying whether we hold data about Frog
521 generated code, plain JS code, or a combination of both, or
522 Dart depending on the test.
523 values_list: A list containing the type of data we will be graphing
524 (benchmarks, percentage passing, etc).
525 test_runner: Reference to the parent test runner object that notifies a
526 test when to run.
527 tester: The visitor that actually performs the test running mechanics.
528 file_processor: The visitor that processes files in the format
529 appropriate for this test.
530 grapher: The visitor that generates graphs given our test result data.
531 extra_metrics: A list of any additional measurements we wish to keep
532 track of (such as the geometric mean of a set, the sum, etc).
533 build_targets: The targets necessary to build to run these tests
534 (default target is create_sdk)."""
535 super(RuntimePerformanceTest, self).__init__(result_folder_name,
536 platform_list, versions, benchmarks, test_runner, tester,
537 file_processor, RuntimePerformanceTest.RuntimePerfGrapher(self),
538 build_targets=build_targets)
395 self.platform_list = platform_list 539 self.platform_list = platform_list
396 self.platform_type = platform_type 540 self.platform_type = platform_type
397 self.versions = versions 541 self.versions = versions
398 self.benchmarks = benchmarks 542 self.benchmarks = benchmarks
399 543
400 def plot_all_perf(self, png_filename): 544 class RuntimePerfGrapher(Grapher):
401 """Create a plot that shows the performance changes of individual benchmarks 545 def plot_all_perf(self, png_filename):
402 run by JS and generated by frog, over svn history.""" 546 """Create a plot that shows the performance changes of individual
403 for benchmark in self.benchmarks: 547 benchmarks run by JS and generated by frog, over svn history."""
404 self.style_and_save_perf_plot( 548 for benchmark in self.test.benchmarks:
405 'Performance of %s over time on the %s on %s' % (benchmark, 549 self.style_and_save_perf_plot(
406 self.platform_type, utils.GuessOS()), 'Speed (bigger = better)', 16, 550 'Performance of %s over time on the %s on %s' % (benchmark,
407 14, 'lower left', benchmark + png_filename, self.platform_list, 551 self.test.platform_type, utils.GuessOS()),
408 self.versions, [benchmark]) 552 'Speed (bigger = better)', 16, 14, 'lower left',
409 553 benchmark + png_filename, self.test.platform_list,
410 def plot_avg_perf(self, png_filename): 554 self.test.versions, [benchmark])
411 """Generate a plot that shows the performance changes of the geomentric mean 555
412 of JS and frog benchmark performance over svn history.""" 556 def plot_avg_perf(self, png_filename):
413 (title, y_axis, size_x, size_y, loc, filename) = \ 557 """Generate a plot that shows the performance changes of the geomentric
414 ('Geometric Mean of benchmark %s performance on %s ' % 558 mean of JS and frog benchmark performance over svn history."""
415 (self.platform_type, utils.GuessOS()), 'Speed (bigger = better)', 16, 5, 559 (title, y_axis, size_x, size_y, loc, filename) = \
416 'lower left', 'avg'+png_filename) 560 ('Geometric Mean of benchmark %s performance on %s ' %
417 clear_axis = True 561 (self.test.platform_type, utils.GuessOS()), 'Speed (bigger = better)',
418 for platform in self.platform_list: 562 16, 5, 'lower left', 'avg'+png_filename)
419 for version in self.versions: 563 clear_axis = True
420 self.style_and_save_perf_plot(title, y_axis, size_x, size_y, loc, 564 for platform in self.test.platform_list:
421 filename, [platform], [version], 565 for version in self.test.versions:
422 [GEO_MEAN], clear_axis) 566 for metric in self.test.extra_metrics:
423 clear_axis = False 567 self.style_and_save_perf_plot(title, y_axis, size_x, size_y, loc,
424 568 filename, [platform], [version],
425 def plot_results(self, png_filename): 569 [metric], clear_axis)
426 self.plot_all_perf(png_filename) 570
427 self.plot_avg_perf('2' + png_filename) 571 def plot_results(self, png_filename):
428 572 self.plot_all_perf(png_filename)
429 573 self.plot_avg_perf('2' + png_filename)
430 class CommandLinePerformanceTest(PerformanceTest): 574
431 """Run performance tests from the command line.""" 575
432 576 class CommonCommandLineTest(RuntimePerformanceTest):
433 def __init__(self): 577 """Run the basic performance tests (Benchpress, some V8 benchmarks) from the
434 super(CommandLinePerformanceTest, self).__init__( 578 command line."""
435 CL_PERF, [COMMAND_LINE], 'command line', 579
436 JS_AND_FROG, get_standalone_benchmarks()) 580 def __init__(self, test_runner):
437 581 """Args:
438 def process_file(self, afile): 582 test_runner: Reference to the object that notfies this test when to
439 """Pull all the relevant information out of a given tracefile. 583 run."""
440 584 super(CommonCommandLineTest, self).__init__(
441 Args: 585 self.name(), ['commandline'],
vsm 2012/04/09 21:50:18 You can replace all uses of CommonCommandLineTest.
Emily Fortuna 2012/04/09 22:21:45 Done.
442 afile: The filename string we will be processing.""" 586 'command line', ['js', 'frog'],
443 os.chdir(os.path.join(DART_INSTALL_LOCATION, 'tools', 'testing', 587 CommonCommandLineTest.get_standalone_benchmarks(),
444 'perf_testing')) 588 test_runner,
445 f = open(os.path.join(self.result_folder_name, afile)) 589 CommonCommandLineTest.CommonCommandLineTester(self),
446 tabulate_data = False 590 CommonCommandLineTest
447 revision_num = 0 591 .CommonCommandLineFileProcessor(self),
448 for line in f.readlines(): 592 build_targets=['create_sdk', 'dart2js'])
449 if 'Revision' in line: 593
450 revision_num = int(line.split()[1]) 594 @staticmethod
451 elif 'Benchmark' in line: 595 def name():
452 tabulate_data = True 596 return 'cl-perf'
453 elif tabulate_data: 597
454 tokens = line.split() 598 @staticmethod
455 if len(tokens) < 4 or tokens[0] not in self.benchmarks: 599 def get_standalone_benchmarks():
456 #Done tabulating data. 600 return ['Mandelbrot', 'DeltaBlue', 'Richards', 'NBody', 'BinaryTrees',
457 break 601 'Fannkuch', 'Meteor', 'BubbleSort', 'Fibonacci', 'Loop', 'Permute',
458 js_value = float(tokens[1]) 602 'Queens', 'QuickSort', 'Recurse', 'Sieve', 'Sum', 'Tak', 'Takl', 'Towers',
459 frog_value = float(tokens[3]) 603 'TreeSort']
460 if js_value == 0 or frog_value == 0: 604
461 #Then there was an error when this performance test was run. Do not 605 class CommonCommandLineTester(Tester):
462 #count it in our numbers. 606 def run_tests(self):
463 return 607 """Run a performance test on our updated system."""
464 benchmark = tokens[0] 608 os.chdir('frog')
465 self.revision_dict[COMMAND_LINE][JS][benchmark] += [revision_num] 609 self.test.trace_file = os.path.join(
466 self.values_dict[COMMAND_LINE][JS][benchmark] += [js_value] 610 '..', 'tools', 'testing', 'perf_testing',
467 self.revision_dict[COMMAND_LINE][FROG][benchmark] += [revision_num] 611 self.test.result_folder_name, 'result' + self.test.cur_time)
468 self.values_dict[COMMAND_LINE][FROG][benchmark] += [frog_value] 612 self.test.test_runner.run_cmd(['python', os.path.join('benchmarks',
469 f.close() 613 'perf_tests.py')], self.test.trace_file)
470 614 os.chdir('..')
471 self.calculate_geometric_mean(COMMAND_LINE, FROG, revision_num) 615
472 self.calculate_geometric_mean(COMMAND_LINE, JS, revision_num) 616 class CommonCommandLineFileProcessor(Processor):
473 617 def process_file(self, afile):
474 def run_tests(self): 618 """Pull all the relevant information out of a given tracefile.
475 """Run a performance test on our updated system.""" 619
476 os.chdir('frog') 620 Args:
477 self.trace_file = os.path.join('..', 'tools', 'testing', 'perf_testing', 621 afile: The filename string we will be processing."""
478 self.result_folder_name, 'result' + self.cur_time) 622 os.chdir(os.path.join(DART_INSTALL_LOCATION, 'tools',
479 run_cmd(['python', os.path.join('benchmarks', 'perf_tests.py')], 623 'testing', 'perf_testing'))
480 self.trace_file) 624 f = open(os.path.join(self.test.result_folder_name, afile))
481 os.chdir('..') 625 tabulate_data = False
482 626 revision_num = 0
483 627 for line in f.readlines():
484 class BrowserStandalonePerformanceTest(PerformanceTest): 628 if 'Revision' in line:
485 """Runs standalone performance tests, in the browser.""" 629 revision_num = int(line.split()[1])
486 630 elif 'Benchmark' in line:
487 def __init__(self): 631 tabulate_data = True
488 super(BrowserStandalonePerformanceTest, self).__init__( 632 elif tabulate_data:
489 BROWSER_PERF, get_browsers(), 'browser', 633 tokens = line.split()
490 JS_AND_FROG, get_standalone_benchmarks()) 634 if len(tokens) < 4 or tokens[0] not in self.test.benchmarks:
491 635 #Done tabulating data.
492 def run_tests(self): 636 break
493 """Run a performance test in the browser.""" 637 js_value = float(tokens[1])
494 638 frog_value = float(tokens[3])
495 os.chdir('frog') 639 if js_value == 0 or frog_value == 0:
496 run_cmd(['python', os.path.join('benchmarks', 'make_web_benchmarks.py')]) 640 #Then there was an error when this performance test was run. Do not
497 os.chdir('..') 641 #count it in our numbers.
498 642 return
499 for browser in get_browsers(): 643 benchmark = tokens[0]
500 for version in self.versions: 644 self.test.revision_dict['commandline']['js'][benchmark] += \
501 self.trace_file = os.path.join('tools', 'testing', 'perf_testing', 645 [revision_num]
502 self.result_folder_name, 646 self.test.values_dict['commandline']['js'][benchmark] += [js_value]
503 'perf-%s-%s-%s' % (self.cur_time, browser, version)) 647 self.test.revision_dict['commandline']['frog'][benchmark] += \
504 self.add_svn_revision_to_trace(self.trace_file) 648 [revision_num]
505 file_path = os.path.join(os.getcwd(), 'internal', 'browserBenchmarks', 649 self.test.values_dict['commandline']['frog'][benchmark] += \
506 'benchmark_page_%s.html' % version) 650 [frog_value]
507 run_cmd(['python', os.path.join('tools', 'testing', 'run_selenium.py'), 651 f.close()
508 '--out', file_path, '--browser', browser, 652
509 '--timeout', '600', '--mode', 'perf'], self.trace_file, append=True) 653 self.calculate_geometric_mean('commandline', 'frog', revision_num)
510 654 self.calculate_geometric_mean('commandline', 'js', revision_num)
511 def process_file(self, afile): 655
512 """Comb through the html to find the performance results.""" 656
513 os.chdir(os.path.join(DART_INSTALL_LOCATION, 'tools', 'testing', 657 class BrowserTester(Tester):
514 'perf_testing')) 658 # TODO(vsm): Add Dartium.
515 parts = afile.split('-') 659 @staticmethod
516 browser = parts[2] 660 def get_browsers():
517 version = parts[3] 661 browsers = ['ff', 'chrome']
518 f = open(os.path.join(self.result_folder_name, afile)) 662 if platform.system() == 'Darwin':
519 lines = f.readlines() 663 browsers += ['safari']
520 line = '' 664 if platform.system() == 'Windows':
521 i = 0 665 browsers += ['ie']
522 revision_num = 0 666 return browsers
523 while '<div id="results">' not in line and i < len(lines): 667
524 if 'Revision' in line: 668
525 revision_num = int(line.split()[1].strip('"')) 669 class CommonBrowserTest(RuntimePerformanceTest):
670 """Runs this basic performance tests (Benchpress, some V8 benchmarks) in the
671 browser."""
672
673 def __init__(self, test_runner):
674 """Args:
675 test_runner: Reference to the object that notifies us when to run."""
676 super(CommonBrowserTest, self).__init__(
677 self.name(), BrowserTester.get_browsers(),
678 'browser', ['js', 'frog'],
679 CommonBrowserTest.get_standalone_benchmarks(), test_runner,
680 CommonBrowserTest.CommonBrowserTester(self),
681 CommonBrowserTest.CommonBrowserFileProcessor(self))
682
683 @staticmethod
684 def name():
685 return 'browser-perf'
686
687 @staticmethod
688 def get_standalone_benchmarks():
689 return ['Mandelbrot', 'DeltaBlue', 'Richards', 'NBody', 'BinaryTrees',
690 'Fannkuch', 'Meteor', 'BubbleSort', 'Fibonacci', 'Loop', 'Permute',
691 'Queens', 'QuickSort', 'Recurse', 'Sieve', 'Sum', 'Tak', 'Takl', 'Towers',
692 'TreeSort']
693
694 class CommonBrowserTester(BrowserTester):
695 def run_tests(self):
696 """Run a performance test in the browser."""
697 os.chdir('frog')
698 self.test.test_runner.run_cmd(['python', os.path.join('benchmarks',
699 'make_web_benchmarks.py')])
700 os.chdir('..')
701
702 for browser in BrowserTester.get_browsers():
703 for version in self.test.versions:
704 self.test.trace_file = os.path.join(
705 'tools', 'testing', 'perf_testing', self.test.result_folder_name,
706 'perf-%s-%s-%s' % (self.test.cur_time, browser, version))
707 self.add_svn_revision_to_trace(self.test.trace_file)
708 file_path = os.path.join(
709 os.getcwd(), 'internal', 'browserBenchmarks',
710 'benchmark_page_%s.html' % version)
711 self.test.test_runner.run_cmd(
712 ['python', os.path.join('tools', 'testing', 'run_selenium.py'),
713 '--out', file_path, '--browser', browser,
714 '--timeout', '600', '--mode', 'perf'], self.test.trace_file,
715 append=True)
716
717 class CommonBrowserFileProcessor(Processor):
718 def process_file(self, afile):
719 """Comb through the html to find the performance results."""
720 os.chdir(os.path.join(DART_INSTALL_LOCATION, 'tools',
721 'testing', 'perf_testing'))
722 parts = afile.split('-')
723 browser = parts[2]
724 version = parts[3]
725 f = open(os.path.join(self.test.result_folder_name, afile))
726 lines = f.readlines()
727 line = ''
728 i = 0
729 revision_num = 0
730 while '<div id="results">' not in line and i < len(lines):
731 if 'Revision' in line:
732 revision_num = int(line.split()[1].strip('"'))
733 line = lines[i]
734 i += 1
735
736 if i >= len(lines) or revision_num == 0:
737 # Then this run did not complete. Ignore this tracefile.
738 return
739
526 line = lines[i] 740 line = lines[i]
527 i += 1 741 i += 1
528 742 results = []
529 if i >= len(lines) or revision_num == 0: 743 if line.find('<br>') > -1:
530 # Then this run did not complete. Ignore this tracefile. 744 results = line.split('<br>')
531 return
532
533 line = lines[i]
534 i += 1
535 results = []
536 if line.find('<br>') > -1:
537 results = line.split('<br>')
538 else:
539 results = line.split('<br />')
540 for result in results:
541 name_and_score = result.split(':')
542 if len(name_and_score) < 2:
543 break
544 name = name_and_score[0].strip()
545 score = name_and_score[1].strip()
546 if version == JS or version == 'v8':
547 version = JS
548 bench_dict = self.values_dict[browser][JS]
549 else: 745 else:
550 bench_dict = self.values_dict[browser][FROG] 746 results = line.split('<br />')
551 bench_dict[name] += [float(score)] 747 for result in results:
552 self.revision_dict[browser][version][name] += [revision_num] 748 name_and_score = result.split(':')
553 749 if len(name_and_score) < 2:
554 f.close() 750 break
555 self.calculate_geometric_mean(browser, version, revision_num) 751 name = name_and_score[0].strip()
556 752 score = name_and_score[1].strip()
557 753 if version == 'js' or version == 'v8':
558 # TODO(vsm): This should not be hardcoded here if possible. 754 version = 'js'
559 DROMAEO_BENCHMARKS = { 755 bench_dict = self.test.values_dict[browser]['js']
560 'attr': ('attributes', [ 756 else:
561 'getAttribute', 757 bench_dict = self.test.values_dict[browser]['frog']
562 'element.property', 758 bench_dict[name] += [float(score)]
563 'setAttribute', 759 self.test.revision_dict[browser][version][name] += [revision_num]
564 'element.property = value']), 760
565 'modify': ('modify', [ 761 f.close()
566 'createElement', 762 self.calculate_geometric_mean(browser, version, revision_num)
567 'createTextNode', 763
568 'innerHTML', 764 class DromaeoTester(Tester):
569 'cloneNode', 765 DROMAEO_BENCHMARKS = {
570 'appendChild', 766 'attr': ('attributes', [
571 'insertBefore']), 767 'getAttribute',
572 'query': ('query', [ 768 'element.property',
573 'getElementById', 769 'setAttribute',
574 'getElementById (not in document)', 770 'element.property = value']),
575 'getElementsByTagName(div)', 771 'modify': ('modify', [
576 'getElementsByTagName(p)', 772 'createElement',
577 'getElementsByTagName(a)', 773 'createTextNode',
578 'getElementsByTagName(*)', 774 'innerHTML',
579 'getElementsByTagName (not in document)', 775 'cloneNode',
580 'getElementsByName', 776 'appendChild',
581 'getElementsByName (not in document)']), 777 'insertBefore']),
582 'traverse': ('traverse', [ 778 'query': ('query', [
583 'firstChild', 779 'getElementById',
584 'lastChild', 780 'getElementById (not in document)',
585 'nextSibling', 781 'getElementsByTagName(div)',
586 'previousSibling', 782 'getElementsByTagName(p)',
587 'childNodes']) 783 'getElementsByTagName(a)',
588 } 784 'getElementsByTagName(*)',
589 785 'getElementsByTagName (not in document)',
590 # Use legal appengine filenames for benchmark names. 786 'getElementsByName',
591 def legalize_filename(str): 787 'getElementsByName (not in document)']),
592 remap = { 788 'traverse': ('traverse', [
593 ' ': '_', 789 'firstChild',
594 '(': '_', 790 'lastChild',
595 ')': '_', 791 'nextSibling',
596 '*': 'ALL', 792 'previousSibling',
597 '=': 'ASSIGN', 793 'childNodes'])
598 } 794 }
599 for (old, new) in remap.iteritems(): 795
600 str = str.replace(old, new) 796 # Use legal appengine filenames for benchmark names.
601 return str 797 @staticmethod
602 798 def legalize_filename(str):
603 # TODO(vsm): This is a hack to skip breaking tests. Triage this 799 remap = {
604 # failure properly. The modify suite fails on 32-bit chrome on 800 ' ': '_',
605 # the mac. 801 '(': '_',
606 def get_valid_dromaeo_tags(): 802 ')': '_',
607 tags = [tag for (tag, _) in DROMAEO_BENCHMARKS.values()] 803 '*': 'ALL',
608 if platform.system() == 'Darwin': 804 '=': 'ASSIGN',
609 tags.remove('modify') 805 }
610 return tags 806 for (old, new) in remap.iteritems():
611 807 str = str.replace(old, new)
612 def get_dromaeo_benchmarks(): 808 return str
613 valid = get_valid_dromaeo_tags() 809
614 benchmarks = reduce(lambda l1,l2: l1+l2, 810 # TODO(vsm): This is a hack to skip breaking tests. Triage this
615 [tests for (tag, tests) in 811 # failure properly. The modify suite fails on 32-bit chrome on
616 DROMAEO_BENCHMARKS.values() if tag in valid]) 812 # the mac.
617 return map(legalize_filename, benchmarks) 813 @staticmethod
618 814 def get_valid_dromaeo_tags():
619 def get_dromaeo_versions(): 815 tags = [tag for (tag, _) in DromaeoTester.DROMAEO_BENCHMARKS.values()]
620 return ['js', 'frog_dom', 'frog_html'] 816 if platform.system() == 'Darwin':
621 817 tags.remove('modify')
622 def get_dromaeo_url_query(version): 818 return tags
623 version = version.replace('_','&') 819
624 tags = get_valid_dromaeo_tags() 820 @staticmethod
625 return '|'.join([ '%s&%s' % (version, tag) for tag in tags]) 821 def get_dromaeo_benchmarks():
626 822 valid = DromaeoTester.get_valid_dromaeo_tags()
627 class DromaeoTest(PerformanceTest): 823 benchmarks = reduce(lambda l1,l2: l1+l2,
824 [tests for (tag, tests) in
825 DromaeoTester.DROMAEO_BENCHMARKS.values()
826 if tag in valid])
827 return map(DromaeoTester.legalize_filename, benchmarks)
828
829 @staticmethod
830 def get_dromaeo_versions():
831 return ['js', 'frog_dom', 'frog_html']
832
833
834 class DromaeoTest(RuntimePerformanceTest):
628 """Runs Dromaeo tests, in the browser.""" 835 """Runs Dromaeo tests, in the browser."""
629 def __init__(self): 836 def __init__(self, test_runner):
630 super(DromaeoTest, self).__init__( 837 super(DromaeoTest, self).__init__(
631 DROMAEO, get_browsers(), 'browser', 838 self.name(), BrowserTester.get_browsers(), 'browser',
632 get_dromaeo_versions(), get_dromaeo_benchmarks()) 839 DromaeoTester.get_dromaeo_versions(),
633 840 DromaeoTester.get_dromaeo_benchmarks(), test_runner,
634 def run_tests(self): 841 DromaeoTest.DromaeoPerfTester(self),
635 """Run dromaeo in the browser.""" 842 DromaeoTest.DromaeoFileProcessor(self))
636 843
637 # Build tests. 844 @staticmethod
638 dromaeo_path = os.path.join('samples', 'third_party', 'dromaeo') 845 def name():
639 current_path = os.getcwd() 846 return 'dromaeo'
640 os.chdir(dromaeo_path) 847
641 run_cmd(['python', 'generate_frog_tests.py']) 848 class DromaeoPerfTester(DromaeoTester):
642 os.chdir(current_path) 849 def run_tests(self):
643 850 """Run dromaeo in the browser."""
644 versions = get_dromaeo_versions() 851
645 852 # Build tests.
646 for browser in get_browsers(): 853 dromaeo_path = os.path.join('samples', 'third_party', 'dromaeo')
647 for version_name in versions: 854 current_path = os.getcwd()
648 version = get_dromaeo_url_query(version_name) 855 os.chdir(dromaeo_path)
649 self.trace_file = os.path.join('tools', 'testing', 'perf_testing', 856 self.test.test_runner.run_cmd(['python', 'generate_frog_tests.py'])
650 self.result_folder_name, 857 os.chdir(current_path)
651 'dromaeo-%s-%s-%s' % (self.cur_time, browser, version_name)) 858
652 self.add_svn_revision_to_trace(self.trace_file) 859 versions = DromaeoTester.get_dromaeo_versions()
653 file_path = os.path.join(os.getcwd(), dromaeo_path, 860
654 'index-js.html?%s' % version) 861 for browser in BrowserTester.get_browsers():
655 run_cmd(['python', os.path.join('tools', 'testing', 'run_selenium.py'), 862 for version_name in versions:
656 '--out', file_path, '--browser', browser, 863 version = DromaeoTest.DromaeoPerfTester.get_dromaeo_url_query(
657 '--timeout', '200', '--mode', 'dromaeo'], self.trace_file, 864 version_name)
658 append=True) 865 self.test.trace_file = os.path.join(
659 866 'tools', 'testing', 'perf_testing', self.test.result_folder_name,
660 def process_file(self, afile): 867 'dromaeo-%s-%s-%s' % (self.test.cur_time, browser, version_name))
661 """Comb through the html to find the performance results.""" 868 self.add_svn_revision_to_trace(self.test.trace_file)
662 parts = afile.split('-') 869 file_path = os.path.join(os.getcwd(), dromaeo_path,
663 browser = parts[2] 870 'index-js.html?%s' % version)
664 version = parts[3] 871 self.test.test_runner.run_cmd(
665 872 ['python', os.path.join('tools', 'testing', 'run_selenium.py'),
666 bench_dict = self.values_dict[browser][version] 873 '--out', file_path, '--browser', browser,
667 874 '--timeout', '600', '--mode', 'dromaeo'], self.test.trace_file,
668 f = open(os.path.join(self.result_folder_name, afile)) 875 append=True)
669 lines = f.readlines() 876
670 i = 0 877 @staticmethod
671 revision_num = 0 878 def get_dromaeo_url_query(version):
672 revision_pattern = r'Revision: (\d+)' 879 version = version.replace('_','&')
673 suite_pattern = r'<div class="result-item done">(.+?)</ol></div>' 880 tags = DromaeoTester.get_valid_dromaeo_tags()
674 result_pattern = r'<b>(.+?)</b>(.+?)<small> runs/s(.+)' 881 return '|'.join([ '%s&%s' % (version, tag) for tag in tags])
675 882
676 for line in lines: 883
677 rev = re.match(revision_pattern, line.strip()) 884 class DromaeoFileProcessor(Processor):
678 if rev: 885 def process_file(self, afile):
679 revision_num = int(rev.group(1)) 886 """Comb through the html to find the performance results."""
680 continue 887 parts = afile.split('-')
681 888 browser = parts[2]
682 suite_results = re.findall(suite_pattern, line) 889 version = parts[3]
683 if suite_results: 890
684 for suite_result in suite_results: 891 bench_dict = self.test.values_dict[browser][version]
685 results = re.findall(r'<li>(.*?)</li>', suite_result) 892
686 if results: 893 f = open(os.path.join(self.test.result_folder_name, afile))
687 for result in results: 894 lines = f.readlines()
688 r = re.match(result_pattern, result) 895 i = 0
689 name = legalize_filename(r.group(1).strip(':')) 896 revision_num = 0
690 score = float(r.group(2)) 897 revision_pattern = r'Revision: (\d+)'
691 bench_dict[name] += [float(score)] 898 suite_pattern = r'<div class="result-item done">(.+?)</ol></div>'
692 self.revision_dict[browser][version][name] += [revision_num] 899 result_pattern = r'<b>(.+?)</b>(.+?)<small> runs/s(.+)'
693 900
694 f.close() 901 for line in lines:
695 self.calculate_geometric_mean(browser, version, revision_num) 902 rev = re.match(revision_pattern, line.strip())
696 903 if rev:
697 904 revision_num = int(rev.group(1))
698 class DromaeoSizeTest(TestRunner): 905 continue
906
907 suite_results = re.findall(suite_pattern, line)
908 if suite_results:
909 for suite_result in suite_results:
910 results = re.findall(r'<li>(.*?)</li>', suite_result)
911 if results:
912 for result in results:
913 r = re.match(result_pattern, result)
914 name = DromaeoTester.legalize_filename(
915 r.group(1).strip(':'))
916 score = float(r.group(2))
917 bench_dict[name] += [float(score)]
918 self.test.revision_dict[browser][version][name] += \
919 [revision_num]
920
921 f.close()
922 self.calculate_geometric_mean(browser, version, revision_num)
923
924
925 class DromaeoSizeTest(Test):
699 """Run tests to determine the compiled file output size of Dromaeo.""" 926 """Run tests to determine the compiled file output size of Dromaeo."""
700 def __init__(self): 927 def __init__(self, test_runner):
701 super(DromaeoSizeTest, self).__init__( 928 super(DromaeoSizeTest, self).__init__(
702 DROMAEO_SIZE, 929 self.name(),
703 ['browser'], ['dart', 'frog_dom', 'frog_html', 'frog_htmlidiomatic'], 930 ['browser'], ['dart', 'frog_dom', 'frog_html', 'frog_htmlidiomatic'],
704 DROMAEO_BENCHMARKS.keys()) 931 DromaeoTester.DROMAEO_BENCHMARKS.keys(), test_runner,
705 932 DromaeoSizeTest.DromaeoSizeTester(self),
706 def run_tests(self): 933 DromaeoSizeTest.DromaeoSizeProcessor(self),
707 # Build tests. 934 DromaeoSizeTest.DromaeoSizeGrapher(self), extra_metrics=['sum'])
708 dromaeo_path = os.path.join('samples', 'third_party', 'dromaeo') 935
709 current_path = os.getcwd() 936 @staticmethod
710 os.chdir(dromaeo_path) 937 def name():
711 run_cmd(['python', os.path.join('generate_frog_tests.py')]) 938 return 'dromaeo-size'
712 os.chdir(current_path) 939
713 940
714 self.trace_file = os.path.join('tools', 'testing', 'perf_testing', 941 class DromaeoSizeTester(DromaeoTester):
715 self.result_folder_name, self.result_folder_name + self.cur_time) 942 def run_tests(self):
716 self.add_svn_revision_to_trace(self.trace_file) 943 # Build tests.
717 944 dromaeo_path = os.path.join('samples', 'third_party', 'dromaeo')
718 variants = [ 945 current_path = os.getcwd()
719 ('frog_dom', ''), 946 os.chdir(dromaeo_path)
720 ('frog_html', '-html'), 947 self.test.test_runner.run_cmd(
721 ('frog_htmlidiomatic', '-htmlidiomatic')] 948 ['python', os.path.join('generate_frog_tests.py')])
722 949 os.chdir(current_path)
723 test_path = os.path.join(dromaeo_path, 'tests') 950
724 frog_path = os.path.join(test_path, 'frog') 951 self.test.trace_file = os.path.join(
725 total_size = {} 952 'tools', 'testing', 'perf_testing', self.test.result_folder_name,
726 for (variant, _) in variants: 953 self.test.result_folder_name + self.test.cur_time)
727 total_size[variant] = 0 954 self.add_svn_revision_to_trace(self.test.trace_file)
728 total_dart_size = 0 955
729 for suite in DROMAEO_BENCHMARKS.keys(): 956 variants = [
730 dart_size = 0 957 ('frog_dom', ''),
958 ('frog_html', '-html'),
959 ('frog_htmlidiomatic', '-htmlidiomatic')]
960
961 test_path = os.path.join(dromaeo_path, 'tests')
962 frog_path = os.path.join(test_path, 'frog')
963 total_size = {}
964 for (variant, _) in variants:
965 total_size[variant] = 0
966 total_dart_size = 0
967 for suite in DromaeoTester.DROMAEO_BENCHMARKS.keys():
968 dart_size = 0
969 try:
970 dart_size = os.path.getsize(os.path.join(test_path,
971 'dom-%s.dart' % suite))
972 except OSError:
973 pass #If compilation failed, continue on running other tests.
974
975 total_dart_size += dart_size
976 self.test.test_runner.run_cmd(
977 ['echo', 'Size (dart, %s): %s' % (suite, str(dart_size))],
978 self.test.trace_file, append=True)
979
980 for (variant, suffix) in variants:
981 name = 'dom-%s%s.dart.js' % (suite, suffix)
982 js_size = 0
983 try:
984 # TODO(vsm): Strip comments at least. Consider compression.
985 js_size = os.path.getsize(os.path.join(frog_path, name))
986 except OSError:
987 pass #If compilation failed, continue on running other tests.
988
989 total_size[variant] += js_size
990 self.test.test_runner.run_cmd(
991 ['echo', 'Size (%s, %s): %s' % (variant, suite, str(js_size))],
992 self.test.trace_file, append=True)
993
994 self.test.test_runner.run_cmd(
995 ['echo', 'Size (dart, %s): %s' % (total_dart_size,
996 self.test.extra_metrics[0])],
997 self.test.trace_file, append=True)
998 for (variant, _) in variants:
999 self.test.test_runner.run_cmd(
1000 ['echo', 'Size (%s, %s): %s' % (variant, self.test.extra_metrics[0],
1001 total_size[variant])],
1002 self.test.trace_file, append=True)
1003
1004 class DromaeoSizeProcessor(Processor):
1005 def process_file(self, afile):
1006 """Pull all the relevant information out of a given tracefile.
1007
1008 Args:
1009 afile: is the filename string we will be processing."""
1010 os.chdir(os.path.join(DART_INSTALL_LOCATION, 'tools',
1011 'testing', 'perf_testing'))
1012 f = open(os.path.join(self.test.result_folder_name, afile))
1013 tabulate_data = False
1014 revision_num = 0
1015 revision_pattern = r'Revision: (\d+)'
1016 result_pattern = r'Size \((\w+), ([a-zA-Z0-9-]+)\): (\d+)'
1017
1018 for line in f.readlines():
1019 rev = re.match(revision_pattern, line.strip())
1020 if rev:
1021 revision_num = int(rev.group(1))
1022 continue
1023
1024 result = re.match(result_pattern, line.strip())
1025 if result:
1026 variant = result.group(1)
1027 metric = result.group(2)
1028 num = result.group(3)
1029 if num.find('.') == -1:
1030 num = int(num)
1031 else:
1032 num = float(num)
1033 self.test.values_dict['browser'][variant][metric] += [num]
1034 self.test.revision_dict['browser'][variant][metric] += [revision_num]
1035
1036 f.close()
1037 class DromaeoSizeGrapher(Grapher):
1038 def plot_results(self, png_filename):
1039 self.style_and_save_perf_plot(
1040 'Compiled Dromaeo Sizes',
1041 'Size (in bytes)', 10, 10, 'lower left', png_filename,
1042 ['browser'], ['dart', 'frog_dom', 'frog_html', 'frog_htmlidiomatic'],
1043 DromaeoTester.DROMAEO_BENCHMARKS.keys())
1044
1045 self.style_and_save_perf_plot(
1046 'Compiled Dromaeo Sizes',
1047 'Size (in bytes)', 10, 10, 'lower left', '2' + png_filename,
1048 ['browser'], ['dart', 'frog_dom', 'frog_html', 'frog_htmlidiomatic'],
1049 [self.test.extra_metrics[0]])
1050
1051
1052 class CompileTimeAndSizeTest(Test):
1053 """Run tests to determine how long minfrog takes to compile, and the compiled
1054 file output size of some benchmarking files."""
1055 def __init__(self, test_runner):
1056 """Reference to the test_runner object that notifies us when to begin
1057 testing."""
1058 super(CompileTimeAndSizeTest, self).__init__(
1059 self.name(), ['commandline'], ['frog'],
1060 ['Compiling on Dart VM', 'Bootstrapping', 'minfrog', 'swarm', 'total'],
1061 test_runner, CompileTimeAndSizeTest.CompileTester(self),
1062 CompileTimeAndSizeTest.CompileProcessor(self),
1063 CompileTimeAndSizeTest.CompileGrapher(self))
1064 self.dart_compiler = os.path.join(
1065 DART_INSTALL_LOCATION, utils.GetBuildRoot(utils.GuessOS(),
1066 'release', 'ia32'), 'dart-sdk', 'bin', 'frogc')
1067 _suffix = ''
1068 if platform.system() == 'Windows':
1069 _suffix = '.exe'
1070 self.dart_vm = os.path.join(
1071 DART_INSTALL_LOCATION, utils.GetBuildRoot(utils.GuessOS(),
1072 'release', 'ia32'), 'dart-sdk', 'bin','dart' + _suffix)
1073 self.failure_threshold = {
1074 'Compiling on Dart VM' : 1, 'Bootstrapping' : .5, 'minfrog' : 100,
1075 'swarm' : 100, 'total' : 100}
1076
1077 @staticmethod
1078 def name():
1079 return 'time-size'
1080
1081 class CompileTester(Tester):
1082 def run_tests(self):
1083 os.chdir('frog')
1084 self.test.trace_file = os.path.join(
1085 '..', 'tools', 'testing', 'perf_testing',
1086 self.test.result_folder_name,
1087 self.test.result_folder_name + self.test.cur_time)
1088
1089 self.add_svn_revision_to_trace(self.test.trace_file)
1090
1091 elapsed = self.test.test_runner.time_cmd(
1092 [self.test.dart_vm, os.path.join('.', 'minfrogc.dart'),
1093 '--out=minfrog', 'minfrog.dart'])
1094 self.test.test_runner.run_cmd(
1095 ['echo', '%f Compiling on Dart VM in production mode in seconds'
1096 % elapsed], self.test.trace_file, append=True)
1097 elapsed = self.test.test_runner.time_cmd(
1098 [os.path.join('.', 'minfrog'), '--out=minfrog', 'minfrog.dart',
1099 os.path.join('tests', 'hello.dart')])
1100 if elapsed < self.test.failure_threshold['Bootstrapping']:
1101 #minfrog didn't compile correctly. Stop testing now, because subsequent
1102 #numbers will be meaningless.
1103 return
1104 size = os.path.getsize('minfrog')
1105 self.test.test_runner.run_cmd(
1106 ['echo', '%f Bootstrapping time in seconds in production mode' %
1107 elapsed], self.test.trace_file, append=True)
1108 self.test.test_runner.run_cmd(
1109 ['echo', '%d Generated checked minfrog size' % size],
1110 self.test.trace_file, append=True)
1111
1112 self.test.test_runner.run_cmd(
1113 [self.test.dart_compiler, '--out=swarm-result',
1114 os.path.join('..', 'samples', 'swarm',
1115 'swarm.dart')])
1116
1117 swarm_size = 0
731 try: 1118 try:
732 dart_size = os.path.getsize(os.path.join(test_path, 1119 swarm_size = os.path.getsize('swarm-result')
733 'dom-%s.dart' % suite))
734 except OSError: 1120 except OSError:
735 pass #If compilation failed, continue on running other tests. 1121 pass #If compilation failed, continue on running other tests.
736 1122
737 total_dart_size += dart_size 1123 self.test.test_runner.run_cmd(
738 run_cmd(['echo', 'Size (dart, %s): %s' % (suite, str(dart_size))], 1124 [self.test.dart_compiler, '--out=total-result',
739 self.trace_file, append=True) 1125 os.path.join('..', 'samples', 'total',
740 1126 'client', 'Total.dart')])
741 for (variant, suffix) in variants: 1127 total_size = 0
742 name = 'dom-%s%s.dart.js' % (suite, suffix) 1128 try:
743 js_size = 0 1129 total_size = os.path.getsize('total-result')
744 try: 1130 except OSError:
745 # TODO(vsm): Strip comments at least. Consider compression. 1131 pass #If compilation failed, continue on running other tests.
746 js_size = os.path.getsize(os.path.join(frog_path, name)) 1132
747 except OSError: 1133 self.test.test_runner.run_cmd(
748 pass #If compilation failed, continue on running other tests. 1134 ['echo', '%d Generated checked swarm size' % swarm_size],
749 1135 self.test.trace_file, append=True)
750 total_size[variant] += js_size 1136
751 run_cmd(['echo', 'Size (%s, %s): %s' % (variant, suite, 1137 self.test.test_runner.run_cmd(
752 str(js_size))], 1138 ['echo', '%d Generated checked total size' % total_size],
753 self.trace_file, append=True) 1139 self.test.trace_file, append=True)
754 1140
755 # TODO(vsm): Change GEO_MEAN to sum. The base class assumes 1141 #Revert our newly built minfrog to prevent conflicts when we update
756 # GEO_MEAN right now. 1142 self.test.test_runner.run_cmd(
757 run_cmd(['echo', 'Size (dart, %s): %s' % (total_dart_size, GEO_MEAN)], 1143 ['svn', 'revert', os.path.join(os.getcwd(), 'frog', 'minfrog')])
758 self.trace_file, append=True) 1144
759 for (variant, _) in variants: 1145 os.chdir('..')
760 run_cmd(['echo', 'Size (%s, %s): %s' % (variant, GEO_MEAN, 1146
761 total_size[variant])], 1147 class CompileProcessor(Processor):
762 self.trace_file, append=True) 1148 def process_file(self, afile):
763 1149 """Pull all the relevant information out of a given tracefile.
764 1150
765 def process_file(self, afile): 1151 Args:
766 """Pull all the relevant information out of a given tracefile. 1152 afile: is the filename string we will be processing."""
767 1153 os.chdir(os.path.join(DART_INSTALL_LOCATION, 'tools',
768 Args: 1154 'testing', 'perf_testing'))
769 afile: is the filename string we will be processing.""" 1155 f = open(os.path.join(self.test.result_folder_name, afile))
770 os.chdir(os.path.join(DART_INSTALL_LOCATION, 'tools', 'testing', 1156 tabulate_data = False
771 'perf_testing')) 1157 revision_num = 0
772 f = open(os.path.join(self.result_folder_name, afile)) 1158 for line in f.readlines():
773 tabulate_data = False 1159 tokens = line.split()
774 revision_num = 0 1160 if 'Revision' in line:
775 revision_pattern = r'Revision: (\d+)' 1161 revision_num = int(line.split()[1])
776 result_pattern = r'Size \((\w+), ([a-zA-Z0-9-]+)\): (\d+)'
777
778 for line in f.readlines():
779 rev = re.match(revision_pattern, line.strip())
780 if rev:
781 revision_num = int(rev.group(1))
782 continue
783
784 result = re.match(result_pattern, line.strip())
785 if result:
786 variant = result.group(1)
787 metric = result.group(2)
788 num = result.group(3)
789 if num.find('.') == -1:
790 num = int(num)
791 else: 1162 else:
792 num = float(num) 1163 for metric in self.test.values_list:
793 self.values_dict['browser'][variant][metric] += [num] 1164 if metric in line:
794 self.revision_dict['browser'][variant][metric] += [revision_num] 1165 num = tokens[0]
795 1166 if num.find('.') == -1:
796 f.close() 1167 num = int(num)
797 1168 else:
798 def plot_results(self, png_filename): 1169 num = float(num)
799 self.style_and_save_perf_plot( 1170 self.test.values_dict['commandline']['frog'][metric] += [num]
800 'Compiled Dromaeo Sizes', 1171 self.test.revision_dict['commandline']['frog'][metric] += \
801 'Size (in bytes)', 10, 10, 'lower left', png_filename, 1172 [revision_num]
802 ['browser'], ['dart', 'frog_dom', 'frog_html', 'frog_htmlidiomatic'], 1173
803 DROMAEO_BENCHMARKS.keys()) 1174 if revision_num != 0:
804 1175 for metric in self.test.values_list:
805 self.style_and_save_perf_plot( 1176 self.test.revision_dict['commandline']['frog'][metric].pop()
806 'Compiled Dromaeo Sizes', 1177 self.test.revision_dict['commandline']['frog'][metric] += \
807 'Size (in bytes)', 10, 10, 'lower left', '2' + png_filename, 1178 [revision_num]
808 ['browser'], ['dart', 'frog_dom', 'frog_html', 'frog_htmlidiomatic'], 1179 # Fill in 0 if compilation failed.
809 [GEO_MEAN]) 1180 if self.test.values_dict['commandline']['frog'][metric][-1] < \
810 1181 self.test.failure_threshold[metric]:
811 1182 self.test.values_dict['commandline']['frog'][metric] += [0]
812 1183 self.test.revision_dict['commandline']['frog'][metric] += \
813 class CompileTimeAndSizeTest(TestRunner): 1184 [revision_num]
814 """Run tests to determine how long minfrog takes to compile, and the compiled 1185
815 file output size of some benchmarking files.""" 1186 f.close()
816 def __init__(self): 1187
817 super(CompileTimeAndSizeTest, self).__init__(TIME_SIZE, 1188 class CompileGrapher(Grapher):
818 [COMMAND_LINE], [FROG], ['Compiling on Dart VM', 'Bootstrapping', 1189
819 'minfrog', 'swarm', 'total']) 1190 def plot_results(self, png_filename):
820 self.failure_threshold = {'Compiling on Dart VM' : 1, 'Bootstrapping' : .5, 1191 self.style_and_save_perf_plot(
821 'minfrog' : 100, 'swarm' : 100, 'total' : 100} 1192 'Compiled minfrog Sizes', 'Size (in bytes)', 10, 10, 'lower left',
822 1193 png_filename, ['commandline'], ['frog'],
823 def run_tests(self): 1194 ['swarm', 'total', 'minfrog'])
824 os.chdir('frog') 1195
825 self.trace_file = os.path.join('..', 'tools', 'testing', 'perf_testing', 1196 self.style_and_save_perf_plot(
826 self.result_folder_name, self.result_folder_name + self.cur_time) 1197 'Time to compile and bootstrap',
827 1198 'Seconds', 10, 10, 'lower left', '2' + png_filename, ['commandline'],
828 self.add_svn_revision_to_trace(self.trace_file) 1199 ['frog'], ['Bootstrapping', 'Compiling on Dart VM'])
829 1200
830 elapsed = time_cmd([DART_VM, os.path.join('.', 'minfrogc.dart'), 1201
831 '--out=minfrog', 'minfrog.dart']) 1202 class TestBuilder(object):
832 run_cmd(['echo', '%f Compiling on Dart VM in production mode in seconds' 1203 """Construct the desired test object."""
833 % elapsed], self.trace_file, append=True) 1204 available_suites = dict((suite.name(), suite) for suite in [
834 elapsed = time_cmd([os.path.join('.', 'minfrog'), '--out=minfrog', 1205 CommonCommandLineTest, CompileTimeAndSizeTest,
835 'minfrog.dart', os.path.join('tests', 'hello.dart')]) 1206 CommonBrowserTest, DromaeoTest, DromaeoSizeTest])
836 if elapsed < self.failure_threshold['Bootstrapping']: 1207
837 #minfrog didn't compile correctly. Stop testing now, because subsequent 1208 @staticmethod
838 #numbers will be meaningless. 1209 def make_test(test_name, test_runner):
839 return 1210 return TestBuilder.available_suites[test_name](test_runner)
840 size = os.path.getsize('minfrog') 1211
841 run_cmd(['echo', '%f Bootstrapping time in seconds in production mode' % 1212 @staticmethod
842 elapsed], self.trace_file, append=True) 1213 def available_suite_names():
843 run_cmd(['echo', '%d Generated checked minfrog size' % size], 1214 return TestBuilder.available_suites.keys()
844 self.trace_file, append=True) 1215
845
846 run_cmd([DART_COMPILER, '--out=swarm-result',
847 os.path.join('..', 'samples', 'swarm',
848 'swarm.dart')])
849 swarm_size = 0
850 try:
851 swarm_size = os.path.getsize('swarm-result')
852 except OSError:
853 pass #If compilation failed, continue on running other tests.
854
855 run_cmd([DART_COMPILER, '--out=total-result',
856 os.path.join('..', 'samples', 'total',
857 'client', 'Total.dart')])
858 total_size = 0
859 try:
860 total_size = os.path.getsize('total-result')
861 except OSError:
862 pass #If compilation failed, continue on running other tests.
863
864 run_cmd(['echo', '%d Generated checked swarm size' % swarm_size],
865 self.trace_file, append=True)
866
867 run_cmd(['echo', '%d Generated checked total size' % total_size],
868 self.trace_file, append=True)
869 os.chdir('..')
870
871 def process_file(self, afile):
872 """Pull all the relevant information out of a given tracefile.
873
874 Args:
875 afile: is the filename string we will be processing."""
876 os.chdir(os.path.join(DART_INSTALL_LOCATION, 'tools', 'testing',
877 'perf_testing'))
878 f = open(os.path.join(self.result_folder_name, afile))
879 tabulate_data = False
880 revision_num = 0
881 for line in f.readlines():
882 tokens = line.split()
883 if 'Revision' in line:
884 revision_num = int(line.split()[1])
885 else:
886 for metric in self.values_list:
887 if metric in line:
888 num = tokens[0]
889 if num.find('.') == -1:
890 num = int(num)
891 else:
892 num = float(num)
893 self.values_dict[COMMAND_LINE][FROG][metric] += [num]
894 self.revision_dict[COMMAND_LINE][FROG][metric] += [revision_num]
895
896 if revision_num != 0:
897 for metric in self.values_list:
898 self.revision_dict[COMMAND_LINE][FROG][metric].pop()
899 self.revision_dict[COMMAND_LINE][FROG][metric] += [revision_num]
900 # Fill in 0 if compilation failed.
901 if self.values_dict[COMMAND_LINE][FROG][metric][-1] < \
902 self.failure_threshold[metric]:
903 self.values_dict[COMMAND_LINE][FROG][metric] += [0]
904 self.revision_dict[COMMAND_LINE][FROG][metric] += [revision_num]
905
906 f.close()
907
908 def plot_results(self, png_filename):
909 self.style_and_save_perf_plot('Compiled minfrog Sizes',
910 'Size (in bytes)', 10, 10, 'lower left', png_filename, [COMMAND_LINE],
911 [FROG], ['swarm', 'total', 'minfrog'])
912
913 self.style_and_save_perf_plot('Time to compile and bootstrap',
914 'Seconds', 10, 10, 'lower left', '2' + png_filename, [COMMAND_LINE],
915 [FROG], ['Bootstrapping', 'Compiling on Dart VM'])
916
917 # TODO(vsm): Make these names consistent with BROWSER_PERF, CL_PERF,
918 # etc. above.
919 SUITES = {
920 CL_PERF: CommandLinePerformanceTest,
921 TIME_SIZE: CompileTimeAndSizeTest,
922 BROWSER_PERF: BrowserStandalonePerformanceTest,
923 DROMAEO: DromaeoTest,
924 DROMAEO_SIZE: DromaeoSizeTest,
925 }
926
927 def parse_args():
928 parser = optparse.OptionParser()
929 # TODO(vsm): Change to a list to scale.
930 parser.add_option('--suites', '-s', dest='suites',
931 help='Run the specified comma-separated test suites from set: %s' % \
932 ','.join(SUITES.keys()),
933 action='store', default=None)
934 parser.add_option('--forever', '-f', dest='continuous',
935 help='Run this script forever, always checking for the next svn '
936 'checkin', action='store_true', default=False)
937 parser.add_option('--graph-only', '-g', dest='graph_only', default=False,
938 help='Do not run tests, only regenerate graphs', action='store_true')
939 parser.add_option('--nobuild', '-n', dest='no_build', action='store_true',
940 help='Do not sync with the repository and do not rebuild.', default=False)
941 parser.add_option('--upload', '-u', dest='upload',
942 help='Upload data to app engine (will require authentication).',
943 action='store_true', default=False)
944 parser.add_option('--verbose', '-v', dest='verbose',
945 help='Print extra debug output', action='store_true', default=False)
946
947 args, ignored = parser.parse_args()
948
949 if not args.suites:
950 suites = SUITES.values()
951 else:
952 suites = []
953 suitelist = args.suites.split(',')
954 for name in suitelist:
955 if name in SUITES:
956 suites.append(SUITES[name])
957 else:
958 print 'Error: Invalid suite %s not in %s' % (name,
959 ','.join(SUITES.keys()))
960 sys.exit(1)
961 return (suites, args.continuous, args.verbose, args.no_build,
962 args.graph_only, args.upload)
963
964 def run_test_sequence(suites, no_build, graph_only, upload):
965 # The buildbot already builds and syncs to a specific revision. Don't fight
966 # with it or replicate work.
967 if not no_build and sync_and_build() == 1:
968 return # The build is broken.
969
970 for test in suites:
971 test().run(graph_only)
972
973 if upload:
974 upload_to_app_engine(SUITES.keys())
975 1216
976 def main(): 1217 def main():
977 global VERBOSE 1218 runner = TestRunner()
978 (suites, continuous, verbose, no_build, graph_only, upload) = parse_args() 1219 continuous = runner.parse_args()
979 VERBOSE = verbose
980 if continuous: 1220 if continuous:
981 while True: 1221 while True:
982 if has_new_code(): 1222 if runner.has_new_code():
983 run_test_sequence(suites, no_build, graph_only, upload) 1223 runner.run_test_sequence()
984 else: 1224 else:
985 time.sleep(SLEEP_TIME) 1225 time.sleep(200)
986 else: 1226 else:
987 run_test_sequence(suites, no_build, graph_only, upload) 1227 runner.run_test_sequence()
988 1228
989 if __name__ == '__main__': 1229 if __name__ == '__main__':
990 main() 1230 main()
OLDNEW
« no previous file with comments | « no previous file | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698