| OLD | NEW |
| 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 tempfile |
| 7 import os |
| 6 import uuid | 8 import uuid |
| 7 | 9 |
| 8 from . import revision_state | 10 from . import revision_state |
| 9 | 11 |
| 12 if 'CACHE_TEST_RESULTS' in os.environ: # pragma: no cover |
| 13 from . import test_results_cache |
| 14 |
| 10 | 15 |
| 11 class PerfRevisionState(revision_state.RevisionState): | 16 class PerfRevisionState(revision_state.RevisionState): |
| 12 | 17 |
| 13 """Contains the state and results for one revision in a perf bisect job.""" | 18 """Contains the state and results for one revision in a perf bisect job.""" |
| 14 def __init__(self, *args, **kwargs): | 19 def __init__(self, *args, **kwargs): |
| 15 super(PerfRevisionState, self).__init__(*args, **kwargs) | 20 super(PerfRevisionState, self).__init__(*args, **kwargs) |
| 16 self.values = [] | 21 self.values = [] |
| 17 self.mean_value = None | 22 self.mean_value = None |
| 18 self.std_err = None | 23 self.std_err = None |
| 24 self._test_config = None |
| 19 | 25 |
| 20 def test_info(self): | 26 def test_info(self): |
| 21 """Returns a dictionary with information that describes this test. | 27 """Returns a dictionary with information that describes this test. |
| 22 | 28 |
| 23 It is meant to be used by the bisector to describe the test being run for | 29 It is meant to be used by the bisector to describe the test being run for |
| 24 each revision evaluated. | 30 each revision evaluated. |
| 25 """ | 31 """ |
| 26 return { | 32 return { |
| 27 'command': self._test_config['command'], | 33 'command': self._test_config['command'], |
| 28 'metric': self._test_config['metric'], | 34 'metric': self._test_config['metric'], |
| 29 } | 35 } |
| 30 | 36 |
| 31 def _read_test_results(self): | 37 def _read_test_results(self): |
| 32 """Gets the test results from GS and checks if the rev is good or bad.""" | 38 """Gets the test results from GS and checks if the rev is good or bad.""" |
| 33 results = self._get_test_results() | 39 results = self._get_test_results() |
| 34 # Results will contain the keys 'results' and 'output' where output is the | 40 # Results will contain the keys 'results' and 'output' where output is the |
| 35 # stdout of the command, and 'results' is itself a dict with the keys: | 41 # stdout of the command, and 'results' is itself a dict with the keys: |
| 36 # 'mean', 'values', 'std_err' | 42 # 'mean', 'values', 'std_err' |
| 37 results = results['results'] | 43 results = results['results'] |
| 38 self.mean_value = results['mean'] | 44 self.mean_value = results['mean'] |
| 39 self.values = results['values'] | 45 self.values = results['values'] |
| 40 self.std_err = results['std_err'] | 46 self.std_err = results['std_err'] |
| 41 # We cannot test the goodness of the initial rev range. | 47 # We cannot test the goodness of the initial rev range. |
| 42 if self.bisector.good_rev != self and self.bisector.bad_rev != self: | 48 if self.bisector.good_rev != self and self.bisector.bad_rev != self: |
| 43 if self._check_revision_good(): | 49 if self._check_revision_good(): |
| 44 self.good = True | 50 self.good = True |
| 45 else: | 51 else: |
| 46 self.bad = True | 52 self.bad = True |
| 47 | 53 |
| 54 def _write_deps_patch_file(self, build_name): |
| 55 api = self.bisector.api |
| 56 file_name = os.path.join(tempfile.gettempdir(), build_name + '.diff') |
| 57 api.m.file.write('Saving diff patch for ' + str(self.revision_string), |
| 58 file_name, self.deps_patch + self.deps_sha_patch) |
| 59 return file_name |
| 60 |
| 48 def _request_build(self): | 61 def _request_build(self): |
| 49 """Posts a request to buildbot to build this revision and archive it.""" | 62 """Posts a request to buildbot to build this revision and archive it.""" |
| 50 # TODO: Rewrite using the trigger module. | 63 # TODO: Rewrite using the trigger module. |
| 51 # TODO: Send a diff patch when appropriate | |
| 52 api = self.bisector.api | 64 api = self.bisector.api |
| 53 bot_name = self.bisector.get_builder_bot_for_this_platform() | 65 bot_name = self.bisector.get_builder_bot_for_this_platform() |
| 54 if self.bisector.dummy_builds: | 66 if self.bisector.dummy_builds: |
| 55 self.build_job_name = self.commit_hash + '-build' | 67 self.build_job_name = self.commit_hash + '-build' |
| 56 else: # pragma: no cover | 68 else: # pragma: no cover |
| 57 self.build_job_name = uuid.uuid4().hex | 69 self.build_job_name = uuid.uuid4().hex |
| 70 if self.needs_patch: |
| 71 self.patch_file = self._write_deps_patch_file( |
| 72 self.build_job_name) |
| 73 else: |
| 74 self.patch_file = '/dev/null' |
| 58 try_cmd = [ | 75 try_cmd = [ |
| 59 'try', | 76 'try', |
| 60 '--bot=%s' % bot_name, | 77 '--bot', bot_name, |
| 61 '--revision=%s' % self.commit_hash, | 78 '--revision', self.commit_hash, |
| 62 '--name=%s' % self.build_job_name, | 79 '--name', self.build_job_name, |
| 63 '--svn_repo=%s' % api.SVN_REPO_URL, | 80 '--svn_repo', api.SVN_REPO_URL, |
| 64 '--diff', | 81 '--diff', self.patch_file, |
| 65 '/dev/null', | |
| 66 ] | 82 ] |
| 67 api.m.git(*try_cmd, name='Requesting build for %s via git try.' | 83 try: |
| 68 % str(self.commit_hash)) | 84 if not self.bisector.bisect_config.get('skip_gclient_ops'): |
| 85 api.m.bot_update.ensure_checkout() |
| 86 api.m.git(*try_cmd, name='Requesting build for %s via git try.' |
| 87 % str(self.commit_hash)) |
| 88 finally: |
| 89 if (self.patch_file != '/dev/null' and not 'TESTING_SLAVENAME' in |
| 90 os.environ): |
| 91 try: |
| 92 api.m.step('cleaning up patch', ['rm', self.patch_file]) |
| 93 except api.m.step.StepFailure: # pragma: no cover |
| 94 print 'Could not clean up ' + self.patch_file |
| 69 | 95 |
| 70 def _get_bisect_config_for_tester(self): | 96 def _get_bisect_config_for_tester(self): |
| 71 """Copies the key-value pairs required by a tester bot to a new dict.""" | 97 """Copies the key-value pairs required by a tester bot to a new dict.""" |
| 72 result = {} | 98 result = {} |
| 73 required_test_properties = { | 99 required_test_properties = { |
| 74 'truncate_percent', | 100 'truncate_percent', |
| 75 'metric', | 101 'metric', |
| 76 'max_time_minutes', | 102 'max_time_minutes', |
| 77 'command', | 103 'command', |
| 78 'repeat_count', | 104 'repeat_count', |
| 79 'test_type' | 105 'test_type' |
| 80 } | 106 } |
| 81 for k, v in self.bisector.bisect_config.iteritems(): | 107 for k, v in self.bisector.bisect_config.iteritems(): |
| 82 if k in required_test_properties: | 108 if k in required_test_properties: |
| 83 result[k] = v | 109 result[k] = v |
| 84 self._test_config = result | 110 self._test_config = result |
| 85 return result | 111 return result |
| 86 | 112 |
| 87 def _do_test(self): | 113 def _do_test(self): |
| 88 """Posts a request to buildbot to download and perf-test this build.""" | 114 """Posts a request to buildbot to download and perf-test this build.""" |
| 89 if self.bisector.dummy_builds: | 115 if self.bisector.dummy_builds: |
| 90 self.test_job_name = self.commit_hash + '-test' | 116 self.test_job_name = self.commit_hash + '-test' |
| 117 elif 'CACHE_TEST_RESULTS' in os.environ: # pragma: no cover |
| 118 self.test_job_name = test_results_cache.make_id( |
| 119 self.revision_string, self._get_bisect_config_for_tester()) |
| 91 else: # pragma: no cover | 120 else: # pragma: no cover |
| 92 self.test_job_name = uuid.uuid4().hex | 121 self.test_job_name = uuid.uuid4().hex |
| 93 api = self.bisector.api | 122 api = self.bisector.api |
| 94 perf_test_properties = { | 123 perf_test_properties = { |
| 95 'buildername': self.bisector.get_perf_tester_name(), | 124 'buildername': self.bisector.get_perf_tester_name(), |
| 96 'revision': self.revision_string, | 125 'revision': self.revision_string, |
| 97 'parent_build_archive_url': self.build_url, | 126 'parent_build_archive_url': self.build_url, |
| 98 'bisect_config': self._get_bisect_config_for_tester(), | 127 'bisect_config': self._get_bisect_config_for_tester(), |
| 99 'job_name': self.test_job_name, | 128 'job_name': self.test_job_name, |
| 100 } | 129 } |
| 130 if 'CACHE_TEST_RESULTS' in os.environ and test_results_cache.has_results( |
| 131 self.test_job_name): # pragma: no cover |
| 132 return |
| 101 step_name = 'Triggering test job for ' + str(self.revision_string) | 133 step_name = 'Triggering test job for ' + str(self.revision_string) |
| 102 api.m.trigger(perf_test_properties, name=step_name) | 134 api.m.trigger(perf_test_properties, name=step_name) |
| 103 | 135 |
| 104 def _get_build_status(self): | 136 def _get_build_status(self): |
| 105 """Queries buildbot through the json API to check if the job is done.""" | 137 """Queries buildbot through the json API to check if the job is done.""" |
| 106 api = self.bisector.api | 138 api = self.bisector.api |
| 107 try: | 139 try: |
| 108 stdout = api.m.raw_io.output() | 140 stdout = api.m.raw_io.output() |
| 109 name = 'Get test status for build ' + self.commit_hash | 141 name = 'Get test status for build ' + self.commit_hash |
| 110 step_result = api.m.python(name, api.resource('check_job_status.py'), | 142 step_result = api.m.python(name, api.resource('check_job_status.py'), |
| (...skipping 17 matching lines...) Expand all Loading... |
| 128 """ | 160 """ |
| 129 api = self.bisector.api | 161 api = self.bisector.api |
| 130 url_file_url = api.GS_RESULTS_URL + self.test_job_name | 162 url_file_url = api.GS_RESULTS_URL + self.test_job_name |
| 131 try: | 163 try: |
| 132 stdout = api.m.raw_io.output() | 164 stdout = api.m.raw_io.output() |
| 133 name = 'Get test status url for build ' + self.commit_hash | 165 name = 'Get test status url for build ' + self.commit_hash |
| 134 step_result = api.m.gsutil.cat(url_file_url, stdout=stdout, name=name) | 166 step_result = api.m.gsutil.cat(url_file_url, stdout=stdout, name=name) |
| 135 except api.m.step.StepFailure: # pragma: no cover | 167 except api.m.step.StepFailure: # pragma: no cover |
| 136 return None | 168 return None |
| 137 else: | 169 else: |
| 138 return step_result.stdout | 170 url = step_result.stdout |
| 171 if 'CACHE_TEST_RESULTS' in os.environ: # pragma: no cover |
| 172 test_results_cache.save_results(self.test_job_name, url) |
| 173 return url |
| 139 | 174 |
| 140 def get_next_url(self): | 175 def get_next_url(self): |
| 141 if not self.in_progress: | 176 if not self.in_progress: |
| 142 return None | 177 return None |
| 143 if not self.built: | 178 if not self.built: |
| 144 return self.build_url | 179 return self.build_url |
| 145 if not self.build_status_url: | 180 if not self.build_status_url: |
| 146 # The file that will eventually contain the buildbot job url | 181 # The file that will eventually contain the buildbot job url |
| 147 return self.bisector.api.GS_RESULTS_URL + self.test_job_name | 182 return self.bisector.api.GS_RESULTS_URL + self.test_job_name |
| 148 return self.build_status_url # pragma: no cover | 183 return self.build_status_url # pragma: no cover |
| (...skipping 23 matching lines...) Expand all Loading... |
| 172 True if this revision is closer to the initial good revision's value than | 207 True if this revision is closer to the initial good revision's value than |
| 173 to the initial bad revision's value. False otherwise. | 208 to the initial bad revision's value. False otherwise. |
| 174 """ | 209 """ |
| 175 # TODO: Reevaluate this approach | 210 # TODO: Reevaluate this approach |
| 176 bisector = self.bisector | 211 bisector = self.bisector |
| 177 distance_to_good = abs(self.mean_value - bisector.good_rev.mean_value) | 212 distance_to_good = abs(self.mean_value - bisector.good_rev.mean_value) |
| 178 distance_to_bad = abs(self.mean_value - bisector.bad_rev.mean_value) | 213 distance_to_bad = abs(self.mean_value - bisector.bad_rev.mean_value) |
| 179 if distance_to_good < distance_to_bad: | 214 if distance_to_good < distance_to_bad: |
| 180 return True | 215 return True |
| 181 return False | 216 return False |
| OLD | NEW |