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

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