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

Side by Side Diff: tools/run_perf.py

Issue 1227033002: [test] Refactoring - Make perf suite definitions stateless regarding measurements. (Closed) Base URL: https://chromium.googlesource.com/v8/v8.git@master
Patch Set: Created 5 years, 5 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
« 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/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
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
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
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:]))
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