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

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 'blue', 'green', 'red', 'cyan', 'magenta', 'black', '#3366CC',
339 output, _ = p.communicate() 494 '#DC3912', '#FF9900', '#109618', '#990099', '#0099C6', '#DD4477',
340 for line in output.split('\n'): 495 '#66AA00', '#B82E2E', '#316395', '#994499', '#22AA99', '#AAAA11',
341 if 'Revision' in line: 496 '#6633CC', '#E67300', '#8B0707', '#651067', '#329262', '#5574A6',
342 run_cmd(['echo', line.strip()], outfile) 497 '#3B3EAC', '#B77322', '#16D620', '#B91383', '#F4359E', '#9C5935',
343 return True 498 '#A9C413', '#2A778D', '#668D1C', '#BEA413', '#0C5922', '#743411',
344 return False 499 '#45AFE2', '#FF3300', '#FFCC00', '#14C21D', '#DF51FD', '#15CBFF',
345 500 '#FF97D2', '#97FB00', '#DB6651', '#518BC6', '#BD6CBD', '#35D7C2',
346 if not search_for_revision(['svn', 'info']): 501 '#E9E91F', '#9877DD', '#FF8F20', '#D20B0B', '#B61DBA', '#40BD7E',
347 if not search_for_revision(['git', 'svn', 'info']): 502 '#6AA7C4', '#6D70CD', '#DA9136', '#2DEA36', '#E81EA6', '#F558AE',
348 run_cmd(['echo', 'Revision: unknown'], outfile) 503 '#C07145', '#D7EE53', '#3EA7C6', '#97D129', '#E9CA1D', '#149638',
349 504 '#C5571D']
350 def calculate_geometric_mean(self, platform, variant, svn_revision): 505 color = colors[self.color_index]
351 """Calculate the aggregate geometric mean for JS and frog benchmark sets, 506 self.color_index = (self.color_index + 1) % len(colors)
352 given two benchmark dictionaries.""" 507 return color
353 geo_mean = 0 508
354 for benchmark in self.values_list: 509
355 geo_mean += math.log(self.values_dict[platform][variant][benchmark][ 510 class RuntimePerformanceTest(Test):
356 len(self.values_dict[platform][variant][benchmark]) - 1]) 511 """Super class for all runtime performance testing."""
357 512
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, 513 def __init__(self, result_folder_name, platform_list, platform_type,
392 versions, benchmarks): 514 versions, benchmarks, test_runner, tester, file_processor,
393 super(PerformanceTest, self).__init__(result_folder_name, 515 build_targets=['create_sdk']):
394 platform_list, versions, benchmarks) 516 """Args:
517 result_folder_name: The name of the folder where a tracefile of
518 performance results will be stored.
519 platform_list: A list containing the platform(s) that our data has been
520 run on. (command line, firefox, chrome, etc)
521 variants: A list specifying whether we hold data about Frog
522 generated code, plain JS code, or a combination of both, or
523 Dart depending on the test.
524 values_list: A list containing the type of data we will be graphing
525 (benchmarks, percentage passing, etc).
526 test_runner: Reference to the parent test runner object that notifies a
527 test when to run.
528 tester: The visitor that actually performs the test running mechanics.
529 file_processor: The visitor that processes files in the format
530 appropriate for this test.
531 grapher: The visitor that generates graphs given our test result data.
532 extra_metrics: A list of any additional measurements we wish to keep
533 track of (such as the geometric mean of a set, the sum, etc).
534 build_targets: The targets necessary to build to run these tests
535 (default target is create_sdk)."""
536 super(RuntimePerformanceTest, self).__init__(result_folder_name,
537 platform_list, versions, benchmarks, test_runner, tester,
538 file_processor, self.RuntimePerfGrapher(self),
539 build_targets=build_targets)
395 self.platform_list = platform_list 540 self.platform_list = platform_list
396 self.platform_type = platform_type 541 self.platform_type = platform_type
397 self.versions = versions 542 self.versions = versions
398 self.benchmarks = benchmarks 543 self.benchmarks = benchmarks
399 544
400 def plot_all_perf(self, png_filename): 545 class RuntimePerfGrapher(Grapher):
401 """Create a plot that shows the performance changes of individual benchmarks 546 def plot_all_perf(self, png_filename):
402 run by JS and generated by frog, over svn history.""" 547 """Create a plot that shows the performance changes of individual
403 for benchmark in self.benchmarks: 548 benchmarks run by JS and generated by frog, over svn history."""
404 self.style_and_save_perf_plot( 549 for benchmark in self.test.benchmarks:
405 'Performance of %s over time on the %s on %s' % (benchmark, 550 self.style_and_save_perf_plot(
406 self.platform_type, utils.GuessOS()), 'Speed (bigger = better)', 16, 551 'Performance of %s over time on the %s on %s' % (benchmark,
407 14, 'lower left', benchmark + png_filename, self.platform_list, 552 self.test.platform_type, utils.GuessOS()),
408 self.versions, [benchmark]) 553 'Speed (bigger = better)', 16, 14, 'lower left',
409 554 benchmark + png_filename, self.test.platform_list,
410 def plot_avg_perf(self, png_filename): 555 self.test.versions, [benchmark])
411 """Generate a plot that shows the performance changes of the geomentric mean 556
412 of JS and frog benchmark performance over svn history.""" 557 def plot_avg_perf(self, png_filename):
413 (title, y_axis, size_x, size_y, loc, filename) = \ 558 """Generate a plot that shows the performance changes of the geomentric
414 ('Geometric Mean of benchmark %s performance on %s ' % 559 mean of JS and frog benchmark performance over svn history."""
415 (self.platform_type, utils.GuessOS()), 'Speed (bigger = better)', 16, 5, 560 (title, y_axis, size_x, size_y, loc, filename) = \
416 'lower left', 'avg'+png_filename) 561 ('Geometric Mean of benchmark %s performance on %s ' %
417 clear_axis = True 562 (self.test.platform_type, utils.GuessOS()), 'Speed (bigger = better)',
418 for platform in self.platform_list: 563 16, 5, 'lower left', 'avg'+png_filename)
419 for version in self.versions: 564 clear_axis = True
420 self.style_and_save_perf_plot(title, y_axis, size_x, size_y, loc, 565 for platform in self.test.platform_list:
421 filename, [platform], [version], 566 for version in self.test.versions:
422 [GEO_MEAN], clear_axis) 567 for metric in self.test.extra_metrics:
423 clear_axis = False 568 self.style_and_save_perf_plot(title, y_axis, size_x, size_y, loc,
424 569 filename, [platform], [version],
425 def plot_results(self, png_filename): 570 [metric], clear_axis)
426 self.plot_all_perf(png_filename) 571
427 self.plot_avg_perf('2' + png_filename) 572 def plot_results(self, png_filename):
428 573 self.plot_all_perf(png_filename)
429 574 self.plot_avg_perf('2' + png_filename)
430 class CommandLinePerformanceTest(PerformanceTest): 575
431 """Run performance tests from the command line.""" 576
432 577 class CommonCommandLineTest(RuntimePerformanceTest):
433 def __init__(self): 578 """Run the basic performance tests (Benchpress, some V8 benchmarks) from the
434 super(CommandLinePerformanceTest, self).__init__( 579 command line."""
435 CL_PERF, [COMMAND_LINE], 'command line', 580
436 JS_AND_FROG, get_standalone_benchmarks()) 581 def __init__(self, test_runner):
437 582 """Args:
438 def process_file(self, afile): 583 test_runner: Reference to the object that notfies this test when to
439 """Pull all the relevant information out of a given tracefile. 584 run."""
440 585 super(CommonCommandLineTest, self).__init__(
441 Args: 586 self.name(), ['commandline'],
442 afile: The filename string we will be processing.""" 587 'command line', ['js', 'frog'], self.get_standalone_benchmarks(),
443 os.chdir(os.path.join(DART_INSTALL_LOCATION, 'tools', 'testing', 588 test_runner, self.CommonCommandLineTester(self),
444 'perf_testing')) 589 self.CommonCommandLineFileProcessor(self),
445 f = open(os.path.join(self.result_folder_name, afile)) 590 build_targets=['create_sdk', 'dart2js'])
446 tabulate_data = False 591
447 revision_num = 0 592 @staticmethod
448 for line in f.readlines(): 593 def name():
449 if 'Revision' in line: 594 return 'cl-perf'
450 revision_num = int(line.split()[1]) 595
451 elif 'Benchmark' in line: 596 @staticmethod
452 tabulate_data = True 597 def get_standalone_benchmarks():
453 elif tabulate_data: 598 return ['Mandelbrot', 'DeltaBlue', 'Richards', 'NBody', 'BinaryTrees',
454 tokens = line.split() 599 'Fannkuch', 'Meteor', 'BubbleSort', 'Fibonacci', 'Loop', 'Permute',
455 if len(tokens) < 4 or tokens[0] not in self.benchmarks: 600 'Queens', 'QuickSort', 'Recurse', 'Sieve', 'Sum', 'Tak', 'Takl', 'Towers',
456 #Done tabulating data. 601 'TreeSort']
457 break 602
458 js_value = float(tokens[1]) 603 class CommonCommandLineTester(Tester):
459 frog_value = float(tokens[3]) 604 def run_tests(self):
460 if js_value == 0 or frog_value == 0: 605 """Run a performance test on our updated system."""
461 #Then there was an error when this performance test was run. Do not 606 os.chdir('frog')
462 #count it in our numbers. 607 self.test.trace_file = os.path.join(
463 return 608 '..', 'tools', 'testing', 'perf_testing',
464 benchmark = tokens[0] 609 self.test.result_folder_name, 'result' + self.test.cur_time)
465 self.revision_dict[COMMAND_LINE][JS][benchmark] += [revision_num] 610 self.test.test_runner.run_cmd(['python', os.path.join('benchmarks',
466 self.values_dict[COMMAND_LINE][JS][benchmark] += [js_value] 611 'perf_tests.py')], self.test.trace_file)
467 self.revision_dict[COMMAND_LINE][FROG][benchmark] += [revision_num] 612 os.chdir('..')
468 self.values_dict[COMMAND_LINE][FROG][benchmark] += [frog_value] 613
469 f.close() 614 class CommonCommandLineFileProcessor(Processor):
470 615 def process_file(self, afile):
471 self.calculate_geometric_mean(COMMAND_LINE, FROG, revision_num) 616 """Pull all the relevant information out of a given tracefile.
472 self.calculate_geometric_mean(COMMAND_LINE, JS, revision_num) 617
473 618 Args:
474 def run_tests(self): 619 afile: The filename string we will be processing."""
475 """Run a performance test on our updated system.""" 620 os.chdir(os.path.join(DART_INSTALL_LOCATION, 'tools',
476 os.chdir('frog') 621 'testing', 'perf_testing'))
477 self.trace_file = os.path.join('..', 'tools', 'testing', 'perf_testing', 622 f = open(os.path.join(self.test.result_folder_name, afile))
478 self.result_folder_name, 'result' + self.cur_time) 623 tabulate_data = False
479 run_cmd(['python', os.path.join('benchmarks', 'perf_tests.py')], 624 revision_num = 0
480 self.trace_file) 625 for line in f.readlines():
481 os.chdir('..') 626 if 'Revision' in line:
482 627 revision_num = int(line.split()[1])
483 628 elif 'Benchmark' in line:
484 class BrowserStandalonePerformanceTest(PerformanceTest): 629 tabulate_data = True
485 """Runs standalone performance tests, in the browser.""" 630 elif tabulate_data:
486 631 tokens = line.split()
487 def __init__(self): 632 if len(tokens) < 4 or tokens[0] not in self.test.benchmarks:
488 super(BrowserStandalonePerformanceTest, self).__init__( 633 #Done tabulating data.
489 BROWSER_PERF, get_browsers(), 'browser', 634 break
490 JS_AND_FROG, get_standalone_benchmarks()) 635 js_value = float(tokens[1])
491 636 frog_value = float(tokens[3])
492 def run_tests(self): 637 if js_value == 0 or frog_value == 0:
493 """Run a performance test in the browser.""" 638 #Then there was an error when this performance test was run. Do not
494 639 #count it in our numbers.
495 os.chdir('frog') 640 return
496 run_cmd(['python', os.path.join('benchmarks', 'make_web_benchmarks.py')]) 641 benchmark = tokens[0]
497 os.chdir('..') 642 self.test.revision_dict['commandline']['js'][benchmark] += \
498 643 [revision_num]
499 for browser in get_browsers(): 644 self.test.values_dict['commandline']['js'][benchmark] += [js_value]
500 for version in self.versions: 645 self.test.revision_dict['commandline']['frog'][benchmark] += \
501 self.trace_file = os.path.join('tools', 'testing', 'perf_testing', 646 [revision_num]
502 self.result_folder_name, 647 self.test.values_dict['commandline']['frog'][benchmark] += \
503 'perf-%s-%s-%s' % (self.cur_time, browser, version)) 648 [frog_value]
504 self.add_svn_revision_to_trace(self.trace_file) 649 f.close()
505 file_path = os.path.join(os.getcwd(), 'internal', 'browserBenchmarks', 650
506 'benchmark_page_%s.html' % version) 651 self.calculate_geometric_mean('commandline', 'frog', revision_num)
507 run_cmd(['python', os.path.join('tools', 'testing', 'run_selenium.py'), 652 self.calculate_geometric_mean('commandline', 'js', revision_num)
508 '--out', file_path, '--browser', browser, 653
509 '--timeout', '600', '--mode', 'perf'], self.trace_file, append=True) 654
510 655 class BrowserTester(Tester):
511 def process_file(self, afile): 656 # TODO(vsm): Add Dartium.
512 """Comb through the html to find the performance results.""" 657 @staticmethod
513 os.chdir(os.path.join(DART_INSTALL_LOCATION, 'tools', 'testing', 658 def get_browsers():
514 'perf_testing')) 659 browsers = ['ff', 'chrome']
515 parts = afile.split('-') 660 if platform.system() == 'Darwin':
516 browser = parts[2] 661 browsers += ['safari']
517 version = parts[3] 662 if platform.system() == 'Windows':
518 f = open(os.path.join(self.result_folder_name, afile)) 663 browsers += ['ie']
519 lines = f.readlines() 664 return browsers
520 line = '' 665
521 i = 0 666
522 revision_num = 0 667 class CommonBrowserTest(RuntimePerformanceTest):
523 while '<div id="results">' not in line and i < len(lines): 668 """Runs this basic performance tests (Benchpress, some V8 benchmarks) in the
524 if 'Revision' in line: 669 browser."""
525 revision_num = int(line.split()[1].strip('"')) 670
671 def __init__(self, test_runner):
672 """Args:
673 test_runner: Reference to the object that notifies us when to run."""
674 super(CommonBrowserTest, self).__init__(
675 self.name(), BrowserTester.get_browsers(),
676 'browser', ['js', 'frog'],
677 self.get_standalone_benchmarks(), test_runner,
678 self.CommonBrowserTester(self),
679 self.CommonBrowserFileProcessor(self))
680
681 @staticmethod
682 def name():
683 return 'browser-perf'
684
685 @staticmethod
686 def get_standalone_benchmarks():
687 return ['Mandelbrot', 'DeltaBlue', 'Richards', 'NBody', 'BinaryTrees',
688 'Fannkuch', 'Meteor', 'BubbleSort', 'Fibonacci', 'Loop', 'Permute',
689 'Queens', 'QuickSort', 'Recurse', 'Sieve', 'Sum', 'Tak', 'Takl', 'Towers',
690 'TreeSort']
691
692 class CommonBrowserTester(BrowserTester):
693 def run_tests(self):
694 """Run a performance test in the browser."""
695 os.chdir('frog')
696 self.test.test_runner.run_cmd(['python', os.path.join('benchmarks',
697 'make_web_benchmarks.py')])
698 os.chdir('..')
699
700 for browser in BrowserTester.get_browsers():
701 for version in self.test.versions:
702 self.test.trace_file = os.path.join(
703 'tools', 'testing', 'perf_testing', self.test.result_folder_name,
704 'perf-%s-%s-%s' % (self.test.cur_time, browser, version))
705 self.add_svn_revision_to_trace(self.test.trace_file)
706 file_path = os.path.join(
707 os.getcwd(), 'internal', 'browserBenchmarks',
708 'benchmark_page_%s.html' % version)
709 self.test.test_runner.run_cmd(
710 ['python', os.path.join('tools', 'testing', 'run_selenium.py'),
711 '--out', file_path, '--browser', browser,
712 '--timeout', '600', '--mode', 'perf'], self.test.trace_file,
713 append=True)
714
715 class CommonBrowserFileProcessor(Processor):
716 def process_file(self, afile):
717 """Comb through the html to find the performance results."""
718 os.chdir(os.path.join(DART_INSTALL_LOCATION, 'tools',
719 'testing', 'perf_testing'))
720 parts = afile.split('-')
721 browser = parts[2]
722 version = parts[3]
723 f = open(os.path.join(self.test.result_folder_name, afile))
724 lines = f.readlines()
725 line = ''
726 i = 0
727 revision_num = 0
728 while '<div id="results">' not in line and i < len(lines):
729 if 'Revision' in line:
730 revision_num = int(line.split()[1].strip('"'))
731 line = lines[i]
732 i += 1
733
734 if i >= len(lines) or revision_num == 0:
735 # Then this run did not complete. Ignore this tracefile.
736 return
737
526 line = lines[i] 738 line = lines[i]
527 i += 1 739 i += 1
528 740 results = []
529 if i >= len(lines) or revision_num == 0: 741 if line.find('<br>') > -1:
530 # Then this run did not complete. Ignore this tracefile. 742 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: 743 else:
550 bench_dict = self.values_dict[browser][FROG] 744 results = line.split('<br />')
551 bench_dict[name] += [float(score)] 745 for result in results:
552 self.revision_dict[browser][version][name] += [revision_num] 746 name_and_score = result.split(':')
553 747 if len(name_and_score) < 2:
554 f.close() 748 break
555 self.calculate_geometric_mean(browser, version, revision_num) 749 name = name_and_score[0].strip()
556 750 score = name_and_score[1].strip()
557 751 if version == 'js' or version == 'v8':
558 # TODO(vsm): This should not be hardcoded here if possible. 752 version = 'js'
559 DROMAEO_BENCHMARKS = { 753 bench_dict = self.test.values_dict[browser]['js']
560 'attr': ('attributes', [ 754 else:
561 'getAttribute', 755 bench_dict = self.test.values_dict[browser]['frog']
562 'element.property', 756 bench_dict[name] += [float(score)]
563 'setAttribute', 757 self.test.revision_dict[browser][version][name] += [revision_num]
564 'element.property = value']), 758
565 'modify': ('modify', [ 759 f.close()
566 'createElement', 760 self.calculate_geometric_mean(browser, version, revision_num)
567 'createTextNode', 761
568 'innerHTML', 762 class DromaeoTester(Tester):
569 'cloneNode', 763 DROMAEO_BENCHMARKS = {
570 'appendChild', 764 'attr': ('attributes', [
571 'insertBefore']), 765 'getAttribute',
572 'query': ('query', [ 766 'element.property',
573 'getElementById', 767 'setAttribute',
574 'getElementById (not in document)', 768 'element.property = value']),
575 'getElementsByTagName(div)', 769 'modify': ('modify', [
576 'getElementsByTagName(p)', 770 'createElement',
577 'getElementsByTagName(a)', 771 'createTextNode',
578 'getElementsByTagName(*)', 772 'innerHTML',
579 'getElementsByTagName (not in document)', 773 'cloneNode',
580 'getElementsByName', 774 'appendChild',
581 'getElementsByName (not in document)']), 775 'insertBefore']),
582 'traverse': ('traverse', [ 776 'query': ('query', [
583 'firstChild', 777 'getElementById',
584 'lastChild', 778 'getElementById (not in document)',
585 'nextSibling', 779 'getElementsByTagName(div)',
586 'previousSibling', 780 'getElementsByTagName(p)',
587 'childNodes']) 781 'getElementsByTagName(a)',
588 } 782 'getElementsByTagName(*)',
589 783 'getElementsByTagName (not in document)',
590 # Use legal appengine filenames for benchmark names. 784 'getElementsByName',
591 def legalize_filename(str): 785 'getElementsByName (not in document)']),
592 remap = { 786 'traverse': ('traverse', [
593 ' ': '_', 787 'firstChild',
594 '(': '_', 788 'lastChild',
595 ')': '_', 789 'nextSibling',
596 '*': 'ALL', 790 'previousSibling',
597 '=': 'ASSIGN', 791 'childNodes'])
598 } 792 }
599 for (old, new) in remap.iteritems(): 793
600 str = str.replace(old, new) 794 # Use legal appengine filenames for benchmark names.
601 return str 795 @staticmethod
602 796 def legalize_filename(str):
603 # TODO(vsm): This is a hack to skip breaking tests. Triage this 797 remap = {
604 # failure properly. The modify suite fails on 32-bit chrome on 798 ' ': '_',
605 # the mac. 799 '(': '_',
606 def get_valid_dromaeo_tags(): 800 ')': '_',
607 tags = [tag for (tag, _) in DROMAEO_BENCHMARKS.values()] 801 '*': 'ALL',
608 if platform.system() == 'Darwin': 802 '=': 'ASSIGN',
609 tags.remove('modify') 803 }
610 return tags 804 for (old, new) in remap.iteritems():
611 805 str = str.replace(old, new)
612 def get_dromaeo_benchmarks(): 806 return str
613 valid = get_valid_dromaeo_tags() 807
614 benchmarks = reduce(lambda l1,l2: l1+l2, 808 # TODO(vsm): This is a hack to skip breaking tests. Triage this
615 [tests for (tag, tests) in 809 # failure properly. The modify suite fails on 32-bit chrome on
616 DROMAEO_BENCHMARKS.values() if tag in valid]) 810 # the mac.
617 return map(legalize_filename, benchmarks) 811 @staticmethod
618 812 def get_valid_dromaeo_tags():
619 def get_dromaeo_versions(): 813 tags = [tag for (tag, _) in DromaeoTester.DROMAEO_BENCHMARKS.values()]
620 return ['js', 'frog_dom', 'frog_html'] 814 if platform.system() == 'Darwin':
621 815 tags.remove('modify')
622 def get_dromaeo_url_query(version): 816 return tags
623 version = version.replace('_','&') 817
624 tags = get_valid_dromaeo_tags() 818 @staticmethod
625 return '|'.join([ '%s&%s' % (version, tag) for tag in tags]) 819 def get_dromaeo_benchmarks():
626 820 valid = DromaeoTester.get_valid_dromaeo_tags()
627 class DromaeoTest(PerformanceTest): 821 benchmarks = reduce(lambda l1,l2: l1+l2,
822 [tests for (tag, tests) in
823 DromaeoTester.DROMAEO_BENCHMARKS.values()
824 if tag in valid])
825 return map(DromaeoTester.legalize_filename, benchmarks)
826
827 @staticmethod
828 def get_dromaeo_versions():
829 return ['js', 'frog_dom', 'frog_html']
830
831
832 class DromaeoTest(RuntimePerformanceTest):
628 """Runs Dromaeo tests, in the browser.""" 833 """Runs Dromaeo tests, in the browser."""
629 def __init__(self): 834 def __init__(self, test_runner):
630 super(DromaeoTest, self).__init__( 835 super(DromaeoTest, self).__init__(
631 DROMAEO, get_browsers(), 'browser', 836 self.name(), BrowserTester.get_browsers(), 'browser',
632 get_dromaeo_versions(), get_dromaeo_benchmarks()) 837 DromaeoTester.get_dromaeo_versions(),
633 838 DromaeoTester.get_dromaeo_benchmarks(), test_runner,
634 def run_tests(self): 839 self.DromaeoPerfTester(self),
635 """Run dromaeo in the browser.""" 840 self.DromaeoFileProcessor(self))
636 841
637 # Build tests. 842 @staticmethod
638 dromaeo_path = os.path.join('samples', 'third_party', 'dromaeo') 843 def name():
639 current_path = os.getcwd() 844 return 'dromaeo'
640 os.chdir(dromaeo_path) 845
641 run_cmd(['python', 'generate_frog_tests.py']) 846 class DromaeoPerfTester(DromaeoTester):
642 os.chdir(current_path) 847 def run_tests(self):
643 848 """Run dromaeo in the browser."""
644 versions = get_dromaeo_versions() 849
645 850 # Build tests.
646 for browser in get_browsers(): 851 dromaeo_path = os.path.join('samples', 'third_party', 'dromaeo')
647 for version_name in versions: 852 current_path = os.getcwd()
648 version = get_dromaeo_url_query(version_name) 853 os.chdir(dromaeo_path)
649 self.trace_file = os.path.join('tools', 'testing', 'perf_testing', 854 self.test.test_runner.run_cmd(['python', 'generate_frog_tests.py'])
650 self.result_folder_name, 855 os.chdir(current_path)
651 'dromaeo-%s-%s-%s' % (self.cur_time, browser, version_name)) 856
652 self.add_svn_revision_to_trace(self.trace_file) 857 versions = DromaeoTester.get_dromaeo_versions()
653 file_path = os.path.join(os.getcwd(), dromaeo_path, 858
654 'index-js.html?%s' % version) 859 for browser in BrowserTester.get_browsers():
655 run_cmd(['python', os.path.join('tools', 'testing', 'run_selenium.py'), 860 for version_name in versions:
656 '--out', file_path, '--browser', browser, 861 version = DromaeoTest.DromaeoPerfTester.get_dromaeo_url_query(
657 '--timeout', '200', '--mode', 'dromaeo'], self.trace_file, 862 version_name)
658 append=True) 863 self.test.trace_file = os.path.join(
659 864 'tools', 'testing', 'perf_testing', self.test.result_folder_name,
660 def process_file(self, afile): 865 'dromaeo-%s-%s-%s' % (self.test.cur_time, browser, version_name))
661 """Comb through the html to find the performance results.""" 866 self.add_svn_revision_to_trace(self.test.trace_file)
662 parts = afile.split('-') 867 file_path = os.path.join(os.getcwd(), dromaeo_path,
663 browser = parts[2] 868 'index-js.html?%s' % version)
664 version = parts[3] 869 self.test.test_runner.run_cmd(
665 870 ['python', os.path.join('tools', 'testing', 'run_selenium.py'),
666 bench_dict = self.values_dict[browser][version] 871 '--out', file_path, '--browser', browser,
667 872 '--timeout', '600', '--mode', 'dromaeo'], self.test.trace_file,
668 f = open(os.path.join(self.result_folder_name, afile)) 873 append=True)
669 lines = f.readlines() 874
670 i = 0 875 @staticmethod
671 revision_num = 0 876 def get_dromaeo_url_query(version):
672 revision_pattern = r'Revision: (\d+)' 877 version = version.replace('_','&')
673 suite_pattern = r'<div class="result-item done">(.+?)</ol></div>' 878 tags = DromaeoTester.get_valid_dromaeo_tags()
674 result_pattern = r'<b>(.+?)</b>(.+?)<small> runs/s(.+)' 879 return '|'.join([ '%s&%s' % (version, tag) for tag in tags])
675 880
676 for line in lines: 881
677 rev = re.match(revision_pattern, line.strip()) 882 class DromaeoFileProcessor(Processor):
678 if rev: 883 def process_file(self, afile):
679 revision_num = int(rev.group(1)) 884 """Comb through the html to find the performance results."""
680 continue 885 parts = afile.split('-')
681 886 browser = parts[2]
682 suite_results = re.findall(suite_pattern, line) 887 version = parts[3]
683 if suite_results: 888
684 for suite_result in suite_results: 889 bench_dict = self.test.values_dict[browser][version]
685 results = re.findall(r'<li>(.*?)</li>', suite_result) 890
686 if results: 891 f = open(os.path.join(self.test.result_folder_name, afile))
687 for result in results: 892 lines = f.readlines()
688 r = re.match(result_pattern, result) 893 i = 0
689 name = legalize_filename(r.group(1).strip(':')) 894 revision_num = 0
690 score = float(r.group(2)) 895 revision_pattern = r'Revision: (\d+)'
691 bench_dict[name] += [float(score)] 896 suite_pattern = r'<div class="result-item done">(.+?)</ol></div>'
692 self.revision_dict[browser][version][name] += [revision_num] 897 result_pattern = r'<b>(.+?)</b>(.+?)<small> runs/s(.+)'
693 898
694 f.close() 899 for line in lines:
695 self.calculate_geometric_mean(browser, version, revision_num) 900 rev = re.match(revision_pattern, line.strip())
696 901 if rev:
697 902 revision_num = int(rev.group(1))
698 class DromaeoSizeTest(TestRunner): 903 continue
904
905 suite_results = re.findall(suite_pattern, line)
906 if suite_results:
907 for suite_result in suite_results:
908 results = re.findall(r'<li>(.*?)</li>', suite_result)
909 if results:
910 for result in results:
911 r = re.match(result_pattern, result)
912 name = DromaeoTester.legalize_filename(
913 r.group(1).strip(':'))
914 score = float(r.group(2))
915 bench_dict[name] += [float(score)]
916 self.test.revision_dict[browser][version][name] += \
917 [revision_num]
918
919 f.close()
920 self.calculate_geometric_mean(browser, version, revision_num)
921
922
923 class DromaeoSizeTest(Test):
699 """Run tests to determine the compiled file output size of Dromaeo.""" 924 """Run tests to determine the compiled file output size of Dromaeo."""
700 def __init__(self): 925 def __init__(self, test_runner):
701 super(DromaeoSizeTest, self).__init__( 926 super(DromaeoSizeTest, self).__init__(
702 DROMAEO_SIZE, 927 self.name(),
703 ['browser'], ['dart', 'frog_dom', 'frog_html', 'frog_htmlidiomatic'], 928 ['browser'], ['dart', 'frog_dom', 'frog_html', 'frog_htmlidiomatic'],
704 DROMAEO_BENCHMARKS.keys()) 929 DromaeoTester.DROMAEO_BENCHMARKS.keys(), test_runner,
705 930 self.DromaeoSizeTester(self),
706 def run_tests(self): 931 self.DromaeoSizeProcessor(self),
707 # Build tests. 932 self.DromaeoSizeGrapher(self), extra_metrics=['sum'])
708 dromaeo_path = os.path.join('samples', 'third_party', 'dromaeo') 933
709 current_path = os.getcwd() 934 @staticmethod
710 os.chdir(dromaeo_path) 935 def name():
711 run_cmd(['python', os.path.join('generate_frog_tests.py')]) 936 return 'dromaeo-size'
712 os.chdir(current_path) 937
713 938
714 self.trace_file = os.path.join('tools', 'testing', 'perf_testing', 939 class DromaeoSizeTester(DromaeoTester):
715 self.result_folder_name, self.result_folder_name + self.cur_time) 940 def run_tests(self):
716 self.add_svn_revision_to_trace(self.trace_file) 941 # Build tests.
717 942 dromaeo_path = os.path.join('samples', 'third_party', 'dromaeo')
718 variants = [ 943 current_path = os.getcwd()
719 ('frog_dom', ''), 944 os.chdir(dromaeo_path)
720 ('frog_html', '-html'), 945 self.test.test_runner.run_cmd(
721 ('frog_htmlidiomatic', '-htmlidiomatic')] 946 ['python', os.path.join('generate_frog_tests.py')])
722 947 os.chdir(current_path)
723 test_path = os.path.join(dromaeo_path, 'tests') 948
724 frog_path = os.path.join(test_path, 'frog') 949 self.test.trace_file = os.path.join(
725 total_size = {} 950 'tools', 'testing', 'perf_testing', self.test.result_folder_name,
726 for (variant, _) in variants: 951 self.test.result_folder_name + self.test.cur_time)
727 total_size[variant] = 0 952 self.add_svn_revision_to_trace(self.test.trace_file)
728 total_dart_size = 0 953
729 for suite in DROMAEO_BENCHMARKS.keys(): 954 variants = [
730 dart_size = 0 955 ('frog_dom', ''),
956 ('frog_html', '-html'),
957 ('frog_htmlidiomatic', '-htmlidiomatic')]
958
959 test_path = os.path.join(dromaeo_path, 'tests')
960 frog_path = os.path.join(test_path, 'frog')
961 total_size = {}
962 for (variant, _) in variants:
963 total_size[variant] = 0
964 total_dart_size = 0
965 for suite in DromaeoTester.DROMAEO_BENCHMARKS.keys():
966 dart_size = 0
967 try:
968 dart_size = os.path.getsize(os.path.join(test_path,
969 'dom-%s.dart' % suite))
970 except OSError:
971 pass #If compilation failed, continue on running other tests.
972
973 total_dart_size += dart_size
974 self.test.test_runner.run_cmd(
975 ['echo', 'Size (dart, %s): %s' % (suite, str(dart_size))],
976 self.test.trace_file, append=True)
977
978 for (variant, suffix) in variants:
979 name = 'dom-%s%s.dart.js' % (suite, suffix)
980 js_size = 0
981 try:
982 # TODO(vsm): Strip comments at least. Consider compression.
983 js_size = os.path.getsize(os.path.join(frog_path, name))
984 except OSError:
985 pass #If compilation failed, continue on running other tests.
986
987 total_size[variant] += js_size
988 self.test.test_runner.run_cmd(
989 ['echo', 'Size (%s, %s): %s' % (variant, suite, str(js_size))],
990 self.test.trace_file, append=True)
991
992 self.test.test_runner.run_cmd(
993 ['echo', 'Size (dart, %s): %s' % (total_dart_size,
994 self.test.extra_metrics[0])],
995 self.test.trace_file, append=True)
996 for (variant, _) in variants:
997 self.test.test_runner.run_cmd(
998 ['echo', 'Size (%s, %s): %s' % (variant, self.test.extra_metrics[0],
999 total_size[variant])],
1000 self.test.trace_file, append=True)
1001
1002 class DromaeoSizeProcessor(Processor):
1003 def process_file(self, afile):
1004 """Pull all the relevant information out of a given tracefile.
1005
1006 Args:
1007 afile: is the filename string we will be processing."""
1008 os.chdir(os.path.join(DART_INSTALL_LOCATION, 'tools',
1009 'testing', 'perf_testing'))
1010 f = open(os.path.join(self.test.result_folder_name, afile))
1011 tabulate_data = False
1012 revision_num = 0
1013 revision_pattern = r'Revision: (\d+)'
1014 result_pattern = r'Size \((\w+), ([a-zA-Z0-9-]+)\): (\d+)'
1015
1016 for line in f.readlines():
1017 rev = re.match(revision_pattern, line.strip())
1018 if rev:
1019 revision_num = int(rev.group(1))
1020 continue
1021
1022 result = re.match(result_pattern, line.strip())
1023 if result:
1024 variant = result.group(1)
1025 metric = result.group(2)
1026 num = result.group(3)
1027 if num.find('.') == -1:
1028 num = int(num)
1029 else:
1030 num = float(num)
1031 self.test.values_dict['browser'][variant][metric] += [num]
1032 self.test.revision_dict['browser'][variant][metric] += [revision_num]
1033
1034 f.close()
1035 class DromaeoSizeGrapher(Grapher):
1036 def plot_results(self, png_filename):
1037 self.style_and_save_perf_plot(
1038 'Compiled Dromaeo Sizes',
1039 'Size (in bytes)', 10, 10, 'lower left', png_filename,
1040 ['browser'], ['dart', 'frog_dom', 'frog_html', 'frog_htmlidiomatic'],
1041 DromaeoTester.DROMAEO_BENCHMARKS.keys())
1042
1043 self.style_and_save_perf_plot(
1044 'Compiled Dromaeo Sizes',
1045 'Size (in bytes)', 10, 10, 'lower left', '2' + png_filename,
1046 ['browser'], ['dart', 'frog_dom', 'frog_html', 'frog_htmlidiomatic'],
1047 [self.test.extra_metrics[0]])
1048
1049
1050 class CompileTimeAndSizeTest(Test):
1051 """Run tests to determine how long minfrog takes to compile, and the compiled
1052 file output size of some benchmarking files."""
1053 def __init__(self, test_runner):
1054 """Reference to the test_runner object that notifies us when to begin
1055 testing."""
1056 super(CompileTimeAndSizeTest, self).__init__(
1057 self.name(), ['commandline'], ['frog'],
1058 ['Compiling on Dart VM', 'Bootstrapping', 'minfrog', 'swarm', 'total'],
1059 test_runner, self.CompileTester(self),
1060 self.CompileProcessor(self), self.CompileGrapher(self))
1061 self.dart_compiler = os.path.join(
1062 DART_INSTALL_LOCATION, utils.GetBuildRoot(utils.GuessOS(),
1063 'release', 'ia32'), 'dart-sdk', 'bin', 'frogc')
1064 _suffix = ''
1065 if platform.system() == 'Windows':
1066 _suffix = '.exe'
1067 self.dart_vm = os.path.join(
1068 DART_INSTALL_LOCATION, utils.GetBuildRoot(utils.GuessOS(),
1069 'release', 'ia32'), 'dart-sdk', 'bin','dart' + _suffix)
1070 self.failure_threshold = {
1071 'Compiling on Dart VM' : 1, 'Bootstrapping' : .5, 'minfrog' : 100,
1072 'swarm' : 100, 'total' : 100}
1073
1074 @staticmethod
1075 def name():
1076 return 'time-size'
1077
1078 class CompileTester(Tester):
1079 def run_tests(self):
1080 os.chdir('frog')
1081 self.test.trace_file = os.path.join(
1082 '..', 'tools', 'testing', 'perf_testing',
1083 self.test.result_folder_name,
1084 self.test.result_folder_name + self.test.cur_time)
1085
1086 self.add_svn_revision_to_trace(self.test.trace_file)
1087
1088 elapsed = self.test.test_runner.time_cmd(
1089 [self.test.dart_vm, os.path.join('.', 'minfrogc.dart'),
1090 '--out=minfrog', 'minfrog.dart'])
1091 self.test.test_runner.run_cmd(
1092 ['echo', '%f Compiling on Dart VM in production mode in seconds'
1093 % elapsed], self.test.trace_file, append=True)
1094 elapsed = self.test.test_runner.time_cmd(
1095 [os.path.join('.', 'minfrog'), '--out=minfrog', 'minfrog.dart',
1096 os.path.join('tests', 'hello.dart')])
1097 if elapsed < self.test.failure_threshold['Bootstrapping']:
1098 #minfrog didn't compile correctly. Stop testing now, because subsequent
1099 #numbers will be meaningless.
1100 return
1101 size = os.path.getsize('minfrog')
1102 self.test.test_runner.run_cmd(
1103 ['echo', '%f Bootstrapping time in seconds in production mode' %
1104 elapsed], self.test.trace_file, append=True)
1105 self.test.test_runner.run_cmd(
1106 ['echo', '%d Generated checked minfrog size' % size],
1107 self.test.trace_file, append=True)
1108
1109 self.test.test_runner.run_cmd(
1110 [self.test.dart_compiler, '--out=swarm-result',
1111 os.path.join('..', 'samples', 'swarm',
1112 'swarm.dart')])
1113
1114 swarm_size = 0
731 try: 1115 try:
732 dart_size = os.path.getsize(os.path.join(test_path, 1116 swarm_size = os.path.getsize('swarm-result')
733 'dom-%s.dart' % suite))
734 except OSError: 1117 except OSError:
735 pass #If compilation failed, continue on running other tests. 1118 pass #If compilation failed, continue on running other tests.
736 1119
737 total_dart_size += dart_size 1120 self.test.test_runner.run_cmd(
738 run_cmd(['echo', 'Size (dart, %s): %s' % (suite, str(dart_size))], 1121 [self.test.dart_compiler, '--out=total-result',
739 self.trace_file, append=True) 1122 os.path.join('..', 'samples', 'total',
740 1123 'client', 'Total.dart')])
741 for (variant, suffix) in variants: 1124 total_size = 0
742 name = 'dom-%s%s.dart.js' % (suite, suffix) 1125 try:
743 js_size = 0 1126 total_size = os.path.getsize('total-result')
744 try: 1127 except OSError:
745 # TODO(vsm): Strip comments at least. Consider compression. 1128 pass #If compilation failed, continue on running other tests.
746 js_size = os.path.getsize(os.path.join(frog_path, name)) 1129
747 except OSError: 1130 self.test.test_runner.run_cmd(
748 pass #If compilation failed, continue on running other tests. 1131 ['echo', '%d Generated checked swarm size' % swarm_size],
749 1132 self.test.trace_file, append=True)
750 total_size[variant] += js_size 1133
751 run_cmd(['echo', 'Size (%s, %s): %s' % (variant, suite, 1134 self.test.test_runner.run_cmd(
752 str(js_size))], 1135 ['echo', '%d Generated checked total size' % total_size],
753 self.trace_file, append=True) 1136 self.test.trace_file, append=True)
754 1137
755 # TODO(vsm): Change GEO_MEAN to sum. The base class assumes 1138 #Revert our newly built minfrog to prevent conflicts when we update
756 # GEO_MEAN right now. 1139 self.test.test_runner.run_cmd(
757 run_cmd(['echo', 'Size (dart, %s): %s' % (total_dart_size, GEO_MEAN)], 1140 ['svn', 'revert', os.path.join(os.getcwd(), 'frog', 'minfrog')])
758 self.trace_file, append=True) 1141
759 for (variant, _) in variants: 1142 os.chdir('..')
760 run_cmd(['echo', 'Size (%s, %s): %s' % (variant, GEO_MEAN, 1143
761 total_size[variant])], 1144 class CompileProcessor(Processor):
762 self.trace_file, append=True) 1145 def process_file(self, afile):
763 1146 """Pull all the relevant information out of a given tracefile.
764 1147
765 def process_file(self, afile): 1148 Args:
766 """Pull all the relevant information out of a given tracefile. 1149 afile: is the filename string we will be processing."""
767 1150 os.chdir(os.path.join(DART_INSTALL_LOCATION, 'tools',
768 Args: 1151 'testing', 'perf_testing'))
769 afile: is the filename string we will be processing.""" 1152 f = open(os.path.join(self.test.result_folder_name, afile))
770 os.chdir(os.path.join(DART_INSTALL_LOCATION, 'tools', 'testing', 1153 tabulate_data = False
771 'perf_testing')) 1154 revision_num = 0
772 f = open(os.path.join(self.result_folder_name, afile)) 1155 for line in f.readlines():
773 tabulate_data = False 1156 tokens = line.split()
774 revision_num = 0 1157 if 'Revision' in line:
775 revision_pattern = r'Revision: (\d+)' 1158 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: 1159 else:
792 num = float(num) 1160 for metric in self.test.values_list:
793 self.values_dict['browser'][variant][metric] += [num] 1161 if metric in line:
794 self.revision_dict['browser'][variant][metric] += [revision_num] 1162 num = tokens[0]
795 1163 if num.find('.') == -1:
796 f.close() 1164 num = int(num)
797 1165 else:
798 def plot_results(self, png_filename): 1166 num = float(num)
799 self.style_and_save_perf_plot( 1167 self.test.values_dict['commandline']['frog'][metric] += [num]
800 'Compiled Dromaeo Sizes', 1168 self.test.revision_dict['commandline']['frog'][metric] += \
801 'Size (in bytes)', 10, 10, 'lower left', png_filename, 1169 [revision_num]
802 ['browser'], ['dart', 'frog_dom', 'frog_html', 'frog_htmlidiomatic'], 1170
803 DROMAEO_BENCHMARKS.keys()) 1171 if revision_num != 0:
804 1172 for metric in self.test.values_list:
805 self.style_and_save_perf_plot( 1173 self.test.revision_dict['commandline']['frog'][metric].pop()
806 'Compiled Dromaeo Sizes', 1174 self.test.revision_dict['commandline']['frog'][metric] += \
807 'Size (in bytes)', 10, 10, 'lower left', '2' + png_filename, 1175 [revision_num]
808 ['browser'], ['dart', 'frog_dom', 'frog_html', 'frog_htmlidiomatic'], 1176 # Fill in 0 if compilation failed.
809 [GEO_MEAN]) 1177 if self.test.values_dict['commandline']['frog'][metric][-1] < \
810 1178 self.test.failure_threshold[metric]:
811 1179 self.test.values_dict['commandline']['frog'][metric] += [0]
812 1180 self.test.revision_dict['commandline']['frog'][metric] += \
813 class CompileTimeAndSizeTest(TestRunner): 1181 [revision_num]
814 """Run tests to determine how long minfrog takes to compile, and the compiled 1182
815 file output size of some benchmarking files.""" 1183 f.close()
816 def __init__(self): 1184
817 super(CompileTimeAndSizeTest, self).__init__(TIME_SIZE, 1185 class CompileGrapher(Grapher):
818 [COMMAND_LINE], [FROG], ['Compiling on Dart VM', 'Bootstrapping', 1186
819 'minfrog', 'swarm', 'total']) 1187 def plot_results(self, png_filename):
820 self.failure_threshold = {'Compiling on Dart VM' : 1, 'Bootstrapping' : .5, 1188 self.style_and_save_perf_plot(
821 'minfrog' : 100, 'swarm' : 100, 'total' : 100} 1189 'Compiled minfrog Sizes', 'Size (in bytes)', 10, 10, 'lower left',
822 1190 png_filename, ['commandline'], ['frog'],
823 def run_tests(self): 1191 ['swarm', 'total', 'minfrog'])
824 os.chdir('frog') 1192
825 self.trace_file = os.path.join('..', 'tools', 'testing', 'perf_testing', 1193 self.style_and_save_perf_plot(
826 self.result_folder_name, self.result_folder_name + self.cur_time) 1194 'Time to compile and bootstrap',
827 1195 'Seconds', 10, 10, 'lower left', '2' + png_filename, ['commandline'],
828 self.add_svn_revision_to_trace(self.trace_file) 1196 ['frog'], ['Bootstrapping', 'Compiling on Dart VM'])
829 1197
830 elapsed = time_cmd([DART_VM, os.path.join('.', 'minfrogc.dart'), 1198
831 '--out=minfrog', 'minfrog.dart']) 1199 class TestBuilder(object):
832 run_cmd(['echo', '%f Compiling on Dart VM in production mode in seconds' 1200 """Construct the desired test object."""
833 % elapsed], self.trace_file, append=True) 1201 available_suites = dict((suite.name(), suite) for suite in [
834 elapsed = time_cmd([os.path.join('.', 'minfrog'), '--out=minfrog', 1202 CommonCommandLineTest, CompileTimeAndSizeTest,
835 'minfrog.dart', os.path.join('tests', 'hello.dart')]) 1203 CommonBrowserTest, DromaeoTest, DromaeoSizeTest])
836 if elapsed < self.failure_threshold['Bootstrapping']: 1204
837 #minfrog didn't compile correctly. Stop testing now, because subsequent 1205 @staticmethod
838 #numbers will be meaningless. 1206 def make_test(test_name, test_runner):
839 return 1207 return TestBuilder.available_suites[test_name](test_runner)
840 size = os.path.getsize('minfrog') 1208
841 run_cmd(['echo', '%f Bootstrapping time in seconds in production mode' % 1209 @staticmethod
842 elapsed], self.trace_file, append=True) 1210 def available_suite_names():
843 run_cmd(['echo', '%d Generated checked minfrog size' % size], 1211 return TestBuilder.available_suites.keys()
844 self.trace_file, append=True) 1212
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 1213
976 def main(): 1214 def main():
977 global VERBOSE 1215 runner = TestRunner()
978 (suites, continuous, verbose, no_build, graph_only, upload) = parse_args() 1216 continuous = runner.parse_args()
979 VERBOSE = verbose
980 if continuous: 1217 if continuous:
981 while True: 1218 while True:
982 if has_new_code(): 1219 if runner.has_new_code():
983 run_test_sequence(suites, no_build, graph_only, upload) 1220 runner.run_test_sequence()
984 else: 1221 else:
985 time.sleep(SLEEP_TIME) 1222 time.sleep(200)
986 else: 1223 else:
987 run_test_sequence(suites, no_build, graph_only, upload) 1224 runner.run_test_sequence()
988 1225
989 if __name__ == '__main__': 1226 if __name__ == '__main__':
990 main() 1227 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