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