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

Unified 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: Review 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 side-by-side diff with in-line comments
Download patch
« no previous file with comments | « no previous file | no next file » | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: tools/run_perf.py
diff --git a/tools/run_perf.py b/tools/run_perf.py
index c04e4e77c3c2e70ecf26d46bb7658c396db25a8d..b13e9179c01341c48b89fc5fe9d2e9ef49672f1e 100755
--- a/tools/run_perf.py
+++ b/tools/run_perf.py
@@ -169,6 +169,146 @@ class Results(object):
return str(self.ToDict())
+class Measurement(object):
+ """Represents a series of results of one trace.
+
+ The results are from repetitive runs of the same executable. They are
+ gathered by repeated calls to ConsumeOutput.
+ """
+ def __init__(self, graphs, units, results_regexp, stddev_regexp):
+ self.name = graphs[-1]
+ self.graphs = graphs
+ self.units = units
+ self.results_regexp = results_regexp
+ self.stddev_regexp = stddev_regexp
+ self.results = []
+ self.errors = []
+ self.stddev = ""
+
+ def ConsumeOutput(self, stdout):
+ try:
+ result = re.search(self.results_regexp, stdout, re.M).group(1)
+ self.results.append(str(float(result)))
+ except ValueError:
+ self.errors.append("Regexp \"%s\" returned a non-numeric for test %s."
+ % (self.results_regexp, self.name))
+ except:
+ self.errors.append("Regexp \"%s\" didn't match for test %s."
+ % (self.results_regexp, self.name))
+
+ try:
+ if self.stddev_regexp and self.stddev:
+ self.errors.append("Test %s should only run once since a stddev "
+ "is provided by the test." % self.name)
+ if self.stddev_regexp:
+ self.stddev = re.search(self.stddev_regexp, stdout, re.M).group(1)
+ except:
+ self.errors.append("Regexp \"%s\" didn't match for test %s."
+ % (self.stddev_regexp, self.name))
+
+ def GetResults(self):
+ return Results([{
+ "graphs": self.graphs,
+ "units": self.units,
+ "results": self.results,
+ "stddev": self.stddev,
+ }], self.errors)
+
+
+def AccumulateResults(graph_names, trace_configs, iter_output, calc_total):
+ """Iterates over the output of multiple benchmark reruns and accumulates
+ results for a configured list of traces.
+
+ Args:
+ graph_names: List of names that configure the base path of the traces. E.g.
+ ['v8', 'Octane'].
+ trace_configs: List of "TraceConfig" instances. Each trace config defines
+ how to perform a measurement.
+ iter_output: Iterator over the standard output of each test run.
+ calc_total: Boolean flag to speficy the calculation of a summary trace.
+ Returns: A "Results" object.
+ """
+ measurements = [trace.CreateMeasurement() for trace in trace_configs]
+ for stdout in iter_output():
+ for measurement in measurements:
+ measurement.ConsumeOutput(stdout)
+
+ res = reduce(lambda r, m: r + m.GetResults(), measurements, Results())
+
+ if not res.traces or not calc_total:
+ return res
+
+ # Assume all traces have the same structure.
+ if len(set(map(lambda t: len(t["results"]), res.traces))) != 1:
+ res.errors.append("Not all traces have the same number of results.")
+ return res
+
+ # Calculate the geometric means for all traces. Above we made sure that
+ # there is at least one trace and that the number of results is the same
+ # for each trace.
+ n_results = len(res.traces[0]["results"])
+ total_results = [GeometricMean(t["results"][i] for t in res.traces)
+ for i in range(0, n_results)]
+ res.traces.append({
+ "graphs": graph_names + ["Total"],
+ "units": res.traces[0]["units"],
+ "results": total_results,
+ "stddev": "",
+ })
+ return res
+
+
+def AccumulateGenericResults(graph_names, suite_units, iter_output):
+ """Iterates over the output of multiple benchmark reruns and accumulates
+ generic results.
+
+ Args:
+ graph_names: List of names that configure the base path of the traces. E.g.
+ ['v8', 'Octane'].
+ suite_units: Measurement default units as defined by the benchmark suite.
+ iter_output: Iterator over the standard output of each test run.
+ Returns: A "Results" object.
+ """
+ traces = OrderedDict()
+ for stdout in iter_output():
+ for line in stdout.strip().splitlines():
+ match = GENERIC_RESULTS_RE.match(line)
+ if match:
+ stddev = ""
+ graph = match.group(1)
+ trace = match.group(2)
+ body = match.group(3)
+ units = match.group(4)
+ match_stddev = RESULT_STDDEV_RE.match(body)
+ match_list = RESULT_LIST_RE.match(body)
+ errors = []
+ if match_stddev:
+ result, stddev = map(str.strip, match_stddev.group(1).split(","))
+ results = [result]
+ elif match_list:
+ results = map(str.strip, match_list.group(1).split(","))
+ else:
+ results = [body.strip()]
+
+ try:
+ results = map(lambda r: str(float(r)), results)
+ except ValueError:
+ results = []
+ errors = ["Found non-numeric in %s" %
+ "/".join(graph_names + [graph, trace])]
+
+ trace_result = traces.setdefault(trace, Results([{
+ "graphs": graph_names + [graph, trace],
+ "units": (units or suite_units).strip(),
+ "results": [],
+ "stddev": "",
+ }], errors))
+ trace_result.traces[0]["results"].extend(results)
+ trace_result.traces[0]["stddev"] = stddev
+
+ return reduce(lambda r, t: r + t, traces.itervalues(), Results())
+
+
class Node(object):
"""Represents a node in the suite tree structure."""
def __init__(self, *args):
@@ -196,13 +336,13 @@ class DefaultSentinel(Node):
self.total = False
-class Graph(Node):
+class GraphConfig(Node):
"""Represents a suite definition.
Can either be a leaf or an inner node that provides default values.
"""
def __init__(self, suite, parent, arch):
- super(Graph, self).__init__()
+ super(GraphConfig, self).__init__()
self._suite = suite
assert isinstance(suite.get("path", []), list)
@@ -248,49 +388,22 @@ class Graph(Node):
self.stddev_regexp = suite.get("stddev_regexp", stddev_default)
-class Trace(Graph):
- """Represents a leaf in the suite tree structure.
-
- Handles collection of measurements.
- """
+class TraceConfig(GraphConfig):
+ """Represents a leaf in the suite tree structure."""
def __init__(self, suite, parent, arch):
- super(Trace, self).__init__(suite, parent, arch)
+ super(TraceConfig, self).__init__(suite, parent, arch)
assert self.results_regexp
- self.results = []
- self.errors = []
- self.stddev = ""
-
- def ConsumeOutput(self, stdout):
- try:
- result = re.search(self.results_regexp, stdout, re.M).group(1)
- self.results.append(str(float(result)))
- except ValueError:
- self.errors.append("Regexp \"%s\" returned a non-numeric for test %s."
- % (self.results_regexp, self.graphs[-1]))
- except:
- self.errors.append("Regexp \"%s\" didn't match for test %s."
- % (self.results_regexp, self.graphs[-1]))
-
- try:
- if self.stddev_regexp and self.stddev:
- self.errors.append("Test %s should only run once since a stddev "
- "is provided by the test." % self.graphs[-1])
- if self.stddev_regexp:
- self.stddev = re.search(self.stddev_regexp, stdout, re.M).group(1)
- except:
- self.errors.append("Regexp \"%s\" didn't match for test %s."
- % (self.stddev_regexp, self.graphs[-1]))
- def GetResults(self):
- return Results([{
- "graphs": self.graphs,
- "units": self.units,
- "results": self.results,
- "stddev": self.stddev,
- }], self.errors)
+ def CreateMeasurement(self):
+ return Measurement(
+ self.graphs,
+ self.units,
+ self.results_regexp,
+ self.stddev_regexp,
+ )
-class Runnable(Graph):
+class RunnableConfig(GraphConfig):
"""Represents a runnable suite definition (i.e. has a main file).
"""
@property
@@ -317,117 +430,56 @@ class Runnable(Graph):
def Run(self, runner):
"""Iterates over several runs and handles the output for all traces."""
- for stdout in runner():
- for trace in self._children:
- trace.ConsumeOutput(stdout)
- res = reduce(lambda r, t: r + t.GetResults(), self._children, Results())
-
- if not res.traces or not self.total:
- return res
-
- # Assume all traces have the same structure.
- if len(set(map(lambda t: len(t["results"]), res.traces))) != 1:
- res.errors.append("Not all traces have the same number of results.")
- return res
-
- # Calculate the geometric means for all traces. Above we made sure that
- # there is at least one trace and that the number of results is the same
- # for each trace.
- n_results = len(res.traces[0]["results"])
- total_results = [GeometricMean(t["results"][i] for t in res.traces)
- for i in range(0, n_results)]
- res.traces.append({
- "graphs": self.graphs + ["Total"],
- "units": res.traces[0]["units"],
- "results": total_results,
- "stddev": "",
- })
- return res
+ return AccumulateResults(self.graphs, self._children, runner, self.total)
-class RunnableTrace(Trace, Runnable):
+
+class RunnableTraceConfig(TraceConfig, RunnableConfig):
"""Represents a runnable suite definition that is a leaf."""
def __init__(self, suite, parent, arch):
- super(RunnableTrace, self).__init__(suite, parent, arch)
+ super(RunnableTraceConfig, self).__init__(suite, parent, arch)
def Run(self, runner):
"""Iterates over several runs and handles the output."""
+ measurement = self.CreateMeasurement()
for stdout in runner():
- self.ConsumeOutput(stdout)
- return self.GetResults()
+ measurement.ConsumeOutput(stdout)
+ return measurement.GetResults()
-class RunnableGeneric(Runnable):
+class RunnableGenericConfig(RunnableConfig):
"""Represents a runnable suite definition with generic traces."""
def __init__(self, suite, parent, arch):
- super(RunnableGeneric, self).__init__(suite, parent, arch)
+ super(RunnableGenericConfig, self).__init__(suite, parent, arch)
def Run(self, runner):
- """Iterates over several runs and handles the output."""
- traces = OrderedDict()
- for stdout in runner():
- for line in stdout.strip().splitlines():
- match = GENERIC_RESULTS_RE.match(line)
- if match:
- stddev = ""
- graph = match.group(1)
- trace = match.group(2)
- body = match.group(3)
- units = match.group(4)
- match_stddev = RESULT_STDDEV_RE.match(body)
- match_list = RESULT_LIST_RE.match(body)
- errors = []
- if match_stddev:
- result, stddev = map(str.strip, match_stddev.group(1).split(","))
- results = [result]
- elif match_list:
- results = map(str.strip, match_list.group(1).split(","))
- else:
- results = [body.strip()]
-
- try:
- results = map(lambda r: str(float(r)), results)
- except ValueError:
- results = []
- errors = ["Found non-numeric in %s" %
- "/".join(self.graphs + [graph, trace])]
-
- trace_result = traces.setdefault(trace, Results([{
- "graphs": self.graphs + [graph, trace],
- "units": (units or self.units).strip(),
- "results": [],
- "stddev": "",
- }], errors))
- trace_result.traces[0]["results"].extend(results)
- trace_result.traces[0]["stddev"] = stddev
-
- return reduce(lambda r, t: r + t, traces.itervalues(), Results())
-
-
-def MakeGraph(suite, arch, parent):
- """Factory method for making graph objects."""
- if isinstance(parent, Runnable):
+ return AccumulateGenericResults(self.graphs, self.units, runner)
+
+
+def MakeGraphConfig(suite, arch, parent):
+ """Factory method for making graph configuration objects."""
+ if isinstance(parent, RunnableConfig):
# Below a runnable can only be traces.
- return Trace(suite, parent, arch)
+ return TraceConfig(suite, parent, arch)
elif suite.get("main") is not None:
# A main file makes this graph runnable. Empty strings are accepted.
if suite.get("tests"):
# This graph has subgraphs (traces).
- return Runnable(suite, parent, arch)
+ return RunnableConfig(suite, parent, arch)
else:
# This graph has no subgraphs, it's a leaf.
- return RunnableTrace(suite, parent, arch)
+ return RunnableTraceConfig(suite, parent, arch)
elif suite.get("generic"):
# This is a generic suite definition. It is either a runnable executable
# or has a main js file.
- return RunnableGeneric(suite, parent, arch)
+ return RunnableGenericConfig(suite, parent, arch)
elif suite.get("tests"):
# This is neither a leaf nor a runnable.
- return Graph(suite, parent, arch)
+ return GraphConfig(suite, parent, arch)
else: # pragma: no cover
raise Exception("Invalid suite configuration.")
-def BuildGraphs(suite, arch, parent=None):
+def BuildGraphConfigs(suite, arch, parent=None):
"""Builds a tree structure of graph objects that corresponds to the suite
configuration.
"""
@@ -437,9 +489,9 @@ def BuildGraphs(suite, arch, parent=None):
if arch not in suite.get("archs", SUPPORTED_ARCHS):
return None
- graph = MakeGraph(suite, arch, parent)
+ graph = MakeGraphConfig(suite, arch, parent)
for subsuite in suite.get("tests", []):
- BuildGraphs(subsuite, arch, graph)
+ BuildGraphConfigs(subsuite, arch, graph)
parent.AppendChild(graph)
return graph
@@ -449,7 +501,7 @@ def FlattenRunnables(node, node_cb):
runnables.
"""
node_cb(node)
- if isinstance(node, Runnable):
+ if isinstance(node, RunnableConfig):
yield node
elif isinstance(node, Node):
for child in node._children:
@@ -483,7 +535,7 @@ class DesktopPlatform(Platform):
pass
def PreTests(self, node, path):
- if isinstance(node, Runnable):
+ if isinstance(node, RunnableConfig):
node.ChangeCWD(path)
def Run(self, runnable, count):
@@ -600,7 +652,7 @@ class AndroidPlatform(Platform): # pragma: no cover
skip_if_missing=True,
)
- if isinstance(node, Runnable):
+ if isinstance(node, RunnableConfig):
self._PushFile(bench_abs, node.main, bench_rel)
for resource in node.resources:
self._PushFile(bench_abs, resource, bench_rel)
@@ -703,7 +755,7 @@ def Main(args):
platform.PreExecution()
# Build the graph/trace tree structure.
- root = BuildGraphs(suite, options.arch)
+ root = BuildGraphConfigs(suite, options.arch)
# Callback to be called on each node on traversal.
def NodeCB(node):
« 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