OLD | NEW |
1 #!/usr/bin/env python | 1 #!/usr/bin/env python |
2 # Copyright 2014 the V8 project authors. All rights reserved. | 2 # Copyright 2014 the V8 project authors. All rights reserved. |
3 # Use of this source code is governed by a BSD-style license that can be | 3 # Use of this source code is governed by a BSD-style license that can be |
4 # found in the LICENSE file. | 4 # found in the LICENSE file. |
5 | 5 |
6 """ | 6 """ |
7 Performance runner for d8. | 7 Performance runner for d8. |
8 | 8 |
9 Call e.g. with tools/run-benchmarks.py --arch ia32 some_suite.json | 9 Call e.g. with tools/run-perf.py --arch ia32 some_suite.json |
10 | 10 |
11 The suite json format is expected to be: | 11 The suite json format is expected to be: |
12 { | 12 { |
13 "path": <relative path chunks to benchmark resources and main file>, | 13 "path": <relative path chunks to perf resources and main file>, |
14 "name": <optional suite name, file name is default>, | 14 "name": <optional suite name, file name is default>, |
15 "archs": [<architecture name for which this suite is run>, ...], | 15 "archs": [<architecture name for which this suite is run>, ...], |
16 "binary": <name of binary to run, default "d8">, | 16 "binary": <name of binary to run, default "d8">, |
17 "flags": [<flag to d8>, ...], | 17 "flags": [<flag to d8>, ...], |
18 "run_count": <how often will this suite run (optional)>, | 18 "run_count": <how often will this suite run (optional)>, |
19 "run_count_XXX": <how often will this suite run for arch XXX (optional)>, | 19 "run_count_XXX": <how often will this suite run for arch XXX (optional)>, |
20 "resources": [<js file to be loaded before main>, ...] | 20 "resources": [<js file to be loaded before main>, ...] |
21 "main": <main js benchmark runner file>, | 21 "main": <main js perf runner file>, |
22 "results_regexp": <optional regexp>, | 22 "results_regexp": <optional regexp>, |
23 "results_processor": <optional python results processor script>, | 23 "results_processor": <optional python results processor script>, |
24 "units": <the unit specification for the performance dashboard>, | 24 "units": <the unit specification for the performance dashboard>, |
25 "benchmarks": [ | 25 "tests": [ |
26 { | 26 { |
27 "name": <name of the benchmark>, | 27 "name": <name of the trace>, |
28 "results_regexp": <optional more specific regexp>, | 28 "results_regexp": <optional more specific regexp>, |
29 "results_processor": <optional python results processor script>, | 29 "results_processor": <optional python results processor script>, |
30 "units": <the unit specification for the performance dashboard>, | 30 "units": <the unit specification for the performance dashboard>, |
31 }, ... | 31 }, ... |
32 ] | 32 ] |
33 } | 33 } |
34 | 34 |
35 The benchmarks field can also nest other suites in arbitrary depth. A suite | 35 The tests field can also nest other suites in arbitrary depth. A suite |
36 with a "main" file is a leaf suite that can contain one more level of | 36 with a "main" file is a leaf suite that can contain one more level of |
37 benchmarks. | 37 tests. |
38 | 38 |
39 A suite's results_regexp is expected to have one string place holder | 39 A suite's results_regexp is expected to have one string place holder |
40 "%s" for the benchmark name. A benchmark's results_regexp overwrites suite | 40 "%s" for the trace name. A trace's results_regexp overwrites suite |
41 defaults. | 41 defaults. |
42 | 42 |
43 A suite's results_processor may point to an optional python script. If | 43 A suite's results_processor may point to an optional python script. If |
44 specified, it is called after running the benchmarks like this (with a path | 44 specified, it is called after running the tests like this (with a path |
45 relatve to the suite level's path): | 45 relatve to the suite level's path): |
46 <results_processor file> <same flags as for d8> <suite level name> <output> | 46 <results_processor file> <same flags as for d8> <suite level name> <output> |
47 | 47 |
48 The <output> is a temporary file containing d8 output. The results_regexp will | 48 The <output> is a temporary file containing d8 output. The results_regexp will |
49 be applied to the output of this script. | 49 be applied to the output of this script. |
50 | 50 |
51 A suite without "benchmarks" is considered a benchmark itself. | 51 A suite without "tests" is considered a performance test itself. |
52 | 52 |
53 Full example (suite with one runner): | 53 Full example (suite with one runner): |
54 { | 54 { |
55 "path": ["."], | 55 "path": ["."], |
56 "flags": ["--expose-gc"], | 56 "flags": ["--expose-gc"], |
57 "archs": ["ia32", "x64"], | 57 "archs": ["ia32", "x64"], |
58 "run_count": 5, | 58 "run_count": 5, |
59 "run_count_ia32": 3, | 59 "run_count_ia32": 3, |
60 "main": "run.js", | 60 "main": "run.js", |
61 "results_regexp": "^%s: (.+)$", | 61 "results_regexp": "^%s: (.+)$", |
62 "units": "score", | 62 "units": "score", |
63 "benchmarks": [ | 63 "tests": [ |
64 {"name": "Richards"}, | 64 {"name": "Richards"}, |
65 {"name": "DeltaBlue"}, | 65 {"name": "DeltaBlue"}, |
66 {"name": "NavierStokes", | 66 {"name": "NavierStokes", |
67 "results_regexp": "^NavierStokes: (.+)$"} | 67 "results_regexp": "^NavierStokes: (.+)$"} |
68 ] | 68 ] |
69 } | 69 } |
70 | 70 |
71 Full example (suite with several runners): | 71 Full example (suite with several runners): |
72 { | 72 { |
73 "path": ["."], | 73 "path": ["."], |
74 "flags": ["--expose-gc"], | 74 "flags": ["--expose-gc"], |
75 "archs": ["ia32", "x64"], | 75 "archs": ["ia32", "x64"], |
76 "run_count": 5, | 76 "run_count": 5, |
77 "units": "score", | 77 "units": "score", |
78 "benchmarks": [ | 78 "tests": [ |
79 {"name": "Richards", | 79 {"name": "Richards", |
80 "path": ["richards"], | 80 "path": ["richards"], |
81 "main": "run.js", | 81 "main": "run.js", |
82 "run_count": 3, | 82 "run_count": 3, |
83 "results_regexp": "^Richards: (.+)$"}, | 83 "results_regexp": "^Richards: (.+)$"}, |
84 {"name": "NavierStokes", | 84 {"name": "NavierStokes", |
85 "path": ["navier_stokes"], | 85 "path": ["navier_stokes"], |
86 "main": "run.js", | 86 "main": "run.js", |
87 "results_regexp": "^NavierStokes: (.+)$"} | 87 "results_regexp": "^NavierStokes: (.+)$"} |
88 ] | 88 ] |
(...skipping 54 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
143 def __add__(self, other): | 143 def __add__(self, other): |
144 self.traces += other.traces | 144 self.traces += other.traces |
145 self.errors += other.errors | 145 self.errors += other.errors |
146 return self | 146 return self |
147 | 147 |
148 def __str__(self): # pragma: no cover | 148 def __str__(self): # pragma: no cover |
149 return str(self.ToDict()) | 149 return str(self.ToDict()) |
150 | 150 |
151 | 151 |
152 class Node(object): | 152 class Node(object): |
153 """Represents a node in the benchmark suite tree structure.""" | 153 """Represents a node in the suite tree structure.""" |
154 def __init__(self, *args): | 154 def __init__(self, *args): |
155 self._children = [] | 155 self._children = [] |
156 | 156 |
157 def AppendChild(self, child): | 157 def AppendChild(self, child): |
158 self._children.append(child) | 158 self._children.append(child) |
159 | 159 |
160 | 160 |
161 class DefaultSentinel(Node): | 161 class DefaultSentinel(Node): |
162 """Fake parent node with all default values.""" | 162 """Fake parent node with all default values.""" |
163 def __init__(self): | 163 def __init__(self): |
164 super(DefaultSentinel, self).__init__() | 164 super(DefaultSentinel, self).__init__() |
165 self.binary = "d8" | 165 self.binary = "d8" |
166 self.run_count = 10 | 166 self.run_count = 10 |
167 self.path = [] | 167 self.path = [] |
168 self.graphs = [] | 168 self.graphs = [] |
169 self.flags = [] | 169 self.flags = [] |
170 self.resources = [] | 170 self.resources = [] |
171 self.results_regexp = None | 171 self.results_regexp = None |
172 self.stddev_regexp = None | 172 self.stddev_regexp = None |
173 self.units = "score" | 173 self.units = "score" |
174 self.total = False | 174 self.total = False |
175 | 175 |
176 | 176 |
177 class Graph(Node): | 177 class Graph(Node): |
178 """Represents a benchmark suite definition. | 178 """Represents a suite definition. |
179 | 179 |
180 Can either be a leaf or an inner node that provides default values. | 180 Can either be a leaf or an inner node that provides default values. |
181 """ | 181 """ |
182 def __init__(self, suite, parent, arch): | 182 def __init__(self, suite, parent, arch): |
183 super(Graph, self).__init__() | 183 super(Graph, self).__init__() |
184 self._suite = suite | 184 self._suite = suite |
185 | 185 |
186 assert isinstance(suite.get("path", []), list) | 186 assert isinstance(suite.get("path", []), list) |
187 assert isinstance(suite["name"], basestring) | 187 assert isinstance(suite["name"], basestring) |
188 assert isinstance(suite.get("flags", []), list) | 188 assert isinstance(suite.get("flags", []), list) |
(...skipping 25 matching lines...) Expand all Loading... |
214 | 214 |
215 # A similar regular expression for the standard deviation (optional). | 215 # A similar regular expression for the standard deviation (optional). |
216 if parent.stddev_regexp: | 216 if parent.stddev_regexp: |
217 stddev_default = parent.stddev_regexp % re.escape(suite["name"]) | 217 stddev_default = parent.stddev_regexp % re.escape(suite["name"]) |
218 else: | 218 else: |
219 stddev_default = None | 219 stddev_default = None |
220 self.stddev_regexp = suite.get("stddev_regexp", stddev_default) | 220 self.stddev_regexp = suite.get("stddev_regexp", stddev_default) |
221 | 221 |
222 | 222 |
223 class Trace(Graph): | 223 class Trace(Graph): |
224 """Represents a leaf in the benchmark suite tree structure. | 224 """Represents a leaf in the suite tree structure. |
225 | 225 |
226 Handles collection of measurements. | 226 Handles collection of measurements. |
227 """ | 227 """ |
228 def __init__(self, suite, parent, arch): | 228 def __init__(self, suite, parent, arch): |
229 super(Trace, self).__init__(suite, parent, arch) | 229 super(Trace, self).__init__(suite, parent, arch) |
230 assert self.results_regexp | 230 assert self.results_regexp |
231 self.results = [] | 231 self.results = [] |
232 self.errors = [] | 232 self.errors = [] |
233 self.stddev = "" | 233 self.stddev = "" |
234 | 234 |
235 def ConsumeOutput(self, stdout): | 235 def ConsumeOutput(self, stdout): |
236 try: | 236 try: |
237 self.results.append( | 237 self.results.append( |
238 re.search(self.results_regexp, stdout, re.M).group(1)) | 238 re.search(self.results_regexp, stdout, re.M).group(1)) |
239 except: | 239 except: |
240 self.errors.append("Regexp \"%s\" didn't match for benchmark %s." | 240 self.errors.append("Regexp \"%s\" didn't match for test %s." |
241 % (self.results_regexp, self.graphs[-1])) | 241 % (self.results_regexp, self.graphs[-1])) |
242 | 242 |
243 try: | 243 try: |
244 if self.stddev_regexp and self.stddev: | 244 if self.stddev_regexp and self.stddev: |
245 self.errors.append("Benchmark %s should only run once since a stddev " | 245 self.errors.append("Test %s should only run once since a stddev " |
246 "is provided by the benchmark." % self.graphs[-1]) | 246 "is provided by the test." % self.graphs[-1]) |
247 if self.stddev_regexp: | 247 if self.stddev_regexp: |
248 self.stddev = re.search(self.stddev_regexp, stdout, re.M).group(1) | 248 self.stddev = re.search(self.stddev_regexp, stdout, re.M).group(1) |
249 except: | 249 except: |
250 self.errors.append("Regexp \"%s\" didn't match for benchmark %s." | 250 self.errors.append("Regexp \"%s\" didn't match for test %s." |
251 % (self.stddev_regexp, self.graphs[-1])) | 251 % (self.stddev_regexp, self.graphs[-1])) |
252 | 252 |
253 def GetResults(self): | 253 def GetResults(self): |
254 return Results([{ | 254 return Results([{ |
255 "graphs": self.graphs, | 255 "graphs": self.graphs, |
256 "units": self.units, | 256 "units": self.units, |
257 "results": self.results, | 257 "results": self.results, |
258 "stddev": self.stddev, | 258 "stddev": self.stddev, |
259 }], self.errors) | 259 }], self.errors) |
260 | 260 |
261 | 261 |
262 class Runnable(Graph): | 262 class Runnable(Graph): |
263 """Represents a runnable benchmark suite definition (i.e. has a main file). | 263 """Represents a runnable suite definition (i.e. has a main file). |
264 """ | 264 """ |
265 @property | 265 @property |
266 def main(self): | 266 def main(self): |
267 return self._suite.get("main", "") | 267 return self._suite.get("main", "") |
268 | 268 |
269 def ChangeCWD(self, suite_path): | 269 def ChangeCWD(self, suite_path): |
270 """Changes the cwd to to path defined in the current graph. | 270 """Changes the cwd to to path defined in the current graph. |
271 | 271 |
272 The benchmarks are supposed to be relative to the suite configuration. | 272 The tests are supposed to be relative to the suite configuration. |
273 """ | 273 """ |
274 suite_dir = os.path.abspath(os.path.dirname(suite_path)) | 274 suite_dir = os.path.abspath(os.path.dirname(suite_path)) |
275 bench_dir = os.path.normpath(os.path.join(*self.path)) | 275 bench_dir = os.path.normpath(os.path.join(*self.path)) |
276 os.chdir(os.path.join(suite_dir, bench_dir)) | 276 os.chdir(os.path.join(suite_dir, bench_dir)) |
277 | 277 |
278 def GetCommand(self, shell_dir): | 278 def GetCommand(self, shell_dir): |
279 # TODO(machenbach): This requires +.exe if run on windows. | 279 # TODO(machenbach): This requires +.exe if run on windows. |
280 return ( | 280 return ( |
281 [os.path.join(shell_dir, self.binary)] + | 281 [os.path.join(shell_dir, self.binary)] + |
282 self.flags + | 282 self.flags + |
(...skipping 24 matching lines...) Expand all Loading... |
307 for i in range(0, n_results)] | 307 for i in range(0, n_results)] |
308 res.traces.append({ | 308 res.traces.append({ |
309 "graphs": self.graphs + ["Total"], | 309 "graphs": self.graphs + ["Total"], |
310 "units": res.traces[0]["units"], | 310 "units": res.traces[0]["units"], |
311 "results": total_results, | 311 "results": total_results, |
312 "stddev": "", | 312 "stddev": "", |
313 }) | 313 }) |
314 return res | 314 return res |
315 | 315 |
316 class RunnableTrace(Trace, Runnable): | 316 class RunnableTrace(Trace, Runnable): |
317 """Represents a runnable benchmark suite definition that is a leaf.""" | 317 """Represents a runnable suite definition that is a leaf.""" |
318 def __init__(self, suite, parent, arch): | 318 def __init__(self, suite, parent, arch): |
319 super(RunnableTrace, self).__init__(suite, parent, arch) | 319 super(RunnableTrace, self).__init__(suite, parent, arch) |
320 | 320 |
321 def Run(self, runner): | 321 def Run(self, runner): |
322 """Iterates over several runs and handles the output.""" | 322 """Iterates over several runs and handles the output.""" |
323 for stdout in runner(): | 323 for stdout in runner(): |
324 self.ConsumeOutput(stdout) | 324 self.ConsumeOutput(stdout) |
325 return self.GetResults() | 325 return self.GetResults() |
326 | 326 |
327 | 327 |
328 class RunnableGeneric(Runnable): | 328 class RunnableGeneric(Runnable): |
329 """Represents a runnable benchmark suite definition with generic traces.""" | 329 """Represents a runnable suite definition with generic traces.""" |
330 def __init__(self, suite, parent, arch): | 330 def __init__(self, suite, parent, arch): |
331 super(RunnableGeneric, self).__init__(suite, parent, arch) | 331 super(RunnableGeneric, self).__init__(suite, parent, arch) |
332 | 332 |
333 def Run(self, runner): | 333 def Run(self, runner): |
334 """Iterates over several runs and handles the output.""" | 334 """Iterates over several runs and handles the output.""" |
335 traces = {} | 335 traces = {} |
336 for stdout in runner(): | 336 for stdout in runner(): |
337 for line in stdout.strip().splitlines(): | 337 for line in stdout.strip().splitlines(): |
338 match = GENERIC_RESULTS_RE.match(line) | 338 match = GENERIC_RESULTS_RE.match(line) |
339 if match: | 339 if match: |
(...skipping 12 matching lines...) Expand all Loading... |
352 return reduce(lambda r, t: r + t, traces.itervalues(), Results()) | 352 return reduce(lambda r, t: r + t, traces.itervalues(), Results()) |
353 | 353 |
354 | 354 |
355 def MakeGraph(suite, arch, parent): | 355 def MakeGraph(suite, arch, parent): |
356 """Factory method for making graph objects.""" | 356 """Factory method for making graph objects.""" |
357 if isinstance(parent, Runnable): | 357 if isinstance(parent, Runnable): |
358 # Below a runnable can only be traces. | 358 # Below a runnable can only be traces. |
359 return Trace(suite, parent, arch) | 359 return Trace(suite, parent, arch) |
360 elif suite.get("main"): | 360 elif suite.get("main"): |
361 # A main file makes this graph runnable. | 361 # A main file makes this graph runnable. |
362 if suite.get("benchmarks"): | 362 if suite.get("tests"): |
363 # This graph has subbenchmarks (traces). | 363 # This graph has subgraphs (traces). |
364 return Runnable(suite, parent, arch) | 364 return Runnable(suite, parent, arch) |
365 else: | 365 else: |
366 # This graph has no subbenchmarks, it's a leaf. | 366 # This graph has no subgraphs, it's a leaf. |
367 return RunnableTrace(suite, parent, arch) | 367 return RunnableTrace(suite, parent, arch) |
368 elif suite.get("generic"): | 368 elif suite.get("generic"): |
369 # This is a generic suite definition. It is either a runnable executable | 369 # This is a generic suite definition. It is either a runnable executable |
370 # or has a main js file. | 370 # or has a main js file. |
371 return RunnableGeneric(suite, parent, arch) | 371 return RunnableGeneric(suite, parent, arch) |
372 elif suite.get("benchmarks"): | 372 elif suite.get("tests"): |
373 # This is neither a leaf nor a runnable. | 373 # This is neither a leaf nor a runnable. |
374 return Graph(suite, parent, arch) | 374 return Graph(suite, parent, arch) |
375 else: # pragma: no cover | 375 else: # pragma: no cover |
376 raise Exception("Invalid benchmark suite configuration.") | 376 raise Exception("Invalid suite configuration.") |
377 | 377 |
378 | 378 |
379 def BuildGraphs(suite, arch, parent=None): | 379 def BuildGraphs(suite, arch, parent=None): |
380 """Builds a tree structure of graph objects that corresponds to the suite | 380 """Builds a tree structure of graph objects that corresponds to the suite |
381 configuration. | 381 configuration. |
382 """ | 382 """ |
383 parent = parent or DefaultSentinel() | 383 parent = parent or DefaultSentinel() |
384 | 384 |
385 # TODO(machenbach): Implement notion of cpu type? | 385 # TODO(machenbach): Implement notion of cpu type? |
386 if arch not in suite.get("archs", ["ia32", "x64"]): | 386 if arch not in suite.get("archs", ["ia32", "x64"]): |
387 return None | 387 return None |
388 | 388 |
389 graph = MakeGraph(suite, arch, parent) | 389 graph = MakeGraph(suite, arch, parent) |
390 for subsuite in suite.get("benchmarks", []): | 390 for subsuite in suite.get("tests", []): |
391 BuildGraphs(subsuite, arch, graph) | 391 BuildGraphs(subsuite, arch, graph) |
392 parent.AppendChild(graph) | 392 parent.AppendChild(graph) |
393 return graph | 393 return graph |
394 | 394 |
395 | 395 |
396 def FlattenRunnables(node): | 396 def FlattenRunnables(node): |
397 """Generator that traverses the tree structure and iterates over all | 397 """Generator that traverses the tree structure and iterates over all |
398 runnables. | 398 runnables. |
399 """ | 399 """ |
400 if isinstance(node, Runnable): | 400 if isinstance(node, Runnable): |
401 yield node | 401 yield node |
402 elif isinstance(node, Node): | 402 elif isinstance(node, Node): |
403 for child in node._children: | 403 for child in node._children: |
404 for result in FlattenRunnables(child): | 404 for result in FlattenRunnables(child): |
405 yield result | 405 yield result |
406 else: # pragma: no cover | 406 else: # pragma: no cover |
407 raise Exception("Invalid benchmark suite configuration.") | 407 raise Exception("Invalid suite configuration.") |
408 | 408 |
409 | 409 |
410 # TODO: Implement results_processor. | 410 # TODO: Implement results_processor. |
411 def Main(args): | 411 def Main(args): |
412 parser = optparse.OptionParser() | 412 parser = optparse.OptionParser() |
413 parser.add_option("--arch", | 413 parser.add_option("--arch", |
414 help=("The architecture to run tests for, " | 414 help=("The architecture to run tests for, " |
415 "'auto' or 'native' for auto-detect"), | 415 "'auto' or 'native' for auto-detect"), |
416 default="x64") | 416 default="x64") |
417 parser.add_option("--buildbot", | 417 parser.add_option("--buildbot", |
(...skipping 22 matching lines...) Expand all Loading... |
440 shell_dir = os.path.join(workspace, options.outdir, "Release") | 440 shell_dir = os.path.join(workspace, options.outdir, "Release") |
441 else: | 441 else: |
442 shell_dir = os.path.join(workspace, options.outdir, | 442 shell_dir = os.path.join(workspace, options.outdir, |
443 "%s.release" % options.arch) | 443 "%s.release" % options.arch) |
444 | 444 |
445 results = Results() | 445 results = Results() |
446 for path in args: | 446 for path in args: |
447 path = os.path.abspath(path) | 447 path = os.path.abspath(path) |
448 | 448 |
449 if not os.path.exists(path): # pragma: no cover | 449 if not os.path.exists(path): # pragma: no cover |
450 results.errors.append("Benchmark file %s does not exist." % path) | 450 results.errors.append("Configuration file %s does not exist." % path) |
451 continue | 451 continue |
452 | 452 |
453 with open(path) as f: | 453 with open(path) as f: |
454 suite = json.loads(f.read()) | 454 suite = json.loads(f.read()) |
455 | 455 |
456 # If no name is given, default to the file name without .json. | 456 # If no name is given, default to the file name without .json. |
457 suite.setdefault("name", os.path.splitext(os.path.basename(path))[0]) | 457 suite.setdefault("name", os.path.splitext(os.path.basename(path))[0]) |
458 | 458 |
459 for runnable in FlattenRunnables(BuildGraphs(suite, options.arch)): | 459 for runnable in FlattenRunnables(BuildGraphs(suite, options.arch)): |
460 print ">>> Running suite: %s" % "/".join(runnable.graphs) | 460 print ">>> Running suite: %s" % "/".join(runnable.graphs) |
(...skipping 18 matching lines...) Expand all Loading... |
479 | 479 |
480 if options.json_test_results: | 480 if options.json_test_results: |
481 results.WriteToFile(options.json_test_results) | 481 results.WriteToFile(options.json_test_results) |
482 else: # pragma: no cover | 482 else: # pragma: no cover |
483 print results | 483 print results |
484 | 484 |
485 return min(1, len(results.errors)) | 485 return min(1, len(results.errors)) |
486 | 486 |
487 if __name__ == "__main__": # pragma: no cover | 487 if __name__ == "__main__": # pragma: no cover |
488 sys.exit(Main(sys.argv[1:])) | 488 sys.exit(Main(sys.argv[1:])) |
OLD | NEW |