blob: 77e0cb2a4a70ce15695a489bc2fbd774f3b42d52 [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
30
31logger = logging.getLogger(__name__)
32
33# What chrome binaries to build for given autotest.
34# This dict is created manually by inspecting output of
35# 'grep -r ChromeBinaryTest autotest/files/client/site_tests'
36# If you change this dict, build_and_deploy_chrome_helper.sh may need update
37# as well.
38CHROME_BINARIES_OF_TEST = {
39 'graphics_Chrome.ozone_gl_unittests': ['ozone_gl_unittests'],
40 'security_SandboxLinuxUnittests': ['sandbox_linux_unittests'],
41 'video_HangoutHardwarePerf*': [
42 'video_decode_accelerator_unittest',
43 'video_encode_accelerator_unittest',
44 ],
45 'video_JDAPerf*': ['jpeg_decode_accelerator_unittest'],
46 'video_JEAPerf': ['jpeg_encode_accelerator_unittest'],
47 'video_JpegDecodeAccelerator': ['jpeg_decode_accelerator_unittest'],
48 'video_JpegEncodeAccelerator': ['jpeg_encode_accelerator_unittest'],
49 'video_VDAPerf': ['video_decode_accelerator_unittest'],
50 'video_VDASanity': ['video_decode_accelerator_unittest'],
51 'video_VEAPerf': ['video_encode_accelerator_unittest'],
52 'video_VideoDecodeAccelerator*': ['video_decode_accelerator_unittest'],
53 'video_VideoEncodeAccelerator*': ['video_encode_accelerator_unittest'],
54}
55
56
57def create_argument_parser():
58 parser = argparse.ArgumentParser()
59 common.add_common_arguments(parser)
60 parser.add_argument('--session_base', default='bisect.sessions')
61 parser.add_argument('--session', help='Session name', required=True)
62
63 group = parser.add_argument_group(title='Essential options')
64 group.add_argument(
65 '--chromeos_root',
66 metavar='CHROMEOS_ROOT',
67 type=cli.argtype_dir_path,
68 required=True,
69 default=configure.get('CHROMEOS_ROOT'),
70 help='ChromeOS tree root')
71 group.add_argument(
Kuang-che Wu94f48e52018-07-25 15:28:31 +080072 '--chromeos_repo_mirror_dir',
73 type=cli.argtype_dir_path,
74 default=configure.get('CHROMEOS_REPO_MIRROR_DIR', ''),
75 help='ChromeOS repo mirror path')
76 group.add_argument(
Kuang-che Wu1fcc0222018-07-07 16:43:22 +080077 '--android_root',
78 metavar='ANDROID_ROOT',
79 type=cli.argtype_dir_path,
80 required=True,
81 default=configure.get('ANDROID_ROOT'),
82 help='Android tree root')
83 group.add_argument(
84 '--android_repo_mirror_dir',
85 type=cli.argtype_dir_path,
86 required=True,
87 default=configure.get('ANDROID_REPO_MIRROR_DIR'),
88 help='Android repo mirror path')
89 group.add_argument(
90 '--chrome_root',
91 metavar='CHROME_ROOT',
92 type=cli.argtype_dir_path,
93 required=True,
94 default=configure.get('CHROME_ROOT'),
95 help='Chrome tree root')
96 group.add_argument(
Kuang-che Wu94f48e52018-07-25 15:28:31 +080097 '--gclient_cache_dir',
98 required=True,
99 metavar='GCLIENT_CACHE_DIR',
100 type=cli.argtype_dir_path,
101 default=configure.get('GCLIENT_CACHE_DIR'),
102 help='gclient cache dir')
103 group.add_argument(
Kuang-che Wu1fcc0222018-07-07 16:43:22 +0800104 '--old',
105 type=cros_util.argtype_cros_version,
106 required=True,
107 help='ChromeOS version with old behavior')
108 group.add_argument(
109 '--new',
110 type=cros_util.argtype_cros_version,
111 required=True,
112 help='ChromeOS version with new behavior')
113 group.add_argument('test_name', help='Test name')
114
115 group = parser.add_argument_group(title='DUT allocation options')
116 group.add_argument(
117 'dut',
118 metavar='DUT',
119 help='Address of DUT (Device Under Test). If "%s", DUT will be '
120 'automatically allocated from the lab' % cros_lab_util.LAB_DUT)
121 group.add_argument(
122 '--board',
123 metavar='BOARD',
124 default=configure.get('BOARD'),
125 help='ChromeOS board name')
126 group.add_argument(
127 '--sku',
128 metavar='SKU',
129 help='SKU criteria if DUT is auto allocated from the lab')
130
131 group = parser.add_argument_group(title='Options for benchmark test')
132 group.add_argument('--metric', help='Metric name of benchmark test')
133 group.add_argument(
134 '--old_value', type=float, help='For benchmark test, old value of metric')
135 group.add_argument(
136 '--new_value', type=float, help='For benchmark test, new value of metric')
137
138 group = parser.add_argument_group(title='Options passed to test_that')
139 group.add_argument(
140 '--args',
141 help='Extra args passed to "test_that --args"; Overrides the default')
142
143 group = parser.add_argument_group(title='Bisect behavior options')
144 group.add_argument(
145 '--noisy',
146 help='Enable noisy binary search. Example value: "old=1/10,new=2/3"')
147 group.add_argument(
148 '--always_reflash',
149 action='store_true',
150 help='Do not trust ChromeOS version number of DUT and always reflash. '
151 'This is usually only needed when resume because previous bisect was '
152 'interrupted and the DUT may be in an unexpected state')
153
154 return parser
155
156
157def grab_dut(opts):
158 # Assume "DEPENDENCIES" is identical between the period of
159 # `old` and `new` version.
160 autotest_dir = os.path.join(opts.chromeos_root,
161 cros_util.prebuilt_autotest_dir)
162 info = cros_util.get_autotest_test_info(autotest_dir, opts.test_name)
163 assert info
164
165 extra_labels = []
166 dependencies = info.variables.get('DEPENDENCIES', '')
167 for label in dependencies.split(','):
168 label = label.strip()
169 # Skip non-machine labels
170 if label in ['cleanup-reboot']:
171 continue
172 extra_labels.append(label)
173
174 reason = 'bisect-kit: %s' % opts.session
175 host = cros_lab_util.allocate_host(reason, opts.board, opts.sku, extra_labels)
176 if not host:
177 logger.error('unable to allocate dut')
178 return None
179
180 logger.info('allocated host %s', host)
181 return host
182
183
184def may_depend_on_extra_chrome_binaries(autotest_dir, test_name):
185 info = cros_util.get_autotest_test_info(autotest_dir, test_name)
186 assert info
187 dirpath = os.path.dirname(info.path)
188 for pypath in glob.glob(os.path.join(dirpath, '*.py')):
189 if 'ChromeBinaryTest' in open(pypath).read():
190 return True
191 return False
192
193
194def determine_chrome_binaries(opts):
195 chrome_binaries = None
196 for name_pattern, binaries in CHROME_BINARIES_OF_TEST.items():
197 if fnmatch.fnmatch(opts.test_name, name_pattern):
198 chrome_binaries = binaries
199 break
200
201 autotest_dir = os.path.join(opts.chromeos_root,
202 cros_util.prebuilt_autotest_dir)
203 if chrome_binaries:
204 logger.info('This test depends on chrome binary: %s', chrome_binaries)
205 elif may_depend_on_extra_chrome_binaries(autotest_dir, opts.test_name):
206 logger.warning('%s code used ChromeBinaryTest but the binary is unknown; '
207 'please update CHROME_BINARIES_OF_TEST table',
208 opts.test_name)
209 return chrome_binaries
210
211
212def check_options(opts, parser):
213 if opts.dut == cros_lab_util.LAB_DUT:
214 session_dir = os.path.join(opts.session_base, opts.session)
215 # 1 is log file
216 if os.path.exists(session_dir) and len(os.listdir(session_dir)) > 1:
217 parser.error('Do not resume if the DUT is dynamically allocated; '
218 '%s is not empty' % session_dir)
219
220 if not opts.board:
221 if opts.dut == cros_lab_util.LAB_DUT:
222 parser.error(
223 '--board need to be specified if DUT is "%s"' % cros_lab_util.LAB_DUT)
224 else:
225 opts.board = cros_util.query_dut_board(opts.dut)
226
227 if cros_util.is_cros_short_version(opts.old):
228 opts.old = cros_util.version_to_full(opts.board, opts.old)
229 if cros_util.is_cros_short_version(opts.new):
230 opts.new = cros_util.version_to_full(opts.board, opts.new)
231
232 if opts.metric:
233 if opts.old_value is None:
234 parser.error('--old_value is not provided')
235 if opts.new_value is None:
236 parser.error('--new_value is not provided')
237 else:
238 if opts.old_value is not None:
239 parser.error('--old_value is provided but --metric is not')
240 if opts.new_value is not None:
241 parser.error('--new_value is provided but --metric is not')
242
243
244def main(args=None):
245 common.init()
246 parser = create_argument_parser()
247 opts = parser.parse_args(args)
248 common.config_logging(opts)
249 check_options(opts, parser)
250
251 # prebuilt version will be specified later.
252 common_switch_cmd = [
253 './switch_autotest_prebuilt.py',
254 '--chromeos_root', opts.chromeos_root,
255 '--test_name', opts.test_name,
256 '--board', opts.board,
257 ] # yapf: disable
258
259 common_eval_cmd = [
260 './eval_cros_autotest.py',
261 '--chromeos_root', opts.chromeos_root,
262 '--test_name', opts.test_name,
263 ] # yapf: disable
264 if opts.metric:
265 common_eval_cmd += [
266 '--metric', opts.metric,
267 '--old_value', str(opts.old_value),
268 '--new_value', str(opts.new_value),
269 ] # yapf: disable
270 if opts.args:
271 common_eval_cmd += ['--args', opts.args]
272
273 # Unpack old autotest prebuilt, assume following information don't change
274 # between versions:
275 # - what chrome binaries to run
276 # - dependency labels for DUT allocation
277 util.check_call(*(common_switch_cmd + [opts.old]))
278
279 chrome_binaries = determine_chrome_binaries(opts)
280
281 with cros_lab_util.dut_manager(opts.dut, lambda: grab_dut(opts)) as dut:
282 if not dut:
283 raise core.ExecutionFatalError('unable to allocate DUT')
284 assert cros_util.is_dut(dut)
285 opts.dut = dut
286 common_eval_cmd.append(opts.dut)
287
288 diagnoser = diagnoser_cros.CrosDiagnoser(
Kuang-che Wu94f48e52018-07-25 15:28:31 +0800289 opts.session, opts.chromeos_root, opts.chromeos_repo_mirror_dir,
290 opts.android_root, opts.android_repo_mirror_dir, opts.chrome_root,
291 opts.gclient_cache_dir, opts.board, opts.noisy, opts.dut)
Kuang-che Wu1fcc0222018-07-07 16:43:22 +0800292
293 eval_cmd = common_eval_cmd + ['--prebuilt', '--reinstall']
294 # Do not specify version for autotest prebuilt switching here. The trick is
295 # that version number is obtained via bisector's environment variable
296 # CROS_VERSION.
297 extra_switch_cmd = common_switch_cmd
298 if not diagnoser.narrow_down_chromeos_prebuilt(
299 opts.old, opts.new, eval_cmd, extra_switch_cmd=extra_switch_cmd):
300 return
301
302 diagnoser.switch_chromeos_to_old(force=opts.always_reflash)
303 util.check_call(*(common_switch_cmd + [diagnoser.cros_old]))
304 util.check_call('ssh', opts.dut, 'rm', '-rf', '/usr/local/autotest')
305
306 if diagnoser.narrow_down_android(eval_cmd) is not None:
307 return
308 # Assume it's ok to leave random version of android prebuilt on DUT.
309
310 # Don't --reinstall to keep chrome binaries override.
311 eval_cmd = common_eval_cmd + ['--prebuilt']
312 if diagnoser.narrow_down_chrome(
313 eval_cmd, chrome_binaries=chrome_binaries) is not None:
314 return
315
316 eval_cmd = common_eval_cmd + ['--reinstall']
317 if diagnoser.narrow_down_chromeos_localbuild(eval_cmd):
318 logger.info('%s done', __file__)
319
320
321if __name__ == '__main__':
322 main()