| 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):
|
|
|