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-perf.py --arch ia32 some_suite.json | 9 Call e.g. with tools/run-perf.py --arch ia32 some_suite.json |
10 | 10 |
(...skipping 151 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
162 | 162 |
163 def __add__(self, other): | 163 def __add__(self, other): |
164 self.traces += other.traces | 164 self.traces += other.traces |
165 self.errors += other.errors | 165 self.errors += other.errors |
166 return self | 166 return self |
167 | 167 |
168 def __str__(self): # pragma: no cover | 168 def __str__(self): # pragma: no cover |
169 return str(self.ToDict()) | 169 return str(self.ToDict()) |
170 | 170 |
171 | 171 |
172 class Measurement(object): | |
173 """Represents a series of results of one trace. | |
Michael Achenbach
2015/07/08 10:03:19
Basically copy from line 259.
| |
174 | |
175 The results are from repetitive runs of the same executable. They are | |
176 gathered by repeated calls to ConsumeOutput. | |
177 """ | |
178 def __init__(self, graphs, units, results_regexp, stddev_regexp): | |
179 self.name = graphs[-1] | |
180 self.graphs = graphs | |
181 self.units = units | |
182 self.results_regexp = results_regexp | |
183 self.stddev_regexp = stddev_regexp | |
184 self.results = [] | |
185 self.errors = [] | |
186 self.stddev = "" | |
187 | |
188 def ConsumeOutput(self, stdout): | |
189 try: | |
190 result = re.search(self.results_regexp, stdout, re.M).group(1) | |
191 self.results.append(str(float(result))) | |
192 except ValueError: | |
193 self.errors.append("Regexp \"%s\" returned a non-numeric for test %s." | |
194 % (self.results_regexp, self.name)) | |
195 except: | |
196 self.errors.append("Regexp \"%s\" didn't match for test %s." | |
197 % (self.results_regexp, self.name)) | |
198 | |
199 try: | |
200 if self.stddev_regexp and self.stddev: | |
201 self.errors.append("Test %s should only run once since a stddev " | |
202 "is provided by the test." % self.name) | |
203 if self.stddev_regexp: | |
204 self.stddev = re.search(self.stddev_regexp, stdout, re.M).group(1) | |
205 except: | |
206 self.errors.append("Regexp \"%s\" didn't match for test %s." | |
207 % (self.stddev_regexp, self.name)) | |
208 | |
209 def GetResults(self): | |
210 return Results([{ | |
211 "graphs": self.graphs, | |
212 "units": self.units, | |
213 "results": self.results, | |
214 "stddev": self.stddev, | |
215 }], self.errors) | |
216 | |
217 | |
218 def AccumulateResults(graph_names, traces, iter_output, calc_total): | |
219 """Iterates over the output of multiple benchmark reruns and accumulates | |
Michael Achenbach
2015/07/08 10:03:19
Basically copy from line 320.
| |
220 results for a configured list of traces. | |
221 | |
222 Args: | |
223 graph_names: List of names that configure the base path of the traces. E.g. | |
224 ['v8', 'Octane']. | |
225 traces: List of "Trace" instances. Each trace defines how to perform a | |
226 measurement. | |
227 iter_output: Iterator over the standard output of each test run. | |
228 calc_total: Boolean flag to speficy the calculation of a summary trace. | |
229 Returns: A "Results" object. | |
230 """ | |
231 measurements = [trace.CreateMeasurement() for trace in traces] | |
232 for stdout in iter_output(): | |
233 for measurement in measurements: | |
234 measurement.ConsumeOutput(stdout) | |
235 | |
236 res = reduce(lambda r, t: r + t.GetResults(), measurements, Results()) | |
237 | |
238 if not res.traces or not calc_total: | |
239 return res | |
240 | |
241 # Assume all traces have the same structure. | |
242 if len(set(map(lambda t: len(t["results"]), res.traces))) != 1: | |
243 res.errors.append("Not all traces have the same number of results.") | |
244 return res | |
245 | |
246 # Calculate the geometric means for all traces. Above we made sure that | |
247 # there is at least one trace and that the number of results is the same | |
248 # for each trace. | |
249 n_results = len(res.traces[0]["results"]) | |
250 total_results = [GeometricMean(t["results"][i] for t in res.traces) | |
251 for i in range(0, n_results)] | |
252 res.traces.append({ | |
253 "graphs": graph_names + ["Total"], | |
254 "units": res.traces[0]["units"], | |
255 "results": total_results, | |
256 "stddev": "", | |
257 }) | |
258 return res | |
259 | |
260 | |
261 def AccumulateGenericResults(graph_names, suite_units, iter_output): | |
262 """Iterates over the output of multiple benchmark reruns and accumulates | |
Michael Achenbach
2015/07/08 10:03:19
Basically copy from line 365.
| |
263 generic results. | |
264 | |
265 Args: | |
266 graph_names: List of names that configure the base path of the traces. E.g. | |
267 ['v8', 'Octane']. | |
268 suite_units: Measurement default units as defined by the benchmark suite. | |
269 iter_output: Iterator over the standard output of each test run. | |
270 Returns: A "Results" object. | |
271 """ | |
272 traces = OrderedDict() | |
273 for stdout in iter_output(): | |
274 for line in stdout.strip().splitlines(): | |
275 match = GENERIC_RESULTS_RE.match(line) | |
276 if match: | |
277 stddev = "" | |
278 graph = match.group(1) | |
279 trace = match.group(2) | |
280 body = match.group(3) | |
281 units = match.group(4) | |
282 match_stddev = RESULT_STDDEV_RE.match(body) | |
283 match_list = RESULT_LIST_RE.match(body) | |
284 errors = [] | |
285 if match_stddev: | |
286 result, stddev = map(str.strip, match_stddev.group(1).split(",")) | |
287 results = [result] | |
288 elif match_list: | |
289 results = map(str.strip, match_list.group(1).split(",")) | |
290 else: | |
291 results = [body.strip()] | |
292 | |
293 try: | |
294 results = map(lambda r: str(float(r)), results) | |
295 except ValueError: | |
296 results = [] | |
297 errors = ["Found non-numeric in %s" % | |
298 "/".join(graph_names + [graph, trace])] | |
299 | |
300 trace_result = traces.setdefault(trace, Results([{ | |
301 "graphs": graph_names + [graph, trace], | |
302 "units": (units or suite_units).strip(), | |
303 "results": [], | |
304 "stddev": "", | |
305 }], errors)) | |
306 trace_result.traces[0]["results"].extend(results) | |
307 trace_result.traces[0]["stddev"] = stddev | |
308 | |
309 return reduce(lambda r, t: r + t, traces.itervalues(), Results()) | |
310 | |
311 | |
172 class Node(object): | 312 class Node(object): |
173 """Represents a node in the suite tree structure.""" | 313 """Represents a node in the suite tree structure.""" |
174 def __init__(self, *args): | 314 def __init__(self, *args): |
175 self._children = [] | 315 self._children = [] |
176 | 316 |
177 def AppendChild(self, child): | 317 def AppendChild(self, child): |
178 self._children.append(child) | 318 self._children.append(child) |
179 | 319 |
180 | 320 |
181 class DefaultSentinel(Node): | 321 class DefaultSentinel(Node): |
(...skipping 60 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
242 | 382 |
243 # A similar regular expression for the standard deviation (optional). | 383 # A similar regular expression for the standard deviation (optional). |
244 if parent.stddev_regexp: | 384 if parent.stddev_regexp: |
245 stddev_default = parent.stddev_regexp % re.escape(suite["name"]) | 385 stddev_default = parent.stddev_regexp % re.escape(suite["name"]) |
246 else: | 386 else: |
247 stddev_default = None | 387 stddev_default = None |
248 self.stddev_regexp = suite.get("stddev_regexp", stddev_default) | 388 self.stddev_regexp = suite.get("stddev_regexp", stddev_default) |
249 | 389 |
250 | 390 |
251 class Trace(Graph): | 391 class Trace(Graph): |
252 """Represents a leaf in the suite tree structure. | 392 """Represents a leaf in the suite tree structure.""" |
253 | |
254 Handles collection of measurements. | |
255 """ | |
256 def __init__(self, suite, parent, arch): | 393 def __init__(self, suite, parent, arch): |
257 super(Trace, self).__init__(suite, parent, arch) | 394 super(Trace, self).__init__(suite, parent, arch) |
258 assert self.results_regexp | 395 assert self.results_regexp |
259 self.results = [] | |
260 self.errors = [] | |
261 self.stddev = "" | |
262 | 396 |
263 def ConsumeOutput(self, stdout): | 397 def CreateMeasurement(self): |
264 try: | 398 return Measurement( |
265 result = re.search(self.results_regexp, stdout, re.M).group(1) | 399 self.graphs, |
266 self.results.append(str(float(result))) | 400 self.units, |
267 except ValueError: | 401 self.results_regexp, |
268 self.errors.append("Regexp \"%s\" returned a non-numeric for test %s." | 402 self.stddev_regexp, |
269 % (self.results_regexp, self.graphs[-1])) | 403 ) |
270 except: | |
271 self.errors.append("Regexp \"%s\" didn't match for test %s." | |
272 % (self.results_regexp, self.graphs[-1])) | |
273 | |
274 try: | |
275 if self.stddev_regexp and self.stddev: | |
276 self.errors.append("Test %s should only run once since a stddev " | |
277 "is provided by the test." % self.graphs[-1]) | |
278 if self.stddev_regexp: | |
279 self.stddev = re.search(self.stddev_regexp, stdout, re.M).group(1) | |
280 except: | |
281 self.errors.append("Regexp \"%s\" didn't match for test %s." | |
282 % (self.stddev_regexp, self.graphs[-1])) | |
283 | |
284 def GetResults(self): | |
285 return Results([{ | |
286 "graphs": self.graphs, | |
287 "units": self.units, | |
288 "results": self.results, | |
289 "stddev": self.stddev, | |
290 }], self.errors) | |
291 | 404 |
292 | 405 |
293 class Runnable(Graph): | 406 class Runnable(Graph): |
294 """Represents a runnable suite definition (i.e. has a main file). | 407 """Represents a runnable suite definition (i.e. has a main file). |
295 """ | 408 """ |
296 @property | 409 @property |
297 def main(self): | 410 def main(self): |
298 return self._suite.get("main", "") | 411 return self._suite.get("main", "") |
299 | 412 |
300 def ChangeCWD(self, suite_path): | 413 def ChangeCWD(self, suite_path): |
301 """Changes the cwd to to path defined in the current graph. | 414 """Changes the cwd to to path defined in the current graph. |
302 | 415 |
303 The tests are supposed to be relative to the suite configuration. | 416 The tests are supposed to be relative to the suite configuration. |
304 """ | 417 """ |
305 suite_dir = os.path.abspath(os.path.dirname(suite_path)) | 418 suite_dir = os.path.abspath(os.path.dirname(suite_path)) |
306 bench_dir = os.path.normpath(os.path.join(*self.path)) | 419 bench_dir = os.path.normpath(os.path.join(*self.path)) |
307 os.chdir(os.path.join(suite_dir, bench_dir)) | 420 os.chdir(os.path.join(suite_dir, bench_dir)) |
308 | 421 |
309 def GetCommandFlags(self, extra_flags=None): | 422 def GetCommandFlags(self, extra_flags=None): |
310 suffix = ["--"] + self.test_flags if self.test_flags else [] | 423 suffix = ["--"] + self.test_flags if self.test_flags else [] |
311 return self.flags + (extra_flags or []) + [self.main] + suffix | 424 return self.flags + (extra_flags or []) + [self.main] + suffix |
312 | 425 |
313 def GetCommand(self, shell_dir, extra_flags=None): | 426 def GetCommand(self, shell_dir, extra_flags=None): |
314 # TODO(machenbach): This requires +.exe if run on windows. | 427 # TODO(machenbach): This requires +.exe if run on windows. |
315 cmd = [os.path.join(shell_dir, self.binary)] | 428 cmd = [os.path.join(shell_dir, self.binary)] |
316 return cmd + self.GetCommandFlags(extra_flags=extra_flags) | 429 return cmd + self.GetCommandFlags(extra_flags=extra_flags) |
317 | 430 |
318 def Run(self, runner): | 431 def Run(self, runner): |
319 """Iterates over several runs and handles the output for all traces.""" | 432 """Iterates over several runs and handles the output for all traces.""" |
320 for stdout in runner(): | 433 return AccumulateResults(self.graphs, self._children, runner, self.total) |
321 for trace in self._children: | |
322 trace.ConsumeOutput(stdout) | |
323 res = reduce(lambda r, t: r + t.GetResults(), self._children, Results()) | |
324 | 434 |
325 if not res.traces or not self.total: | |
326 return res | |
327 | |
328 # Assume all traces have the same structure. | |
329 if len(set(map(lambda t: len(t["results"]), res.traces))) != 1: | |
330 res.errors.append("Not all traces have the same number of results.") | |
331 return res | |
332 | |
333 # Calculate the geometric means for all traces. Above we made sure that | |
334 # there is at least one trace and that the number of results is the same | |
335 # for each trace. | |
336 n_results = len(res.traces[0]["results"]) | |
337 total_results = [GeometricMean(t["results"][i] for t in res.traces) | |
338 for i in range(0, n_results)] | |
339 res.traces.append({ | |
340 "graphs": self.graphs + ["Total"], | |
341 "units": res.traces[0]["units"], | |
342 "results": total_results, | |
343 "stddev": "", | |
344 }) | |
345 return res | |
346 | 435 |
347 class RunnableTrace(Trace, Runnable): | 436 class RunnableTrace(Trace, Runnable): |
348 """Represents a runnable suite definition that is a leaf.""" | 437 """Represents a runnable suite definition that is a leaf.""" |
349 def __init__(self, suite, parent, arch): | 438 def __init__(self, suite, parent, arch): |
350 super(RunnableTrace, self).__init__(suite, parent, arch) | 439 super(RunnableTrace, self).__init__(suite, parent, arch) |
351 | 440 |
352 def Run(self, runner): | 441 def Run(self, runner): |
353 """Iterates over several runs and handles the output.""" | 442 """Iterates over several runs and handles the output.""" |
443 measurement = self.CreateMeasurement() | |
354 for stdout in runner(): | 444 for stdout in runner(): |
355 self.ConsumeOutput(stdout) | 445 measurement.ConsumeOutput(stdout) |
356 return self.GetResults() | 446 return measurement.GetResults() |
357 | 447 |
358 | 448 |
359 class RunnableGeneric(Runnable): | 449 class RunnableGeneric(Runnable): |
360 """Represents a runnable suite definition with generic traces.""" | 450 """Represents a runnable suite definition with generic traces.""" |
361 def __init__(self, suite, parent, arch): | 451 def __init__(self, suite, parent, arch): |
362 super(RunnableGeneric, self).__init__(suite, parent, arch) | 452 super(RunnableGeneric, self).__init__(suite, parent, arch) |
363 | 453 |
364 def Run(self, runner): | 454 def Run(self, runner): |
365 """Iterates over several runs and handles the output.""" | 455 return AccumulateGenericResults(self.graphs, self.units, runner) |
366 traces = OrderedDict() | |
367 for stdout in runner(): | |
368 for line in stdout.strip().splitlines(): | |
369 match = GENERIC_RESULTS_RE.match(line) | |
370 if match: | |
371 stddev = "" | |
372 graph = match.group(1) | |
373 trace = match.group(2) | |
374 body = match.group(3) | |
375 units = match.group(4) | |
376 match_stddev = RESULT_STDDEV_RE.match(body) | |
377 match_list = RESULT_LIST_RE.match(body) | |
378 errors = [] | |
379 if match_stddev: | |
380 result, stddev = map(str.strip, match_stddev.group(1).split(",")) | |
381 results = [result] | |
382 elif match_list: | |
383 results = map(str.strip, match_list.group(1).split(",")) | |
384 else: | |
385 results = [body.strip()] | |
386 | |
387 try: | |
388 results = map(lambda r: str(float(r)), results) | |
389 except ValueError: | |
390 results = [] | |
391 errors = ["Found non-numeric in %s" % | |
392 "/".join(self.graphs + [graph, trace])] | |
393 | |
394 trace_result = traces.setdefault(trace, Results([{ | |
395 "graphs": self.graphs + [graph, trace], | |
396 "units": (units or self.units).strip(), | |
397 "results": [], | |
398 "stddev": "", | |
399 }], errors)) | |
400 trace_result.traces[0]["results"].extend(results) | |
401 trace_result.traces[0]["stddev"] = stddev | |
402 | |
403 return reduce(lambda r, t: r + t, traces.itervalues(), Results()) | |
404 | 456 |
405 | 457 |
406 def MakeGraph(suite, arch, parent): | 458 def MakeGraph(suite, arch, parent): |
407 """Factory method for making graph objects.""" | 459 """Factory method for making graph objects.""" |
408 if isinstance(parent, Runnable): | 460 if isinstance(parent, Runnable): |
409 # Below a runnable can only be traces. | 461 # Below a runnable can only be traces. |
410 return Trace(suite, parent, arch) | 462 return Trace(suite, parent, arch) |
411 elif suite.get("main") is not None: | 463 elif suite.get("main") is not None: |
412 # A main file makes this graph runnable. Empty strings are accepted. | 464 # A main file makes this graph runnable. Empty strings are accepted. |
413 if suite.get("tests"): | 465 if suite.get("tests"): |
(...skipping 313 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
727 | 779 |
728 if options.json_test_results: | 780 if options.json_test_results: |
729 results.WriteToFile(options.json_test_results) | 781 results.WriteToFile(options.json_test_results) |
730 else: # pragma: no cover | 782 else: # pragma: no cover |
731 print results | 783 print results |
732 | 784 |
733 return min(1, len(results.errors)) | 785 return min(1, len(results.errors)) |
734 | 786 |
735 if __name__ == "__main__": # pragma: no cover | 787 if __name__ == "__main__": # pragma: no cover |
736 sys.exit(Main(sys.argv[1:])) | 788 sys.exit(Main(sys.argv[1:])) |
OLD | NEW |