blob: 6ef4aacab758f12df424706dc61d621fa7cb6473 [file] [log] [blame]
Kuang-che Wu1fcc0222018-07-07 16:43:22 +08001#!/usr/bin/env python2
2# -*- coding: utf-8 -*-
3# Copyright 2018 The Chromium OS Authors. All rights reserved.
4# Use of this source code is governed by a BSD-style license that can be
5# found in the LICENSE file.
6"""Diagnose ChromeOS autotest regressions.
7
8This is integrated bisection utility. Given ChromeOS, Chrome, Android source
Kuang-che Wu927231f2018-07-24 14:21:56 +08009tree, and necessary parameters, this script can determine which components to
Kuang-che Wu1fcc0222018-07-07 16:43:22 +080010bisect, and hopefully output the culprit CL of regression.
11
12Sometimes the script failed to figure out the final CL for various reasons, it
13will cut down the search range as narrow as it can.
14"""
15from __future__ import print_function
16import argparse
17import fnmatch
18import glob
19import logging
20import os
21
22from bisect_kit import cli
23from bisect_kit import common
24from bisect_kit import configure
25from bisect_kit import core
26from bisect_kit import cros_lab_util
27from bisect_kit import cros_util
28from bisect_kit import diagnoser_cros
29from bisect_kit import util
Kuang-che Wud8fc9572018-10-03 21:00:41 +080030import setup_cros_bisect
Kuang-che Wu1fcc0222018-07-07 16:43:22 +080031
32logger = logging.getLogger(__name__)
33
34# What chrome binaries to build for given autotest.
35# This dict is created manually by inspecting output of
36# 'grep -r ChromeBinaryTest autotest/files/client/site_tests'
37# If you change this dict, build_and_deploy_chrome_helper.sh may need update
38# as well.
39CHROME_BINARIES_OF_TEST = {
40 'graphics_Chrome.ozone_gl_unittests': ['ozone_gl_unittests'],
41 'security_SandboxLinuxUnittests': ['sandbox_linux_unittests'],
42 'video_HangoutHardwarePerf*': [
43 'video_decode_accelerator_unittest',
44 'video_encode_accelerator_unittest',
45 ],
46 'video_JDAPerf*': ['jpeg_decode_accelerator_unittest'],
47 'video_JEAPerf': ['jpeg_encode_accelerator_unittest'],
48 'video_JpegDecodeAccelerator': ['jpeg_decode_accelerator_unittest'],
49 'video_JpegEncodeAccelerator': ['jpeg_encode_accelerator_unittest'],
50 'video_VDAPerf': ['video_decode_accelerator_unittest'],
51 'video_VDASanity': ['video_decode_accelerator_unittest'],
52 'video_VEAPerf': ['video_encode_accelerator_unittest'],
53 'video_VideoDecodeAccelerator*': ['video_decode_accelerator_unittest'],
54 'video_VideoEncodeAccelerator*': ['video_encode_accelerator_unittest'],
55}
56
57
58def create_argument_parser():
59 parser = argparse.ArgumentParser()
60 common.add_common_arguments(parser)
61 parser.add_argument('--session_base', default='bisect.sessions')
62 parser.add_argument('--session', help='Session name', required=True)
63
Kuang-che Wud8fc9572018-10-03 21:00:41 +080064 group = parser.add_argument_group(
65 title='Source tree path options',
66 description='''
67 Specify the paths of chromeos/chrome/android mirror and checkout. They
68 have the same default values as setup_cros_bisect.py, so usually you can
69 omit them and it just works.
70 ''')
71 group.add_argument(
72 '--mirror_base',
73 metavar='MIRROR_BASE',
74 default=configure.get('MIRROR_BASE',
75 setup_cros_bisect.DEFAULT_MIRROR_BASE),
76 help='Directory for mirrors (default: %(default)s)')
77 group.add_argument(
78 '--work_base',
79 metavar='WORK_BASE',
80 default=configure.get('WORK_BASE', setup_cros_bisect.DEFAULT_WORK_BASE),
81 help='Directory for bisection working directories (default: %(default)s)')
Kuang-che Wu1fcc0222018-07-07 16:43:22 +080082 group.add_argument(
83 '--chromeos_root',
84 metavar='CHROMEOS_ROOT',
85 type=cli.argtype_dir_path,
Kuang-che Wu1fcc0222018-07-07 16:43:22 +080086 default=configure.get('CHROMEOS_ROOT'),
87 help='ChromeOS tree root')
88 group.add_argument(
Kuang-che Wud8fc9572018-10-03 21:00:41 +080089 '--chromeos_mirror',
Kuang-che Wu94f48e52018-07-25 15:28:31 +080090 type=cli.argtype_dir_path,
Kuang-che Wud8fc9572018-10-03 21:00:41 +080091 default=configure.get('CHROMEOS_MIRROR'),
Kuang-che Wu94f48e52018-07-25 15:28:31 +080092 help='ChromeOS repo mirror path')
93 group.add_argument(
Kuang-che Wu1fcc0222018-07-07 16:43:22 +080094 '--android_root',
95 metavar='ANDROID_ROOT',
96 type=cli.argtype_dir_path,
Kuang-che Wu1fcc0222018-07-07 16:43:22 +080097 default=configure.get('ANDROID_ROOT'),
98 help='Android tree root')
99 group.add_argument(
Kuang-che Wud8fc9572018-10-03 21:00:41 +0800100 '--android_mirror',
Kuang-che Wu1fcc0222018-07-07 16:43:22 +0800101 type=cli.argtype_dir_path,
Kuang-che Wud8fc9572018-10-03 21:00:41 +0800102 default=configure.get('ANDROID_MIRROR'),
Kuang-che Wu1fcc0222018-07-07 16:43:22 +0800103 help='Android repo mirror path')
104 group.add_argument(
105 '--chrome_root',
106 metavar='CHROME_ROOT',
107 type=cli.argtype_dir_path,
Kuang-che Wu1fcc0222018-07-07 16:43:22 +0800108 default=configure.get('CHROME_ROOT'),
109 help='Chrome tree root')
110 group.add_argument(
Kuang-che Wud8fc9572018-10-03 21:00:41 +0800111 '--chrome_mirror',
112 metavar='CHROME_MIRROR',
Kuang-che Wu94f48e52018-07-25 15:28:31 +0800113 type=cli.argtype_dir_path,
Kuang-che Wud8fc9572018-10-03 21:00:41 +0800114 default=configure.get('CHROME_MIRROR'),
115 help="chrome's gclient cache dir")
116
117 group = parser.add_argument_group(title='Essential options')
Kuang-che Wu94f48e52018-07-25 15:28:31 +0800118 group.add_argument(
Kuang-che Wu1fcc0222018-07-07 16:43:22 +0800119 '--old',
120 type=cros_util.argtype_cros_version,
121 required=True,
122 help='ChromeOS version with old behavior')
123 group.add_argument(
124 '--new',
125 type=cros_util.argtype_cros_version,
126 required=True,
127 help='ChromeOS version with new behavior')
128 group.add_argument('test_name', help='Test name')
129
Kuang-che Wud8fc9572018-10-03 21:00:41 +0800130 group = parser.add_argument_group(title='Options for benchmark test')
131 group.add_argument('--metric', help='Metric name of benchmark test')
132 group.add_argument(
133 '--old_value', type=float, help='For benchmark test, old value of metric')
134 group.add_argument(
135 '--new_value', type=float, help='For benchmark test, new value of metric')
136
Kuang-che Wu1fcc0222018-07-07 16:43:22 +0800137 group = parser.add_argument_group(title='DUT allocation options')
138 group.add_argument(
139 'dut',
140 metavar='DUT',
141 help='Address of DUT (Device Under Test). If "%s", DUT will be '
142 'automatically allocated from the lab' % cros_lab_util.LAB_DUT)
143 group.add_argument(
144 '--board',
145 metavar='BOARD',
146 default=configure.get('BOARD'),
147 help='ChromeOS board name')
148 group.add_argument(
149 '--sku',
150 metavar='SKU',
151 help='SKU criteria if DUT is auto allocated from the lab')
152
Kuang-che Wu1fcc0222018-07-07 16:43:22 +0800153 group = parser.add_argument_group(title='Options passed to test_that')
154 group.add_argument(
155 '--args',
156 help='Extra args passed to "test_that --args"; Overrides the default')
157
158 group = parser.add_argument_group(title='Bisect behavior options')
159 group.add_argument(
160 '--noisy',
161 help='Enable noisy binary search. Example value: "old=1/10,new=2/3"')
162 group.add_argument(
163 '--always_reflash',
164 action='store_true',
165 help='Do not trust ChromeOS version number of DUT and always reflash. '
166 'This is usually only needed when resume because previous bisect was '
167 'interrupted and the DUT may be in an unexpected state')
168
169 return parser
170
171
172def grab_dut(opts):
173 # Assume "DEPENDENCIES" is identical between the period of
174 # `old` and `new` version.
175 autotest_dir = os.path.join(opts.chromeos_root,
176 cros_util.prebuilt_autotest_dir)
177 info = cros_util.get_autotest_test_info(autotest_dir, opts.test_name)
178 assert info
179
180 extra_labels = []
181 dependencies = info.variables.get('DEPENDENCIES', '')
182 for label in dependencies.split(','):
183 label = label.strip()
184 # Skip non-machine labels
185 if label in ['cleanup-reboot']:
186 continue
187 extra_labels.append(label)
188
189 reason = 'bisect-kit: %s' % opts.session
190 host = cros_lab_util.allocate_host(reason, opts.board, opts.sku, extra_labels)
191 if not host:
192 logger.error('unable to allocate dut')
193 return None
194
195 logger.info('allocated host %s', host)
196 return host
197
198
199def may_depend_on_extra_chrome_binaries(autotest_dir, test_name):
200 info = cros_util.get_autotest_test_info(autotest_dir, test_name)
201 assert info
202 dirpath = os.path.dirname(info.path)
203 for pypath in glob.glob(os.path.join(dirpath, '*.py')):
204 if 'ChromeBinaryTest' in open(pypath).read():
205 return True
206 return False
207
208
209def determine_chrome_binaries(opts):
210 chrome_binaries = None
211 for name_pattern, binaries in CHROME_BINARIES_OF_TEST.items():
212 if fnmatch.fnmatch(opts.test_name, name_pattern):
213 chrome_binaries = binaries
214 break
215
216 autotest_dir = os.path.join(opts.chromeos_root,
217 cros_util.prebuilt_autotest_dir)
218 if chrome_binaries:
219 logger.info('This test depends on chrome binary: %s', chrome_binaries)
220 elif may_depend_on_extra_chrome_binaries(autotest_dir, opts.test_name):
Kuang-che Wu74768d32018-09-07 12:03:24 +0800221 logger.warning(
222 '%s code used ChromeBinaryTest but the binary is unknown; '
223 'please update CHROME_BINARIES_OF_TEST table', opts.test_name)
Kuang-che Wu1fcc0222018-07-07 16:43:22 +0800224 return chrome_binaries
225
226
Kuang-che Wud8fc9572018-10-03 21:00:41 +0800227def check_options(opts, parser, path_factory):
228 if not opts.chromeos_mirror:
229 opts.chromeos_mirror = path_factory.get_chromeos_mirror()
230 logger.info('chromeos_mirror = %s', opts.chromeos_mirror)
231 if not opts.chromeos_root:
232 opts.chromeos_root = path_factory.get_chromeos_tree()
233 logger.info('chromeos_root = %s', opts.chromeos_root)
234 if not opts.chrome_mirror:
235 opts.chrome_mirror = path_factory.get_chrome_cache()
236 logger.info('chrome_mirror = %s', opts.chrome_mirror)
237 if not opts.chrome_root:
238 opts.chrome_root = path_factory.get_chrome_tree()
239 logger.info('chrome_root = %s', opts.chrome_root)
240
Kuang-che Wu1fcc0222018-07-07 16:43:22 +0800241 if opts.dut == cros_lab_util.LAB_DUT:
242 session_dir = os.path.join(opts.session_base, opts.session)
243 # 1 is log file
244 if os.path.exists(session_dir) and len(os.listdir(session_dir)) > 1:
245 parser.error('Do not resume if the DUT is dynamically allocated; '
246 '%s is not empty' % session_dir)
247
248 if not opts.board:
249 if opts.dut == cros_lab_util.LAB_DUT:
250 parser.error(
251 '--board need to be specified if DUT is "%s"' % cros_lab_util.LAB_DUT)
252 else:
253 opts.board = cros_util.query_dut_board(opts.dut)
254
255 if cros_util.is_cros_short_version(opts.old):
256 opts.old = cros_util.version_to_full(opts.board, opts.old)
257 if cros_util.is_cros_short_version(opts.new):
258 opts.new = cros_util.version_to_full(opts.board, opts.new)
259
260 if opts.metric:
261 if opts.old_value is None:
262 parser.error('--old_value is not provided')
263 if opts.new_value is None:
264 parser.error('--new_value is not provided')
265 else:
266 if opts.old_value is not None:
267 parser.error('--old_value is provided but --metric is not')
268 if opts.new_value is not None:
269 parser.error('--new_value is provided but --metric is not')
270
271
272def main(args=None):
273 common.init()
274 parser = create_argument_parser()
275 opts = parser.parse_args(args)
276 common.config_logging(opts)
Kuang-che Wud8fc9572018-10-03 21:00:41 +0800277 path_factory = setup_cros_bisect.DefaultProjectPathFactory(
278 opts.mirror_base, opts.work_base, opts.session)
279 check_options(opts, parser, path_factory)
Kuang-che Wu1fcc0222018-07-07 16:43:22 +0800280
281 # prebuilt version will be specified later.
282 common_switch_cmd = [
283 './switch_autotest_prebuilt.py',
284 '--chromeos_root', opts.chromeos_root,
285 '--test_name', opts.test_name,
286 '--board', opts.board,
287 ] # yapf: disable
288
289 common_eval_cmd = [
290 './eval_cros_autotest.py',
291 '--chromeos_root', opts.chromeos_root,
292 '--test_name', opts.test_name,
293 ] # yapf: disable
294 if opts.metric:
295 common_eval_cmd += [
296 '--metric', opts.metric,
297 '--old_value', str(opts.old_value),
298 '--new_value', str(opts.new_value),
299 ] # yapf: disable
300 if opts.args:
301 common_eval_cmd += ['--args', opts.args]
302
303 # Unpack old autotest prebuilt, assume following information don't change
304 # between versions:
305 # - what chrome binaries to run
306 # - dependency labels for DUT allocation
307 util.check_call(*(common_switch_cmd + [opts.old]))
308
309 chrome_binaries = determine_chrome_binaries(opts)
310
311 with cros_lab_util.dut_manager(opts.dut, lambda: grab_dut(opts)) as dut:
312 if not dut:
313 raise core.ExecutionFatalError('unable to allocate DUT')
314 assert cros_util.is_dut(dut)
315 opts.dut = dut
316 common_eval_cmd.append(opts.dut)
317
318 diagnoser = diagnoser_cros.CrosDiagnoser(
Kuang-che Wud8fc9572018-10-03 21:00:41 +0800319 opts.session, path_factory, opts.chromeos_root, opts.chromeos_mirror,
320 opts.android_root, opts.android_mirror, opts.chrome_root,
321 opts.chrome_mirror, opts.board, opts.noisy, opts.dut)
Kuang-che Wu1fcc0222018-07-07 16:43:22 +0800322
323 eval_cmd = common_eval_cmd + ['--prebuilt', '--reinstall']
324 # Do not specify version for autotest prebuilt switching here. The trick is
325 # that version number is obtained via bisector's environment variable
326 # CROS_VERSION.
327 extra_switch_cmd = common_switch_cmd
328 if not diagnoser.narrow_down_chromeos_prebuilt(
329 opts.old, opts.new, eval_cmd, extra_switch_cmd=extra_switch_cmd):
330 return
331
332 diagnoser.switch_chromeos_to_old(force=opts.always_reflash)
333 util.check_call(*(common_switch_cmd + [diagnoser.cros_old]))
334 util.check_call('ssh', opts.dut, 'rm', '-rf', '/usr/local/autotest')
335
336 if diagnoser.narrow_down_android(eval_cmd) is not None:
337 return
338 # Assume it's ok to leave random version of android prebuilt on DUT.
339
340 # Don't --reinstall to keep chrome binaries override.
341 eval_cmd = common_eval_cmd + ['--prebuilt']
342 if diagnoser.narrow_down_chrome(
343 eval_cmd, chrome_binaries=chrome_binaries) is not None:
344 return
345
346 eval_cmd = common_eval_cmd + ['--reinstall']
347 if diagnoser.narrow_down_chromeos_localbuild(eval_cmd):
348 logger.info('%s done', __file__)
349
350
351if __name__ == '__main__':
352 main()