Index: scripts/slave/recipe_modules/auto_bisect/bisect_results.py |
diff --git a/scripts/slave/recipe_modules/auto_bisect/bisect_results.py b/scripts/slave/recipe_modules/auto_bisect/bisect_results.py |
deleted file mode 100644 |
index 0a84203d39f3d74f91c67c89ea98a3c3f8c48ac8..0000000000000000000000000000000000000000 |
--- a/scripts/slave/recipe_modules/auto_bisect/bisect_results.py |
+++ /dev/null |
@@ -1,276 +0,0 @@ |
-# Copyright 2015 The Chromium Authors. All rights reserved. |
-# Use of this source code is governed by a BSD-style license that can be |
-# found in the LICENSE file. |
- |
-import json |
-import re |
- |
-# Note: The Perf Dashboard specifically for the string "Status: Positive" when |
-# deciding whether to CC authors on the bug. |
-_RESULTS_BANNER = """ |
-===== BISECT JOB RESULTS ===== |
-Status: %(status)s |
- |
-Test Command: %(command)s |
-Test Metric: %(metric)s |
-Relative Change: %(change)s |
-Score: %(score)s |
-Retested CL with revert: %(retest)s |
- |
-""" |
- |
-# When the bisect was aborted without a bisect failure the following template |
-# is used. |
-_ABORT_REASON_TEMPLATE = """ |
-===== BISECTION ABORTED ===== |
-The bisect was aborted because %(abort_reason)s |
-Please contact the the team (see below) if you believe this is in error. |
- |
-Bug ID: %(bug_id)s |
- |
-Test Command: %(command)s |
-Test Metric: %(metric)s |
-Good revision: %(good_revision)s |
-Bad revision: %(bad_revision)s |
- |
-""" |
- |
-# The perf dashboard specifically looks for the string |
-# "Author : " to parse out who to cc on a bug. If you change the |
-# formatting here, please update the perf dashboard as well. |
-_RESULTS_REVISION_INFO = """ |
-===== SUSPECTED CL(s) ===== |
-Subject : %(subject)s |
-Author : %(author)s |
-Commit description: |
- %(commit_info)s |
-Commit : %(cl)s |
-Date : %(cl_date)s |
- |
-""" |
- |
-_REVISION_TABLE_TEMPLATE = """ |
-===== TESTED REVISIONS ===== |
-%(table)s |
- |
-""" |
- |
-_RESULTS_THANKYOU = """ |
-| O O | Visit http://www.chromium.org/developers/speed-infra/perf-bug-faq |
-| X | for more information addressing perf regression bugs. For feedback, |
-| / \\ | file a bug with label Cr-Tests-AutoBisect. Thank you!""" |
- |
- |
-_WARNINGS_TEMPLATE = """ |
-===== WARNINGS ===== |
-The following warnings were raised by the bisect job: |
- |
- * %(warnings)s |
- |
-""" |
- |
-_FAILED_INITIAL_CONFIDENCE_ABORT_REASON = ( |
- 'The metric values for the initial "good" and "bad" revisions ' |
- 'do not represent a clear regression.') |
- |
-_DIRECTION_OF_IMPROVEMENT_ABORT_REASON = ( |
- 'The metric values for the initial "good" and "bad" revisions match the ' |
- 'expected direction of improvement. Thus, likely represent an improvement ' |
- 'and not a regression.') |
- |
-_REQUIRED_RESULTS_CONFIDENCE = 95.0 |
- |
- |
-class BisectResults(object): |
- |
- def __init__(self, bisector, partial=False): |
- """Create a new results object from a finished bisect job.""" |
- if not bisector.bisect_over and not partial: |
- raise ValueError( |
- 'Invalid parameter, the bisect must be over by the time the ' |
- 'BisectResults constructor is called') # pragma: no cover |
- self._bisector = bisector |
- self.results_confidence = None |
- self.abort_reason = None |
- self.culprit_cl_hash = None |
- self.commit_info = None |
- self.culprit_author = None |
- self.culprit_subject = None |
- self.culprit_date = None |
- self.partial = partial |
- self._gather_results() |
- |
- def as_string(self): |
- return self._make_header() + self._make_body() + self._make_footer() |
- |
- def _make_header(self): |
- # Unconditionally include this string at the top of the results since it is |
- # used by the dashboard to separate the bisect results from other buildbot |
- # output. |
- if self.partial: |
- return '---partial bisect results start here---\n' |
- header = '---bisect results start here---\n' |
- if not self.abort_reason: |
- header += _RESULTS_BANNER % { |
- 'status': self.status, |
- 'command': self.command, |
- 'metric': self.metric, |
- 'change': self.relative_change, |
- 'score': self.results_confidence, |
- 'retest': 'Not Implemented.' |
- } |
- else: |
- header += _ABORT_REASON_TEMPLATE % { |
- 'abort_reason': self.abort_reason, |
- 'bug_id': self.bug_id, |
- 'command': self.command, |
- 'metric': self.metric, |
- 'good_revision': self.good_revision, |
- 'bad_revision': self.bad_revision |
- } |
- if self.warnings and not self.partial: |
- header += _WARNINGS_TEMPLATE % {'warnings': '\n * '.join(self.warnings)} |
- return header |
- |
- def _make_body(self): |
- body = '' |
- if self.culprit_cl_hash: |
- body += _RESULTS_REVISION_INFO % { |
- 'subject': self.culprit_subject, |
- 'author': self.culprit_author, |
- 'cl_date': self.culprit_date, |
- 'commit_info': self.commit_info, |
- 'cl': self.culprit_cl_hash |
- } |
- body += self._compose_revisions_table() |
- return body.encode('ascii', 'replace') |
- |
- def _make_footer(self): |
- if self.partial: |
- return '----End of partial results----' |
- return _RESULTS_THANKYOU |
- |
- def _gather_results(self): |
- # TODO(robertocn): Add viewcl link here. |
- # TODO(robertocn): Merge this into constructor. |
- bisector = self._bisector |
- config = bisector.bisect_config |
- |
- # TODO(robertocn): Add platform here. |
- self.relative_change = bisector.relative_change |
- self.warnings = bisector.warnings |
- self.command = config['command'] |
- self.metric = config['metric'] |
- self.bug_id = config.get('bug_id') |
- self.good_revision = bisector.good_rev.commit_hash |
- self.bad_revision = bisector.bad_rev.commit_hash |
- |
- self.is_telemetry = ('tools/perf/run_' in self.command or |
- 'tools\\perf\\run_' in self.command) |
- |
- if self.is_telemetry: |
- self.telemetry_command = re.sub(r'--browser=[^\s]+', |
- '--browser=<bot-name>', |
- self.command) |
- |
- self.culprit_cl_hash = None |
- if bisector.culprit: |
- self._set_culprit_attributes(bisector.culprit) |
- self.results_confidence = bisector.api.m.math_utils.confidence_score( |
- bisector.lkgr.values, bisector.fkbr.values) |
- |
- if bisector.failed_initial_confidence: |
- self.abort_reason = _FAILED_INITIAL_CONFIDENCE_ABORT_REASON |
- elif bisector.failed_direction: |
- self.abort_reason = _DIRECTION_OF_IMPROVEMENT_ABORT_REASON |
- |
- if self.partial: |
- self.status = 'Partial Results only.' |
- elif bisector.failed: |
- self.status = 'Negative: Failed to bisect.' |
- elif self.results_confidence > _REQUIRED_RESULTS_CONFIDENCE: |
- self.status = 'Positive: A suspected commit was found.' |
- self._bisector.surface_result('CULPRIT_FOUND') |
- else: |
- self.status = ('Negative: Completed, but no culprit was found with ' |
- 'high confidence.') |
- self._bisector.surface_result('LO_FINAL_CONF') |
- |
- def _set_culprit_attributes(self, culprit): |
- self.culprit_cl_hash = None |
- api = self._bisector.api |
- if culprit: |
- self.culprit_cl_hash = culprit.deps_revision or culprit.commit_hash |
- culprit_info = api.query_revision_info( |
- self.culprit_cl_hash, culprit.depot_name) |
- self.culprit_subject = culprit_info['subject'] |
- self.culprit_author = (culprit_info['author'] + ', ' + |
- culprit_info['email']) |
- self.commit_info = culprit_info['body'] |
- self.culprit_date = culprit_info['date'] |
- |
- def _compose_revisions_table(self): |
- def revision_row(r): |
- result = [ |
- r.depot_name, |
- r.deps_revision or 'r' + str(r.commit_pos), |
- _format_number(r.mean_value), |
- _format_number(r.std_dev), |
- len(r.values), |
- 'good' if r.good else 'bad' if r.bad else 'unknown', |
- '<-' if self._bisector.culprit == r else '', |
- ] |
- return map(str, result) |
- |
- is_return_code = self._bisector.is_return_code_mode() |
- headers_row = [[ |
- 'Depot', |
- 'Revision', |
- 'Mean Value' if not is_return_code else 'Exit Code', |
- 'Std. Dev.', |
- 'Num Values', |
- 'Good?', |
- '', |
- ]] |
- revision_rows = [revision_row(r) |
- for r in self._bisector.revisions |
- if r.tested or r.aborted] |
- all_rows = headers_row + revision_rows |
- return _REVISION_TABLE_TEMPLATE % {'table': pretty_table(all_rows)} |
- |
- |
-def _format_number(x): |
- if x is None: |
- return 'N/A' |
- if isinstance(x, int): |
- return str(x) |
- return str(round(x, 6)) |
- |
- |
-def pretty_table(data): |
- """Arrange a matrix of strings into an ascii table. |
- |
- This function was ripped off directly from somewhere in skia. It is |
- inefficient and so, should be avoided for large data sets. |
- |
- Args: |
- data (list): A list of lists of strings containing the data to tabulate. It |
- is expected to be rectangular. |
- |
- Returns: A multi-line string containing the data arranged in a tabular manner. |
- """ |
- result = '' |
- column_widths = [0] * len(data[0]) |
- for row in data: |
- column_widths = [max(longest_len, len(prop)) for |
- longest_len, prop in zip(column_widths, row)] |
- for row in data: |
- is_culprit_row = row[-1] == '<-' |
- if is_culprit_row: |
- result += '\n' |
- for prop, width in zip(row, column_widths): |
- result += prop.ljust(width + 1) |
- result += '\n' |
- if is_culprit_row: |
- result += '\n' |
- return result |