1 # Copyright 2014 The Chromium Authors. All rights reserved.
2 # Use of this source code is governed by a BSD-style license that can be
3 # found in the LICENSE file.
5 """This file contains printing-related functionality of the bisect."""
10 from bisect_results
import BisectResults
15 # The perf dashboard looks for a string like "Estimated Confidence: 95%"
16 # to decide whether or not to cc the author(s). If you change this, please
17 # update the perf dashboard as well.
19 ===== BISECT JOB RESULTS =====
22 Test Command: %(command)s
23 Test Metric: %(metric)s
24 Relative Change: %(change)s
25 Estimated Confidence: %(confidence).02f%%
26 Retested CL with revert: %(retest)s"""
28 # When the bisect was aborted without a bisect failure the following template
30 ABORT_REASON_TEMPLATE
= """
31 ===== BISECTION ABORTED =====
32 The bisect was aborted because %(abort_reason)s
33 Please contact the the team (see below) if you believe this is in error.
37 Test Command: %(command)s
38 Test Metric: %(metric)s
39 Good revision: %(good_revision)s
40 Bad revision: %(bad_revision)s """
42 # The perf dashboard specifically looks for the string
43 # "Author : " to parse out who to cc on a bug. If you change the
44 # formatting here, please update the perf dashboard as well.
45 RESULTS_REVISION_INFO
= """
46 ===== SUSPECTED CL(s) =====
48 Author : %(author)s%(commit_info)s
52 RESULTS_THANKYOU
= """
53 | O O | Visit http://www.chromium.org/developers/speed-infra/perf-bug-faq
54 | X | for more information addressing perf regression bugs. For feedback,
55 | / \\ | file a bug with label Cr-Tests-AutoBisect. Thank you!"""
58 class BisectPrinter(object):
60 def __init__(self
, opts
, depot_registry
=None):
62 self
.depot_registry
= depot_registry
64 def FormatAndPrintResults(self
, bisect_results
):
65 """Prints the results from a bisection run in a readable format.
67 Also prints annotations creating buildbot step "Results".
70 bisect_results: BisectResult object containing results to be printed.
72 if bisect_results
.abort_reason
:
73 self
._PrintAbortResults
(bisect_results
.abort_reason
)
76 if self
.opts
.output_buildbot_annotations
:
77 bisect_utils
.OutputAnnotationStepStart('Build Status Per Revision')
80 print 'Full results of bisection:'
81 for revision_state
in bisect_results
.state
.GetRevisionStates():
82 build_status
= revision_state
.passed
84 if type(build_status
) is bool:
90 print ' %20s %40s %s' % (revision_state
.depot
,
91 revision_state
.revision
,
95 if self
.opts
.output_buildbot_annotations
:
96 bisect_utils
.OutputAnnotationStepClosed()
97 # The perf dashboard scrapes the "results" step in order to comment on
98 # bugs. If you change this, please update the perf dashboard as well.
99 bisect_utils
.OutputAnnotationStepStart('Results')
101 self
._PrintBanner
(bisect_results
)
102 self
._PrintWarnings
(bisect_results
.warnings
)
104 if bisect_results
.culprit_revisions
and bisect_results
.confidence
:
105 for culprit
in bisect_results
.culprit_revisions
:
106 cl
, info
, depot
= culprit
107 self
._PrintRevisionInfo
(cl
, info
, depot
)
108 self
._PrintRetestResults
(bisect_results
)
109 self
._PrintTestedCommitsTable
(bisect_results
.state
.GetRevisionStates(),
110 bisect_results
.first_working_revision
,
111 bisect_results
.last_broken_revision
,
112 bisect_results
.confidence
,
114 self
._PrintStepTime
(bisect_results
.state
.GetRevisionStates())
115 self
._PrintThankYou
()
116 if self
.opts
.output_buildbot_annotations
:
117 bisect_utils
.OutputAnnotationStepClosed()
119 def PrintPartialResults(self
, bisect_state
):
120 revision_states
= bisect_state
.GetRevisionStates()
121 first_working_rev
, last_broken_rev
= BisectResults
.FindBreakingRevRange(
123 self
._PrintTestedCommitsTable
(revision_states
, first_working_rev
,
124 last_broken_rev
, 100, final_step
=False)
126 def _PrintAbortResults(self
, abort_reason
):
127 if self
.opts
.output_buildbot_annotations
:
128 bisect_utils
.OutputAnnotationStepStart('Results')
130 # Metric string in config is not split in case of return code mode.
131 if (self
.opts
.metric
and
132 self
.opts
.bisect_mode
!= bisect_utils
.BISECT_MODE_RETURN_CODE
):
133 metric
= '/'.join(self
.opts
.metric
)
135 metric
= self
.opts
.metric
137 print ABORT_REASON_TEMPLATE
% {
138 'abort_reason': abort_reason
,
139 'bug_id': self
.opts
.bug_id
or 'NOT SPECIFIED',
140 'command': self
.opts
.command
,
142 'good_revision': self
.opts
.good_revision
,
143 'bad_revision': self
.opts
.bad_revision
,
145 self
._PrintThankYou
()
146 if self
.opts
.output_buildbot_annotations
:
147 bisect_utils
.OutputAnnotationStepClosed()
150 def _PrintThankYou():
151 print RESULTS_THANKYOU
154 def _PrintStepTime(revision_states
):
155 """Prints information about how long various steps took.
158 revision_states: Ordered list of revision states."""
159 step_perf_time_avg
= 0.0
160 step_build_time_avg
= 0.0
162 for revision_state
in revision_states
:
163 if revision_state
.value
:
164 step_perf_time_avg
+= revision_state
.perf_time
165 step_build_time_avg
+= revision_state
.build_time
168 step_perf_time_avg
= step_perf_time_avg
/ step_count
169 step_build_time_avg
= step_build_time_avg
/ step_count
171 print 'Average build time : %s' % datetime
.timedelta(
172 seconds
=int(step_build_time_avg
))
173 print 'Average test time : %s' % datetime
.timedelta(
174 seconds
=int(step_perf_time_avg
))
177 def _GetViewVCLinkFromDepotAndHash(git_revision
, depot
):
178 """Gets link to the repository browser."""
179 if depot
and 'viewvc' in bisect_utils
.DEPOT_DEPS_NAME
[depot
]:
180 return bisect_utils
.DEPOT_DEPS_NAME
[depot
]['viewvc'] + git_revision
183 def _PrintRevisionInfo(self
, cl
, info
, depot
=None):
184 commit_link
= self
._GetViewVCLinkFromDepotAndHash
(cl
, depot
)
186 commit_link
= '\nLink : %s' % commit_link
188 commit_link
= ('\Description:\n%s' % info
['body'])
189 print RESULTS_REVISION_INFO
% {
190 'subject': info
['subject'],
191 'author': info
['email'],
192 'commit_info': commit_link
,
194 'cl_date': info
['date']
198 def _PrintTableRow(column_widths
, row_data
):
199 """Prints out a row in a formatted table that has columns aligned.
202 column_widths: A list of column width numbers.
203 row_data: A list of items for each column in this row.
205 assert len(column_widths
) == len(row_data
)
207 for i
in xrange(len(column_widths
)):
208 current_row_data
= row_data
[i
].center(column_widths
[i
], ' ')
209 text
+= ('%%%ds' % column_widths
[i
]) % current_row_data
212 def _PrintTestedCommitsHeader(self
):
213 if self
.opts
.bisect_mode
== bisect_utils
.BISECT_MODE_MEAN
:
215 [20, 12, 70, 14, 12, 13],
216 ['Depot', 'Position', 'SHA', 'Mean', 'Std. Error', 'State'])
217 elif self
.opts
.bisect_mode
== bisect_utils
.BISECT_MODE_STD_DEV
:
219 [20, 12, 70, 14, 12, 13],
220 ['Depot', 'Position', 'SHA', 'Std. Error', 'Mean', 'State'])
221 elif self
.opts
.bisect_mode
== bisect_utils
.BISECT_MODE_RETURN_CODE
:
223 [20, 12, 70, 14, 13],
224 ['Depot', 'Position', 'SHA', 'Return Code', 'State'])
226 assert False, 'Invalid bisect_mode specified.'
228 def _PrintTestedCommitsEntry(self
, revision_state
, commit_position
, cl_link
,
230 if self
.opts
.bisect_mode
== bisect_utils
.BISECT_MODE_MEAN
:
231 std_error
= '+-%.02f' % revision_state
.value
['std_err']
232 mean
= '%.02f' % revision_state
.value
['mean']
234 [20, 12, 70, 12, 14, 13],
235 [revision_state
.depot
, commit_position
, cl_link
, mean
, std_error
,
237 elif self
.opts
.bisect_mode
== bisect_utils
.BISECT_MODE_STD_DEV
:
238 std_error
= '+-%.02f' % revision_state
.value
['std_err']
239 mean
= '%.02f' % revision_state
.value
['mean']
241 [20, 12, 70, 12, 14, 13],
242 [revision_state
.depot
, commit_position
, cl_link
, std_error
, mean
,
244 elif self
.opts
.bisect_mode
== bisect_utils
.BISECT_MODE_RETURN_CODE
:
245 mean
= '%d' % revision_state
.value
['mean']
247 [20, 12, 70, 14, 13],
248 [revision_state
.depot
, commit_position
, cl_link
, mean
,
251 def _PrintTestedCommitsTable(
252 self
, revision_states
, first_working_revision
, last_broken_revision
,
253 confidence
, final_step
=True):
256 print '===== TESTED COMMITS ====='
258 print '===== PARTIAL RESULTS ====='
259 self
._PrintTestedCommitsHeader
()
261 for revision_state
in revision_states
:
262 if revision_state
.value
:
263 if (revision_state
== last_broken_revision
or
264 revision_state
== first_working_revision
):
265 # If confidence is too low, don't add this empty line since it's
266 # used to put focus on a suspected CL.
267 if confidence
and final_step
:
270 if state
== 2 and not final_step
:
271 # Just want a separation between "bad" and "good" cl's.
275 if state
== 1 and final_step
:
276 state_str
= 'Suspected CL'
280 # If confidence is too low, don't bother outputting good/bad.
283 state_str
= state_str
.center(13, ' ')
284 commit_position
= source_control
.GetCommitPosition(
285 revision_state
.revision
,
286 self
.depot_registry
.GetDepotDir(revision_state
.depot
))
287 display_commit_pos
= ''
289 display_commit_pos
= str(commit_position
)
290 self
._PrintTestedCommitsEntry
(revision_state
,
292 revision_state
.revision
,
295 def _PrintRetestResults(self
, bisect_results
):
296 if (not bisect_results
.retest_results_tot
or
297 not bisect_results
.retest_results_reverted
):
300 print '===== RETEST RESULTS ====='
301 self
._PrintTestedCommitsEntry
(
302 bisect_results
.retest_results_tot
, '', '', '')
303 self
._PrintTestedCommitsEntry
(
304 bisect_results
.retest_results_reverted
, '', '', '')
306 def _PrintBanner(self
, bisect_results
):
307 if self
.opts
.bisect_mode
== bisect_utils
.BISECT_MODE_RETURN_CODE
:
311 metric
= '/'.join(self
.opts
.metric
)
312 change
= '%.02f%% (+/-%.02f%%)' % (
313 bisect_results
.regression_size
, bisect_results
.regression_std_err
)
314 if not bisect_results
.culprit_revisions
:
315 change
= 'No significant change reproduced.'
317 print RESULTS_BANNER
% {
318 'status': self
._StatusMessage
(bisect_results
),
319 'command': self
.opts
.command
,
322 'confidence': bisect_results
.confidence
,
323 'retest': 'Yes' if bisect_results
.retest_results_tot
else 'No',
327 def _StatusMessage(bisect_results
):
328 if bisect_results
.confidence
>= bisect_utils
.HIGH_CONFIDENCE
:
329 return 'Positive: Reproduced a change.'
330 elif bisect_results
.culprit_revisions
:
331 return 'Negative: Found possible suspect(s), but with low confidence.'
332 return 'Negative: Did not reproduce a change.'
335 def _PrintWarnings(warnings
):
336 """Prints a list of warning strings if there are any."""
341 for w
in set(warnings
):