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

Side by Side Diff: scripts/slave/recipe_modules/auto_bisect/bisector.py

Issue 1573293002: Change auto_bisect to post results to perf dashboard. (Closed) Base URL: https://chromium.googlesource.com/chromium/tools/build.git@master
Patch Set: address comments Created 4 years, 11 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 unified diff | Download patch
OLDNEW
1 # Copyright 2015 The Chromium Authors. All rights reserved. 1 # Copyright 2015 The Chromium Authors. All rights reserved.
2 # Use of this source code is governed by a BSD-style license that can be 2 # Use of this source code is governed by a BSD-style license that can be
3 # found in the LICENSE file. 3 # found in the LICENSE file.
4 4
5 import json 5 import json
6 import re 6 import re
7 import urllib
7 8
8 from . import bisect_results
9 from . import depot_config 9 from . import depot_config
10 from . import revision_state 10 from . import revision_state
11 11
12 _DEPS_SHA_PATCH = """ 12 _DEPS_SHA_PATCH = """
13 diff --git DEPS.sha DEPS.sha 13 diff --git DEPS.sha DEPS.sha
14 new file mode 100644 14 new file mode 100644
15 --- /dev/null 15 --- /dev/null
16 +++ DEPS.sha 16 +++ DEPS.sha
17 @@ -0,0 +1 @@ 17 @@ -0,0 +1 @@
18 +%(deps_sha)s 18 +%(deps_sha)s
(...skipping 16 matching lines...) Expand all
35 'LO_INIT_CONF', # Bisect aborted early for lack of confidence. 35 'LO_INIT_CONF', # Bisect aborted early for lack of confidence.
36 'MISSING_METRIC', # The metric was not found in the test text/json output. 36 'MISSING_METRIC', # The metric was not found in the test text/json output.
37 'LO_FINAL_CONF', # The bisect completed without a culprit. 37 'LO_FINAL_CONF', # The bisect completed without a culprit.
38 ) 38 )
39 39
40 # When we look for the next revision to build, we search nearby revisions 40 # When we look for the next revision to build, we search nearby revisions
41 # looking for a revision that's already been archived. Since we don't want 41 # looking for a revision that's already been archived. Since we don't want
42 # to move *too* far from the original revision, we'll cap the search at 25%. 42 # to move *too* far from the original revision, we'll cap the search at 25%.
43 DEFAULT_SEARCH_RANGE_PERCENTAGE = 0.25 43 DEFAULT_SEARCH_RANGE_PERCENTAGE = 0.25
44 44
45 _FAILED_INITIAL_CONFIDENCE_ABORT_REASON = (
46 'The metric values for the initial "good" and "bad" revisions '
47 'do not represent a clear regression.')
48
49 _DIRECTION_OF_IMPROVEMENT_ABORT_REASON = (
50 'The metric values for the initial "good" and "bad" revisions match the '
51 'expected direction of improvement. Thus, likely represent an improvement '
52 'and not a regression.')
qyearsley 2016/01/17 22:49:19 Note: these are moved from bisect_results.py
53
45 54
46 class Bisector(object): 55 class Bisector(object):
47 """This class abstracts an ongoing bisect (or n-sect) job.""" 56 """This class abstracts an ongoing bisect (or n-sect) job."""
48 57
49 def __init__(self, api, bisect_config, revision_class, init_revisions=True): 58 def __init__(self, api, bisect_config, revision_class, init_revisions=True):
50 """Initializes the state of a new bisect job from a dictionary. 59 """Initializes the state of a new bisect job from a dictionary.
51 60
52 Note that the initial good_rev and bad_rev MUST resolve to a commit position 61 Note that the initial good_rev and bad_rev MUST resolve to a commit position
53 in the chromium repo. 62 in the chromium repo.
54 """ 63 """
(...skipping 402 matching lines...) Expand 10 before | Expand all | Expand 10 after
457 result += 'bisector.fkbr: %r\n\n' % self.fkbr 466 result += 'bisector.fkbr: %r\n\n' % self.fkbr
458 result += self._revision_value_table() 467 result += self._revision_value_table()
459 if (self.lkgr and self.lkgr.values and self.fkbr and self.fkbr.values): 468 if (self.lkgr and self.lkgr.values and self.fkbr and self.fkbr.values):
460 result += '\n' + self._t_test_results() 469 result += '\n' + self._t_test_results()
461 return result 470 return result
462 471
463 def _revision_value_table(self): 472 def _revision_value_table(self):
464 """Returns a string table showing revisions and their values.""" 473 """Returns a string table showing revisions and their values."""
465 header = [['Revision', 'Values']] 474 header = [['Revision', 'Values']]
466 rows = [[str(r.commit_pos), str(r.values)] for r in self.revisions] 475 rows = [[str(r.commit_pos), str(r.values)] for r in self.revisions]
467 return bisect_results.pretty_table(header + rows) 476 return self._pretty_table(header + rows)
477
478 def _pretty_table(self, data):
479 results = []
480 for row in data:
481 results.append('%-15s' * len(row) % tuple(row))
482 return '\n'.join(results)
468 483
469 def _t_test_results(self): 484 def _t_test_results(self):
470 """Returns a string showing t-test results for lkgr and fkbr.""" 485 """Returns a string showing t-test results for lkgr and fkbr."""
471 t, df, p = self.api.m.math_utils.welchs_t_test( 486 t, df, p = self.api.m.math_utils.welchs_t_test(
472 self.lkgr.values, self.fkbr.values) 487 self.lkgr.values, self.fkbr.values)
473 lines = [ 488 lines = [
474 'LKGR values: %r' % self.lkgr.values, 489 'LKGR values: %r' % self.lkgr.values,
475 'FKBR values: %r' % self.fkbr.values, 490 'FKBR values: %r' % self.fkbr.values,
476 't-statistic: %r' % t, 491 't-statistic: %r' % t,
477 'deg. of freedom: %r' % df, 492 'deg. of freedom: %r' % df,
478 'p-value: %r' % p, 493 'p-value: %r' % p,
479 'Confidence score: %r' % (100 * (1 - p)) 494 'Confidence score: %r' % (100 * (1 - p))
480 ] 495 ]
481 return '\n'.join(lines) 496 return '\n'.join(lines)
482 497
483 def partial_results(self):
484 return bisect_results.BisectResults(self, partial=True).as_string()
485
486 def print_result_debug_info(self): 498 def print_result_debug_info(self):
487 """Prints extra debug info at the end of the bisect process.""" 499 """Prints extra debug info at the end of the bisect process."""
488 lines = self._results_debug_message().splitlines() 500 lines = self._results_debug_message().splitlines()
489 # If we emit a null step then add a log to it, the log should be kept 501 # If we emit a null step then add a log to it, the log should be kept
490 # longer than 7 days (which is often needed to debug some issues). 502 # longer than 7 days (which is often needed to debug some issues).
491 self.api.m.step('Debug Info', []) 503 self.api.m.step('Debug Info', [])
492 self.api.m.step.active_result.presentation.logs['Debug Info'] = lines 504 self.api.m.step.active_result.presentation.logs['Debug Info'] = lines
493 505
494 def print_result(self): 506 def post_result(self, halt_on_failure=False):
495 results = bisect_results.BisectResults(self).as_string() 507 """Posts bisect results to Perf Dashboard."""
496 self.api.m.python.inline( 508 self.api.m.perf_dashboard.set_default_config()
497 'Results', 509 self.api.m.perf_dashboard.post_bisect(self.get_result(), halt_on_failure)
498 """
499 import shutil
500 import sys
501 shutil.copyfileobj(open(sys.argv[1]), sys.stdout)
502 """,
503 args=[self.api.m.raw_io.input(data=results)])
504 510
505 def get_revision_to_eval(self): 511 def get_revision_to_eval(self):
506 """Gets the next RevistionState object in the candidate range. 512 """Gets the next RevistionState object in the candidate range.
507 513
508 Returns: 514 Returns:
509 The next Revision object in a list. 515 The next Revision object in a list.
510 """ 516 """
511 self._update_candidate_range() 517 self._update_candidate_range()
512 candidate_range = [revision for revision in 518 candidate_range = [revision for revision in
513 self.revisions[self.lkgr.list_index + 1: 519 self.revisions[self.lkgr.list_index + 1:
(...skipping 271 matching lines...) Expand 10 before | Expand all | Expand 10 after
785 def surface_result(self, result_string): 791 def surface_result(self, result_string):
786 assert result_string in VALID_RESULT_CODES 792 assert result_string in VALID_RESULT_CODES
787 prefix = 'B4T_' # To avoid collision. Stands for bisect (abbr. `a la i18n). 793 prefix = 'B4T_' # To avoid collision. Stands for bisect (abbr. `a la i18n).
788 result_code = prefix + result_string 794 result_code = prefix + result_string
789 assert len(result_code) <= 20 795 assert len(result_code) <= 20
790 if result_code not in self.result_codes: 796 if result_code not in self.result_codes:
791 self.result_codes.add(result_code) 797 self.result_codes.add(result_code)
792 properties = self.api.m.step.active_result.presentation.properties 798 properties = self.api.m.step.active_result.presentation.properties
793 properties['extra_result_code'] = sorted(self.result_codes) 799 properties['extra_result_code'] = sorted(self.result_codes)
794 800
801 def get_result(self):
802 """Returns the results as a jsonable object."""
803 config = self.bisect_config
804 results_confidence = 0
805 if self.culprit:
806 results_confidence = self.api.m.math_utils.confidence_score(
807 self.lkgr.values, self.fkbr.values)
808
809 if self.failed:
810 status = 'failed'
811 elif self.bisect_over:
812 status = 'completed'
813 else:
814 status = 'started'
815
816 fail_reason = None
817 if self.failed_initial_confidence:
818 fail_reason = _FAILED_INITIAL_CONFIDENCE_ABORT_REASON
819 elif self.failed_direction:
820 fail_reason = _DIRECTION_OF_IMPROVEMENT_ABORT_REASON
821 return {
822 'try_job_id': config.get('try_job_id'),
823 'bug_id': config.get('bug_id'),
824 'status': status,
825 'buildbot_log_url': self._get_build_url(),
826 'bisect_bot': self.get_perf_tester_name(),
827 'command': config['command'],
828 'test_type': config['test_type'],
829 'metric': config['metric'],
830 'change': self.relative_change,
831 'score': results_confidence,
832 'good_revision': self.good_rev.commit_hash,
833 'bad_revision': self.bad_rev.commit_hash,
834 'warnings': self.warnings,
835 'fail_reason': fail_reason,
836 'culprit_data': self._culprit_data(),
837 'revision_data': self._revision_data()
838 }
839
840 def _culprit_data(self):
841 culprit = self.culprit
842 api = self.api
843 if not culprit:
844 return None
845 culprit_cl_hash = culprit.deps_revision or culprit.commit_hash
846 culprit_info = api.query_revision_info(
847 culprit_cl_hash, culprit.depot_name)
848
849 return {
850 'subject': culprit_info['subject'],
851 'author': culprit_info['author'],
852 'email': culprit_info['email'],
853 'cl_date': culprit_info['date'],
854 'commit_info': culprit_info['body'],
855 'revisions_links': [],
856 'cl': culprit.deps_revision or culprit.commit_hash
857 }
858
859 def _revision_data(self):
860 revision_rows = []
861 for r in self.revisions:
862 if r.tested or r.aborted:
863 revision_rows.append({
864 'depot_name': r.depot_name,
865 'deps_revision': r.deps_revision,
866 'commit_pos': r.commit_pos,
867 'mean_value': r.mean_value,
868 'std_dev': r.std_dev,
869 'values': r.values,
870 'result': 'good' if r.good else 'bad' if r.bad else 'unknown',
871 })
872 return revision_rows
873
874 def _get_build_url(self):
875 properties = self.api.m.properties
876 bot_url = properties.get('buildbotURL',
877 'http://build.chromium.org/p/chromium/')
878 builder_name = urllib.quote(properties.get('buildername', ''))
879 builder_number = str(properties.get('buildnumber', ''))
880 return '%sbuilders/%s/builds/%s' % (bot_url, builder_name, builder_number)
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698