| Index: dashboard/dashboard/bisect_report.py
|
| diff --git a/dashboard/dashboard/bisect_report.py b/dashboard/dashboard/bisect_report.py
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..00e6eef0278d665b99888fa4f5aa3b176ab0fc2e
|
| --- /dev/null
|
| +++ b/dashboard/dashboard/bisect_report.py
|
| @@ -0,0 +1,143 @@
|
| +# Copyright 2016 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.
|
| +
|
| +"""Generates reports base on bisect result data."""
|
| +
|
| +import copy
|
| +
|
| +_CONFIDENCE_THRESHOLD = 99.5
|
| +
|
| +_BISECT_REPORT_TEMPLATE = """
|
| +===== BISECT JOB RESULTS =====
|
| +Status: %(status)s
|
| +
|
| +%(result)s
|
| +
|
| +Bisect job ran on: %(bisect_bot)s
|
| +Bug ID: %(bug_id)s
|
| +
|
| +Test Command: %(command)s
|
| +Test Metric: %(metric)s
|
| +Relative Change: %(change)s
|
| +Score: %(score)s
|
| +
|
| +Buildbot stdio: %(buildbot_log_url)s
|
| +Job details: %(issue_url)s
|
| +
|
| +"""
|
| +
|
| +_RESULTS_REVISION_INFO = """
|
| +===== SUSPECTED CL(s) =====
|
| +Subject : %(subject)s
|
| +Author : %(author)s
|
| +Commit description:
|
| + %(commit_info)s
|
| +Commit : %(cl)s
|
| +Date : %(cl_date)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.
|
| +
|
| +"""
|
| +
|
| +_WARNINGS_TEMPLATE = """
|
| +=== Warnings ===
|
| +The following warnings were raised by the bisect job:
|
| +
|
| + * %(warnings)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!"""
|
| +
|
| +
|
| +def GetReport(try_job_entity):
|
| + """Generates a report for bisect results.
|
| +
|
| + This was ported from recipe_modules/auto_bisect/bisect_results.py.
|
| +
|
| + Args:
|
| + try_job_entity: A TryJob entity.
|
| +
|
| + Returns:
|
| + Bisect report string.
|
| + """
|
| + results_data = copy.deepcopy(try_job_entity.results_data)
|
| + if not results_data:
|
| + return ''
|
| + result = ''
|
| + if results_data.get('aborted_reason'):
|
| + result += _ABORT_REASON_TEMPLATE % results_data['aborted_reason']
|
| +
|
| + if results_data.get('warnings'):
|
| + result += _WARNINGS_TEMPLATE % results_data['warnings']
|
| +
|
| + if results_data.get('culprit_data'):
|
| + result += _RESULTS_REVISION_INFO % results_data['culprit_data']
|
| +
|
| + if results_data.get('revision_data'):
|
| + result += _RevisionTable(results_data)
|
| +
|
| + results_data['result'] = result
|
| + report = _BISECT_REPORT_TEMPLATE % results_data
|
| + report += _RESULTS_THANKYOU
|
| + return report
|
| +
|
| +
|
| +def _RevisionTable(results_data):
|
| + is_return_code = results_data.get('test_type') == 'return_code'
|
| + has_culprit = 'culprit_data' in results_data
|
| +
|
| + def RevisionRow(r):
|
| + result = [
|
| + r['depot_name'],
|
| + r['deps_revision'] or 'r' + str(r['commit_pos']),
|
| + _FormatNumber(r['mean_value']),
|
| + _FormatNumber(r['std_dev']),
|
| + len(r['values']),
|
| + r['result'],
|
| + '<-' if has_culprit == r else '',
|
| + ]
|
| + return map(str, result)
|
| + revision_rows = [RevisionRow(r) for r in results_data['revision_data']]
|
| +
|
| + headers_row = [[
|
| + 'Depot',
|
| + 'Revision',
|
| + 'Mean Value' if not is_return_code else 'Exit Code',
|
| + 'Std. Dev.',
|
| + 'Num Values',
|
| + 'Good?',
|
| + '',
|
| + ]]
|
| + all_rows = headers_row + revision_rows
|
| + return _REVISION_TABLE_TEMPLATE % {'table': _PrettyTable(all_rows)}
|
| +
|
| +
|
| +def _FormatNumber(x):
|
| + if x is None:
|
| + return 'N/A'
|
| + if isinstance(x, int):
|
| + return str(x)
|
| + return str(round(x, 6))
|
| +
|
| +
|
| +def _PrettyTable(data):
|
| + results = []
|
| + for row in data:
|
| + results.append(('%-12s' * len(row) % tuple(row)).rstrip())
|
| + return '\n'.join(results)
|
|
|