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 base64 | 5 import base64 |
6 import collections | 6 import collections |
7 import json | 7 import json |
8 | 8 |
9 DEPS = [ | 9 DEPS = [ |
10 'auto_bisect', | 10 'auto_bisect', |
11 'properties', | 11 'properties', |
12 'test_utils', | 12 'test_utils', |
13 'chromium_tests', | 13 'chromium_tests', |
14 'raw_io', | 14 'raw_io', |
15 ] | 15 ] |
16 | 16 |
17 AVAILABLE_BOTS = 1 # Change this for n-secting instead of bi-. | 17 AVAILABLE_BOTS = 1 # Change this for n-secting instead of bi-. |
| 18 |
18 | 19 |
19 def GenSteps(api): | 20 def GenSteps(api): |
20 _ensure_checkout(api) | 21 _ensure_checkout(api) |
21 # HORRIBLE hack to get buildbot web ui to let us pass stuff as properties | 22 # HORRIBLE hack to get buildbot web ui to let us pass stuff as properties |
22 bisect_config_b32_string = api.properties.get('bcb32') | 23 bisect_config_b32_string = api.properties.get('bcb32') |
23 if bisect_config_b32_string is not None: | 24 if bisect_config_b32_string is not None: |
24 bisect_config = bisect_config_b32_string.replace('0', '=') | 25 bisect_config = bisect_config_b32_string.replace('0', '=') |
25 bisect_config = base64.b32decode(bisect_config) | 26 bisect_config = base64.b32decode(bisect_config) |
26 bisect_config = json.loads(bisect_config) | 27 bisect_config = json.loads(bisect_config) |
27 else: | 28 else: |
28 bisect_config = api.properties.get('bisect_config') | 29 bisect_config = api.properties.get('bisect_config') |
29 assert isinstance(bisect_config, collections.Mapping) | 30 assert isinstance(bisect_config, collections.Mapping) |
30 bisector = api.auto_bisect.create_bisector(bisect_config) | 31 bisector = api.auto_bisect.create_bisector(bisect_config) |
31 _gather_reference_range(bisector) | 32 _gather_reference_range(bisector) |
32 _ensure_checkout(api) | 33 _ensure_checkout(api) |
33 if (not bisector.failed and bisector.check_improvement_direction() and | 34 if (not bisector.failed and bisector.check_improvement_direction() and |
34 bisector.check_regression_confidence()): | 35 bisector.check_regression_confidence()): |
35 if not bisector.check_bisect_finished(bisector.good_rev): | 36 if not bisector.check_bisect_finished(bisector.good_rev): |
36 _bisect_main_loop(bisector) | 37 _bisect_main_loop(bisector) |
37 else: #pragma: no cover | 38 else: # pragma: no cover |
38 bisector.bisect_over = True | 39 bisector.bisect_over = True |
39 bisector.print_result() | 40 bisector.print_result() |
40 | 41 |
41 | 42 |
42 def GenTests(api): | 43 def GenTests(api): |
43 basic_test = api.test('basic') | 44 basic_test = api.test('basic') |
44 encoded_config_test = api.test('encoded_config_test') | 45 encoded_config_test = api.test('encoded_config_test') |
45 broken_cp_test = api.test('broken_cp_test') | 46 broken_cp_test = api.test('broken_cp_test') |
46 broken_hash_test = api.test('broken_hash_test') | 47 broken_hash_test = api.test('broken_hash_test') |
47 invalid_config_test = api.test('invalid_config_test') | 48 invalid_config_test = api.test('invalid_config_test') |
48 basic_test += api.properties.generic(mastername='tryserver.chromium.perf', | 49 basic_test += api.properties.generic( |
49 buildername='linux_perf_bisect_builder') | 50 mastername='tryserver.chromium.perf', |
50 broken_cp_test += api.properties.generic(mastername='tryserver.chromium.perf', | 51 buildername='linux_perf_bisect_builder') |
51 buildername='linux_perf_bisect_builder') | 52 broken_cp_test += api.properties.generic( |
| 53 mastername='tryserver.chromium.perf', |
| 54 buildername='linux_perf_bisect_builder') |
52 broken_hash_test += api.properties.generic( | 55 broken_hash_test += api.properties.generic( |
53 mastername='tryserver.chromium.perf', | 56 mastername='tryserver.chromium.perf', |
54 buildername='linux_perf_bisect_builder') | 57 buildername='linux_perf_bisect_builder') |
55 invalid_config_test += api.properties.generic( | 58 invalid_config_test += api.properties.generic( |
56 mastername='tryserver.chromium.perf', | 59 mastername='tryserver.chromium.perf', |
57 buildername='linux_perf_bisect_builder') | 60 buildername='linux_perf_bisect_builder') |
58 encoded_config_test += api.properties.generic( | 61 encoded_config_test += api.properties.generic( |
59 mastername='tryserver.chromium.perf', | 62 mastername='tryserver.chromium.perf', |
60 buildername='linux_perf_bisect_builder') | 63 buildername='linux_perf_bisect_builder') |
61 bisect_config = { | 64 bisect_config = { |
62 'test_type': 'perf', | 65 'test_type': 'perf', |
63 'command': 'tools/perf/run_benchmark -v ' | 66 'command': ('tools/perf/run_benchmark -v ' |
64 '--browser=release page_cycler.intl_ar_fa_he', | 67 '--browser=release page_cycler.intl_ar_fa_he'), |
65 'good_revision': '306475', | 68 'good_revision': '306475', |
66 'bad_revision': 'src@a6298e4afedbf2cd461755ea6f45b0ad64222222', | 69 'bad_revision': 'src@a6298e4afedbf2cd461755ea6f45b0ad64222222', |
67 'metric': 'warm_times/page_load_time', | 70 'metric': 'warm_times/page_load_time', |
68 'repeat_count': '2', | 71 'repeat_count': '2', |
69 'max_time_minutes': '5', | 72 'max_time_minutes': '5', |
70 'truncate_percent': '25', | 73 'truncate_percent': '25', |
71 'bug_id': '425582', | 74 'bug_id': '425582', |
72 'gs_bucket': 'chrome-perf', | 75 'gs_bucket': 'chrome-perf', |
73 'builder_host': 'master4.golo.chromium.org', | 76 'builder_host': 'master4.golo.chromium.org', |
74 'builder_port': '8341', | 77 'builder_port': '8341', |
75 'dummy_regression_confidence': '95', | 78 'dummy_regression_confidence': '95', |
76 'poll_sleep': 0, | 79 'poll_sleep': 0, |
77 'dummy_builds': True, | 80 'dummy_builds': True, |
78 } | 81 } |
79 invalid_cp_bisect_config = dict(bisect_config) | 82 invalid_cp_bisect_config = dict(bisect_config) |
80 invalid_cp_bisect_config ['good_revision'] = 'XXX' | 83 invalid_cp_bisect_config['good_revision'] = 'XXX' |
81 | 84 |
82 basic_test += api.properties(bisect_config=bisect_config) | 85 basic_test += api.properties(bisect_config=bisect_config) |
83 broken_cp_test += api.properties(bisect_config=bisect_config) | 86 broken_cp_test += api.properties(bisect_config=bisect_config) |
84 broken_hash_test += api.properties(bisect_config=bisect_config) | 87 broken_hash_test += api.properties(bisect_config=bisect_config) |
85 invalid_config_test += api.properties(bisect_config=invalid_cp_bisect_config) | 88 invalid_config_test += api.properties(bisect_config=invalid_cp_bisect_config) |
86 encoded_config_test += api.properties(bcb32=base64.b32encode(json.dumps( | 89 encoded_config_test += api.properties(bcb32=base64.b32encode(json.dumps( |
87 bisect_config)).replace('=', '0')) | 90 bisect_config)).replace('=', '0')) |
88 # This data represents fake results for a basic scenario, the items in it are | 91 # This data represents fake results for a basic scenario, the items in it are |
89 # passed to the `_gen_step_data_for_revision` that patches the necessary steps | 92 # passed to the `_gen_step_data_for_revision` that patches the necessary steps |
90 # with step_data instances. | 93 # with step_data instances. |
91 test_data = [ | 94 test_data = [ |
92 { | 95 { |
93 'hash': 'a6298e4afedbf2cd461755ea6f45b0ad64222222', | 96 'hash': 'a6298e4afedbf2cd461755ea6f45b0ad64222222', |
94 'commit_pos': '306478', | 97 'commit_pos': '306478', |
95 'test_results': {'results':{ | 98 'test_results': {'results':{ |
96 'mean': 20, | 99 'mean': 20, |
97 'std_err': 1, | 100 'std_err': 1, |
98 'values': [19, 20, 21], | 101 'values': [19, 20, 21], |
99 }}, | 102 }}, |
100 'cl_info': 'S3P4R4T0R'.join(['DummyAuthor', 'dummy@nowhere.com', | 103 'cl_info': 'S3P4R4T0R'.join(['DummyAuthor', 'dummy@nowhere.com', |
101 'Some random CL', '01/01/2015', | 104 'Some random CL', '01/01/2015', |
102 'A long description for a CL.\n' | 105 'A long description for a CL.\n' |
103 'Containing multiple lines']) | 106 'Containing multiple lines']) |
104 }, | 107 }, |
105 { | 108 { |
106 'hash': '00316c9ddfb9d7b4e1ed2fff9fe6d964d2111111', | 109 'hash': '00316c9ddfb9d7b4e1ed2fff9fe6d964d2111111', |
107 'commit_pos': '306477', | 110 'commit_pos': '306477', |
108 'test_results': {'results':{ | 111 'test_results': {'results': { |
109 'mean': 15, | 112 'mean': 15, |
110 'std_err': 1, | 113 'std_err': 1, |
111 'values': [14, 15, 16], | 114 'values': [14, 15, 16], |
112 }} | 115 }} |
113 }, | 116 }, |
114 { | 117 { |
115 'hash': 'fc6dfc7ff5b1073408499478969261b826441144', | 118 'hash': 'fc6dfc7ff5b1073408499478969261b826441144', |
116 'commit_pos': '306476', | 119 'commit_pos': '306476', |
117 'test_results': {'results':{ | 120 'test_results': {'results': { |
118 'mean': 70, | 121 'mean': 70, |
119 'std_err': 2, | 122 'std_err': 2, |
120 'values': [68, 70, 72], | 123 'values': [68, 70, 72], |
121 }} | 124 }} |
122 }, | 125 }, |
123 { | 126 { |
124 'hash': 'e28dc0d49c331def2a3bbf3ddd0096eb51551155', | 127 'hash': 'e28dc0d49c331def2a3bbf3ddd0096eb51551155', |
125 'commit_pos': '306475', | 128 'commit_pos': '306475', |
126 'test_results': {'results':{ | 129 'test_results': {'results': { |
127 'mean': 80, | 130 'mean': 80, |
128 'std_err': 10, | 131 'std_err': 10, |
129 'values': [70, 70, 80, 90, 90], | 132 'values': [70, 70, 80, 90, 90], |
130 }} | 133 }} |
131 }, | 134 }, |
132 ] | 135 ] |
133 | 136 |
134 | |
135 | |
136 for revision_data in test_data: | 137 for revision_data in test_data: |
137 for step_data in _get_step_data_for_revision(api, revision_data): | 138 for step_data in _get_step_data_for_revision(api, revision_data): |
138 basic_test += step_data | 139 basic_test += step_data |
139 encoded_config_test += step_data | 140 encoded_config_test += step_data |
140 for step_data in _get_step_data_for_revision(api, revision_data, | 141 for step_data in _get_step_data_for_revision(api, revision_data, |
141 broken_cp='306475'): | 142 broken_cp='306475'): |
142 broken_cp_test += step_data | 143 broken_cp_test += step_data |
143 for step_data in _get_step_data_for_revision( | 144 for step_data in _get_step_data_for_revision( |
144 api, revision_data, | 145 api, revision_data, |
145 broken_hash='e28dc0d49c331def2a3bbf3ddd0096eb51551155'): | 146 broken_hash='e28dc0d49c331def2a3bbf3ddd0096eb51551155'): |
146 broken_hash_test += step_data | 147 broken_hash_test += step_data |
147 | 148 |
148 yield basic_test | 149 yield basic_test |
149 yield encoded_config_test | 150 yield encoded_config_test |
150 yield broken_hash_test | 151 yield broken_hash_test |
151 yield broken_cp_test | 152 yield broken_cp_test |
152 yield invalid_config_test | 153 yield invalid_config_test |
153 | 154 |
154 | 155 |
155 | |
156 | |
157 | |
158 def _get_step_data_for_revision(api, revision_data, broken_cp=None, | 156 def _get_step_data_for_revision(api, revision_data, broken_cp=None, |
159 broken_hash=None): | 157 broken_hash=None): |
160 """Generator that produces step patches for fake results.""" | 158 """Generator that produces step patches for fake results.""" |
161 commit_pos = revision_data['commit_pos'] | 159 commit_pos = revision_data['commit_pos'] |
162 commit_hash = revision_data['hash'] | 160 commit_hash = revision_data['hash'] |
163 test_results = revision_data['test_results'] | 161 test_results = revision_data['test_results'] |
164 | 162 |
165 step_name ='resolving commit_pos ' + commit_pos | 163 step_name = 'resolving commit_pos ' + commit_pos |
166 if commit_pos == broken_cp: | 164 if commit_pos == broken_cp: |
167 yield api.step_data(step_name, stdout=api.raw_io.output('')) | 165 yield api.step_data(step_name, stdout=api.raw_io.output('')) |
168 else: | 166 else: |
169 yield api.step_data(step_name, stdout=api.raw_io.output('hash:' + | 167 yield api.step_data(step_name, stdout=api.raw_io.output('hash:' + |
170 commit_hash)) | 168 commit_hash)) |
171 | 169 |
172 step_name ='resolving hash ' + commit_hash | 170 step_name = 'resolving hash ' + commit_hash |
173 if commit_hash == broken_hash: | 171 if commit_hash == broken_hash: |
174 yield api.step_data(step_name, stdout=api.raw_io.output('UnCastable')) | 172 yield api.step_data(step_name, stdout=api.raw_io.output('UnCastable')) |
175 else: | 173 else: |
176 commit_pos_str = 'refs/heads/master@{#%s}' % commit_pos | 174 commit_pos_str = 'refs/heads/master@{#%s}' % commit_pos |
177 yield api.step_data(step_name, stdout=api.raw_io.output(commit_pos_str)) | 175 yield api.step_data(step_name, stdout=api.raw_io.output(commit_pos_str)) |
178 | 176 |
179 step_name ='gsutil Get test results for build ' + commit_hash | 177 step_name = 'gsutil Get test results for build ' + commit_hash |
180 yield api.step_data(step_name, stdout=api.raw_io.output(json.dumps( | 178 yield api.step_data(step_name, stdout=api.raw_io.output(json.dumps( |
181 test_results))) | 179 test_results))) |
182 | 180 |
183 step_name = 'Get test status for build ' + commit_hash | 181 step_name = 'Get test status for build ' + commit_hash |
184 yield api.step_data(step_name, stdout=api.raw_io.output('Complete')) | 182 yield api.step_data(step_name, stdout=api.raw_io.output('Complete')) |
185 | 183 |
186 step_name ='gsutil Get test status url for build ' + commit_hash | 184 step_name = 'gsutil Get test status url for build ' + commit_hash |
187 yield api.step_data(step_name, stdout=api.raw_io.output('dummy/url')) | 185 yield api.step_data(step_name, stdout=api.raw_io.output('dummy/url')) |
188 | 186 |
189 if 'cl_info' in revision_data: | 187 if 'cl_info' in revision_data: |
190 step_name = 'Reading culprit cl information.' | 188 step_name = 'Reading culprit cl information.' |
191 stdout = api.raw_io.output(revision_data['cl_info']) | 189 stdout = api.raw_io.output(revision_data['cl_info']) |
192 yield api.step_data(step_name, stdout=stdout) | 190 yield api.step_data(step_name, stdout=stdout) |
193 | 191 |
194 | 192 |
195 def _ensure_checkout(api): | 193 def _ensure_checkout(api): |
196 mastername = api.properties.get('mastername') | 194 mastername = api.properties.get('mastername') |
197 buildername = api.properties.get('buildername') | 195 buildername = api.properties.get('buildername') |
198 api.chromium_tests.sync_and_configure_build(mastername, buildername) | 196 api.chromium_tests.sync_and_configure_build(mastername, buildername) |
199 | 197 |
200 | 198 |
201 def _gather_reference_range(bisector): | 199 def _gather_reference_range(bisector): |
202 bisector.good_rev.start_job() | 200 bisector.good_rev.start_job() |
203 bisector.bad_rev.start_job() | 201 bisector.bad_rev.start_job() |
204 bisector.wait_for_all([bisector.good_rev, bisector.bad_rev]) | 202 bisector.wait_for_all([bisector.good_rev, bisector.bad_rev]) |
205 bisector.compute_relative_change() | 203 bisector.compute_relative_change() |
206 | 204 |
207 | 205 |
208 def _bisect_main_loop(bisector): | 206 def _bisect_main_loop(bisector): |
209 """This is the main bisect loop. | 207 """This is the main bisect loop. |
210 | 208 |
211 It gets an evenly distributed number of revisions in the candidate range, | 209 It gets an evenly distributed number of revisions in the candidate range, |
212 then it starts them in parallel and waits for them to finish. | 210 then it starts them in parallel and waits for them to finish. |
213 """ | 211 """ |
214 while not bisector.bisect_over: | 212 while not bisector.bisect_over: |
215 revisions_to_check = bisector.get_revisions_to_eval(AVAILABLE_BOTS) | 213 revisions_to_check = bisector.get_revisions_to_eval(AVAILABLE_BOTS) |
216 #TODO: Add a test case to remove this pragma | 214 # TODO: Add a test case to remove this pragma |
217 if not revisions_to_check: #pragma: no cover | 215 if not revisions_to_check: # pragma: no cover |
218 bisector.bisect_over = True | 216 bisector.bisect_over = True |
219 break | 217 break |
220 for r in revisions_to_check: | 218 for r in revisions_to_check: |
221 r.start_job() | 219 r.start_job() |
222 _wait_for_revisions(bisector, revisions_to_check) | 220 _wait_for_revisions(bisector, revisions_to_check) |
223 | 221 |
224 | 222 |
225 def _wait_for_revisions(bisector, revisions_to_check): | 223 def _wait_for_revisions(bisector, revisions_to_check): |
226 """Wait for possibly multiple revision evaluations. | 224 """Wait for possibly multiple revision evaluations. |
227 | 225 |
228 Waits for the first of such revisions to finish, it then checks if any of the | 226 Waits for the first of such revisions to finish, it then checks if any of the |
229 other revisions in progress has become superfluous and has them aborted. | 227 other revisions in progress has become superfluous and has them aborted. |
230 | 228 |
231 If such revision completes the bisect process it sets the flag so that the | 229 If such revision completes the bisect process it sets the flag so that the |
232 main loop stops. | 230 main loop stops. |
233 """ | 231 """ |
234 while revisions_to_check: | 232 while revisions_to_check: |
235 completed_revision = bisector.wait_for_any(revisions_to_check) | 233 completed_revision = bisector.wait_for_any(revisions_to_check) |
236 revisions_to_check.remove(completed_revision) | 234 revisions_to_check.remove(completed_revision) |
237 if not completed_revision.aborted: | 235 if not completed_revision.aborted: |
238 if bisector.check_bisect_finished(completed_revision): | 236 if bisector.check_bisect_finished(completed_revision): |
239 bisector.bisect_over = True | 237 bisector.bisect_over = True |
240 bisector.abort_unnecessary_jobs() | 238 bisector.abort_unnecessary_jobs() |
OLD | NEW |