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

Unified Diff: log_parser/process_log.py

Issue 177028: Detect perf regressions and speedups automatically.... (Closed) Base URL: svn://chrome-svn.corp.google.com/chrome/trunk/tools/buildbot/scripts/master/
Patch Set: '' Created 11 years, 4 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 | « factory/commands.py ('k') | no next file » | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: log_parser/process_log.py
===================================================================
--- log_parser/process_log.py (revision 24874)
+++ log_parser/process_log.py (working copy)
@@ -19,6 +19,8 @@
import chromium_config as config
import chromium_utils
+from buildbot.status import builder
+
READABLE_FILE_PERMISSIONS = int('644', 8)
EXECUTABLE_FILE_PERMISSIONS = int('755', 8)
@@ -26,6 +28,38 @@
# of graph names for use by the JS doing the plotting.
GRAPH_LIST = config.Master.perf_graph_list
+# perf_expectations.json holds performance expectations. It is a
+# JSON-formatted file with this format:
+#
+# {PERFID: {
+# RESULTTYPE: {"delta": DELTA, "var": VAR},
+# RESULTTYPE: {"delta": DELTA, "var": VAR},
+# ...
+# },
+# ...,
+# "loaded": true
+# }
+#
+# PERFID (string):
+# Perf mapping identifier of the form "build-perf-name/test-name",
+# (see factory/chromium_commands.py).
+#
+# RESULTTYPE (string):
+# A particular trace within a test (ie. t, vm_rss_f_r).
+#
+# DELTA (integer):
+# Delta tolerance (test - ref).
+#
+# VAR (integer):
+# Variance tolerance.
+#
+# Notes:
+# - Strings are quoted with " (not ').
+# - Comments are not allowed in JSON.
+#
+PERF_EXPECTATIONS = ('../scripts/master/log_parser/perf_expectations/' +
+ 'perf_expectations.json')
+
def FormatFloat(number):
"""Formats float with two decimal points."""
if number:
@@ -73,7 +107,7 @@
class PerformanceLogProcessor(object):
""" Parent class for performance log parsers. """
- def __init__(self, report_link=None, output_dir=None):
+ def __init__(self, report_link=None, output_dir=None, perf_name=None):
self._report_link = report_link
if output_dir is None:
output_dir = os.getcwd()
@@ -82,9 +116,107 @@
self._output_dir = output_dir
self._matches = {}
+ # Performance regression/speedup alerts.
+ self._perf_name = perf_name
+ self._actual_performance = None
+ self._expected_performance = None
+ self._result_types = []
+ self._perf_regress = []
+ self._var_regress = []
+ self._perf_improve = []
+ self._var_improve = []
+
# The revision isn't known until the Process() call.
self._revision = -1
+ def LoadPerformanceExpectations(self):
+ self._expected = {}
+ try:
+ perf_file = open(PERF_EXPECTATIONS, 'r')
+ except IOError, e:
+ raise
M-A Ruel 2009/08/31 20:31:12 That seems quite useless
+
+ perf_list = []
+ if perf_file:
+ try:
+ perf_list = simplejson.load(perf_file)
+ except ValueError:
+ perf_file.seek(0)
+ logging.error("Error parsing %s: '%s'" % (PERF_EXPECTATIONS,
+ perf_file.read().strip()))
M-A Ruel 2009/08/31 20:31:12 I'm not sure it's a good idea to read the file aft
+ perf_file.close()
+
+ # Find this perf/test entry
+ if perf_list and perf_list.has_key(self._perf_name):
+ self._expected_performance = perf_list[self._perf_name]
+
+ def PerformanceChangesAsText(self):
+ text = []
+
+ if len(self._perf_regress) > 0:
+ text.append("PERF_REGRESS: " + ', '.join(self._perf_regress))
+
+ if len(self._var_regress) > 0:
+ text.append("VAR_REGRESS: " + ', '.join(self._var_regress))
+
+ if len(self._perf_improve) > 0:
+ text.append("PERF_IMPROVE: " + ', '.join(self._perf_improve))
+
+ if len(self._var_improve) > 0:
+ text.append("VAR_IMPROVE: " + ', '.join(self._var_improve))
+
+ return text
+
+ def PerformanceChanges(self):
+ # Load performance expectations for this test.
+ self.LoadPerformanceExpectations()
+
+ # Return if no performance expectations or results were found.
+ if not self._expected_performance or not self._actual_performance:
+ return []
+
+ # Compare actual and expected results.
+ for type in self._result_types:
+ if not (self._expected_performance.has_key(type) and
+ self._actual_performance.has_key(type)):
+ # Skip result types we don't know about.
+ continue
+
+ expect = self._expected_performance[type]
+ actual = self._actual_performance[type]
+
+ # Set the high and low performance and variance tolerances. The actual
+ # delta and variance needs to be within 50% above and below this range to
+ # keep the performance test green. If the results fall above or below
+ # this range, the test will go red (signaling a regression) or orange
+ # (signaling a speedup).
+ high_perf = (expect['delta'] + 1.5*expect['var'])
+ low_perf = (expect['delta'] - 1.5*expect['var'])
+ high_var = 1.5*expect['var']
+ low_var = 0.5*expect['var']
+
+ if actual['delta'] > high_perf:
+ self._perf_regress.append(type)
+ elif actual['delta'] < low_perf:
+ self._perf_improve.append(type)
+
+ if actual['var'] > high_var:
+ self._var_regress.append(type)
+ elif actual['var'] < low_var:
+ self._var_improve.append(type)
+
+ return self.PerformanceChangesAsText()
+
+ def evaluateCommand(self, cmd):
+ if len(self._perf_regress) > 0 or len(self._var_regress) > 0:
+ return builder.FAILURE
+
+ if len(self._perf_improve) > 0 or len(self._var_improve) > 0:
+ return builder.WARNINGS
+
+ # There was no change in performance, report success.
+ return builder.SUCCESS
+
def Process(self, revision, data):
"""Invoked by the step with data from log file once it completes.
@@ -343,7 +475,7 @@
self.__CreateSummaryOutput()
if self._ShouldWriteResults():
self.__SaveGraphInfo()
- return self._text_summary
+ return self.PerformanceChanges() + self._text_summary
def _ProcessLine(self, line):
line_match = self.RESULTS_REGEX.match(line)
@@ -388,6 +520,43 @@
graph.traces[trace_name] = trace
self._graphs[graph_name] = graph
+ # Set actual performance data when we come across useful values.
+ #
+ # trace_name will be of the form "RESULTTYPE" or "RESULTTYPE_ref".
+ # A trace with _ref in its name refers to a reference build.
+ #
+ # Common result types for page cyclers: t, vm_rss_f_r, IO_b_b, etc.
+ # A test's result types vary between test types. Currently, a test
+ # only needs to output the appropriate text format to embed a new
+ # result type.
+
+ m = re.match(r"^(\w+)_ref$", trace_name)
+ if m:
+ is_ref_build = True
+ result_type = m.group(1)
+ else:
+ is_ref_build = False
+ result_type = trace_name
+
+ if not self._actual_performance:
+ self._actual_performance = {}
+
+ if not self._actual_performance.has_key(result_type):
+ self._actual_performance[result_type] = {}
+
+ actual = self._actual_performance[result_type]
+ if is_ref_build:
+ actual['ref'] = trace.value
+ else:
+ actual['test'] = trace.value
+ actual['var'] = trace.stddev
+
+ # If we have both the test and ref results, compute the delta for this
+ # result type.
+ if actual.has_key('test') and actual.has_key('ref'):
+ self._result_types.append(result_type)
+ actual['delta'] = actual['test'] - actual['ref']
+
def _CalculateStatistics(self, value_list, trace_name):
"""Returns a tuple (mean, standard deviation) from a list of values.
« no previous file with comments | « factory/commands.py ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698