1 #!/usr/bin/env python2 2 # 3 # Copyright 2016 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 """Script for running nightly compiler tests on ChromeOS. 7 8 This script launches a buildbot to build ChromeOS with the latest compiler on 9 a particular board; then it finds and downloads the trybot image and the 10 corresponding official image, and runs crosperf performance tests comparing 11 the two. It then generates a report, emails it to the c-compiler-chrome, as 12 well as copying the images into the seven-day reports directory. 13 """ 14 15 # Script to test different toolchains against ChromeOS benchmarks. 16 17 from __future__ import print_function 18 19 import argparse 20 import datetime 21 import os 22 import re 23 import sys 24 import time 25 26 from cros_utils import command_executer 27 from cros_utils import logger 28 29 from cros_utils import buildbot_utils 30 31 # CL that uses LLVM-Next to build the images (includes chrome). 32 USE_LLVM_NEXT_PATCH = '513590' 33 34 CROSTC_ROOT = '/usr/local/google/crostc' 35 ROLE_ACCOUNT = 'mobiletc-prebuild' 36 TOOLCHAIN_DIR = os.path.dirname(os.path.realpath(__file__)) 37 MAIL_PROGRAM = '~/var/bin/mail-sheriff' 38 PENDING_ARCHIVES_DIR = os.path.join(CROSTC_ROOT, 'pending_archives') 39 NIGHTLY_TESTS_DIR = os.path.join(CROSTC_ROOT, 'nightly_test_reports') 40 41 IMAGE_DIR = '{board}-{image_type}' 42 IMAGE_VERSION_STR = r'{chrome_version}-{tip}\.{branch}\.{branch_branch}' 43 IMAGE_FS = IMAGE_DIR + '/' + IMAGE_VERSION_STR 44 TRYBOT_IMAGE_FS = 'trybot-' + IMAGE_FS + '-{build_id}' 45 PFQ_IMAGE_FS = IMAGE_FS + '-rc1' 46 IMAGE_RE_GROUPS = { 47 'board': r'(?P<board>\S+)', 48 'image_type': r'(?P<image_type>\S+)', 49 'chrome_version': r'(?P<chrome_version>R\d+)', 50 'tip': r'(?P<tip>\d+)', 51 'branch': r'(?P<branch>\d+)', 52 'branch_branch': r'(?P<branch_branch>\d+)', 53 'build_id': r'(?P<build_id>b\d+)' 54 } 55 TRYBOT_IMAGE_RE = TRYBOT_IMAGE_FS.format(**IMAGE_RE_GROUPS) 56 57 58 class ToolchainComparator(object): 59 """Class for doing the nightly tests work.""" 60 61 def __init__(self, 62 board, 63 remotes, 64 chromeos_root, 65 weekday, 66 patches, 67 noschedv2=False): 68 self._board = board 69 self._remotes = remotes 70 self._chromeos_root = chromeos_root 71 self._base_dir = os.getcwd() 72 self._ce = command_executer.GetCommandExecuter() 73 self._l = logger.GetLogger() 74 self._build = '%s-release' % board 75 self._patches = patches.split(',') if patches else [] 76 self._patches_string = '_'.join(str(p) for p in self._patches) 77 self._noschedv2 = noschedv2 78 79 if not weekday: 80 self._weekday = time.strftime('%a') 81 else: 82 self._weekday = weekday 83 timestamp = datetime.datetime.strftime(datetime.datetime.now(), 84 '%Y-%m-%d_%H:%M:%S') 85 self._reports_dir = os.path.join( 86 NIGHTLY_TESTS_DIR, 87 '%s.%s' % (timestamp, board),) 88 89 def _GetVanillaImageName(self, trybot_image): 90 """Given a trybot artifact name, get latest vanilla image name. 91 92 Args: 93 trybot_image: artifact name such as 94 'trybot-daisy-release/R40-6394.0.0-b1389' 95 96 Returns: 97 Latest official image name, e.g. 'daisy-release/R57-9089.0.0'. 98 """ 99 mo = re.search(TRYBOT_IMAGE_RE, trybot_image) 100 assert mo 101 dirname = IMAGE_DIR.replace('\\', '').format(**mo.groupdict()) 102 return buildbot_utils.GetLatestImage(self._chromeos_root, dirname) 103 104 def _GetNonAFDOImageName(self, trybot_image): 105 """Given a trybot artifact name, get corresponding non-AFDO image name. 106 107 We get the non-AFDO image from the PFQ builders. This image 108 is not generated for all the boards and, the closest PFQ image 109 was the one build for the previous ChromeOS version (the chrome 110 used in the current version is the one validated in the previous 111 version). 112 The previous ChromeOS does not always exist either. So, we try 113 a couple of versions before. 114 115 Args: 116 trybot_image: artifact name such as 117 'trybot-daisy-release/R40-6394.0.0-b1389' 118 119 Returns: 120 Corresponding chrome PFQ image name, e.g. 121 'daisy-chrome-pfq/R40-6393.0.0-rc1'. 122 """ 123 mo = re.search(TRYBOT_IMAGE_RE, trybot_image) 124 assert mo 125 image_dict = mo.groupdict() 126 image_dict['image_type'] = 'chrome-pfq' 127 for _ in xrange(2): 128 image_dict['tip'] = str(int(image_dict['tip']) - 1) 129 nonafdo_image = PFQ_IMAGE_FS.replace('\\', '').format(**image_dict) 130 if buildbot_utils.DoesImageExist(self._chromeos_root, nonafdo_image): 131 return nonafdo_image 132 return '' 133 134 def _FinishSetup(self): 135 """Make sure testing_rsa file is properly set up.""" 136 # Fix protections on ssh key 137 command = ('chmod 600 /var/cache/chromeos-cache/distfiles/target' 138 '/chrome-src-internal/src/third_party/chromite/ssh_keys' 139 '/testing_rsa') 140 ret_val = self._ce.ChrootRunCommand(self._chromeos_root, command) 141 if ret_val != 0: 142 raise RuntimeError('chmod for testing_rsa failed') 143 144 def _TestImages(self, trybot_image, vanilla_image, nonafdo_image): 145 """Create crosperf experiment file. 146 147 Given the names of the trybot, vanilla and non-AFDO images, create the 148 appropriate crosperf experiment file and launch crosperf on it. 149 """ 150 experiment_file_dir = os.path.join(self._chromeos_root, '..', self._weekday) 151 experiment_file_name = '%s_toolchain_experiment.txt' % self._board 152 153 compiler_string = 'llvm' 154 if USE_LLVM_NEXT_PATCH in self._patches_string: 155 experiment_file_name = '%s_llvm_next_experiment.txt' % self._board 156 compiler_string = 'llvm_next' 157 158 experiment_file = os.path.join(experiment_file_dir, experiment_file_name) 159 experiment_header = """ 160 board: %s 161 remote: %s 162 retries: 1 163 """ % (self._board, self._remotes) 164 experiment_tests = """ 165 benchmark: all_toolchain_perf { 166 suite: telemetry_Crosperf 167 iterations: 0 168 } 169 170 benchmark: page_cycler_v2.typical_25 { 171 suite: telemetry_Crosperf 172 iterations: 0 173 run_local: False 174 retries: 0 175 } 176 """ 177 178 with open(experiment_file, 'w') as f: 179 f.write(experiment_header) 180 f.write(experiment_tests) 181 182 # Now add vanilla to test file. 183 official_image = """ 184 vanilla_image { 185 chromeos_root: %s 186 build: %s 187 compiler: llvm 188 } 189 """ % (self._chromeos_root, vanilla_image) 190 f.write(official_image) 191 192 # Now add non-AFDO image to test file. 193 if nonafdo_image: 194 official_nonafdo_image = """ 195 nonafdo_image { 196 chromeos_root: %s 197 build: %s 198 compiler: llvm 199 } 200 """ % (self._chromeos_root, nonafdo_image) 201 f.write(official_nonafdo_image) 202 203 label_string = '%s_trybot_image' % compiler_string 204 205 # Reuse autotest files from vanilla image for trybot images 206 autotest_files = os.path.join('/tmp', vanilla_image, 'autotest_files') 207 experiment_image = """ 208 %s { 209 chromeos_root: %s 210 build: %s 211 autotest_path: %s 212 compiler: %s 213 } 214 """ % (label_string, self._chromeos_root, trybot_image, 215 autotest_files, compiler_string) 216 f.write(experiment_image) 217 218 crosperf = os.path.join(TOOLCHAIN_DIR, 'crosperf', 'crosperf') 219 noschedv2_opts = '--noschedv2' if self._noschedv2 else '' 220 command = ('{crosperf} --no_email=True --results_dir={r_dir} ' 221 '--json_report=True {noschedv2_opts} {exp_file}').format( 222 crosperf=crosperf, 223 r_dir=self._reports_dir, 224 noschedv2_opts=noschedv2_opts, 225 exp_file=experiment_file) 226 227 ret = self._ce.RunCommand(command) 228 if ret != 0: 229 raise RuntimeError('Crosperf execution error!') 230 else: 231 # Copy json report to pending archives directory. 232 command = 'cp %s/*.json %s/.' % (self._reports_dir, PENDING_ARCHIVES_DIR) 233 ret = self._ce.RunCommand(command) 234 return 235 236 def _SendEmail(self): 237 """Find email message generated by crosperf and send it.""" 238 filename = os.path.join(self._reports_dir, 'msg_body.html') 239 if (os.path.exists(filename) and 240 os.path.exists(os.path.expanduser(MAIL_PROGRAM))): 241 email_title = 'buildbot llvm test results' 242 if USE_LLVM_NEXT_PATCH in self._patches_string: 243 email_title = 'buildbot llvm_next test results' 244 command = ('cat %s | %s -s "%s, %s" -team -html' % 245 (filename, MAIL_PROGRAM, email_title, self._board)) 246 self._ce.RunCommand(command) 247 248 def DoAll(self): 249 """Main function inside ToolchainComparator class. 250 251 Launch trybot, get image names, create crosperf experiment file, run 252 crosperf, and copy images into seven-day report directories. 253 """ 254 date_str = datetime.date.today() 255 description = 'master_%s_%s_%s' % (self._patches_string, self._build, 256 date_str) 257 build_id, trybot_image = buildbot_utils.GetTrybotImage( 258 self._chromeos_root, 259 self._build, 260 self._patches, 261 description, 262 other_flags=['--notests'], 263 build_toolchain=True) 264 265 print('trybot_url: \ 266 https://uberchromegw.corp.google.com/i/chromiumos.tryserver/builders/release/builds/%s' 267 % build_id) 268 if len(trybot_image) == 0: 269 self._l.LogError('Unable to find trybot_image for %s!' % description) 270 return 1 271 272 vanilla_image = self._GetVanillaImageName(trybot_image) 273 nonafdo_image = self._GetNonAFDOImageName(trybot_image) 274 275 print('trybot_image: %s' % trybot_image) 276 print('vanilla_image: %s' % vanilla_image) 277 print('nonafdo_image: %s' % nonafdo_image) 278 279 if os.getlogin() == ROLE_ACCOUNT: 280 self._FinishSetup() 281 282 self._TestImages(trybot_image, vanilla_image, nonafdo_image) 283 self._SendEmail() 284 return 0 285 286 287 def Main(argv): 288 """The main function.""" 289 290 # Common initializations 291 command_executer.InitCommandExecuter() 292 parser = argparse.ArgumentParser() 293 parser.add_argument( 294 '--remote', dest='remote', help='Remote machines to run tests on.') 295 parser.add_argument( 296 '--board', dest='board', default='x86-zgb', help='The target board.') 297 parser.add_argument( 298 '--chromeos_root', 299 dest='chromeos_root', 300 help='The chromeos root from which to run tests.') 301 parser.add_argument( 302 '--weekday', 303 default='', 304 dest='weekday', 305 help='The day of the week for which to run tests.') 306 parser.add_argument( 307 '--patch', 308 dest='patches', 309 help='The patches to use for the testing, ' 310 "seprate the patch numbers with ',' " 311 'for more than one patches.') 312 parser.add_argument( 313 '--noschedv2', 314 dest='noschedv2', 315 action='store_true', 316 default=False, 317 help='Pass --noschedv2 to crosperf.') 318 319 options = parser.parse_args(argv[1:]) 320 if not options.board: 321 print('Please give a board.') 322 return 1 323 if not options.remote: 324 print('Please give at least one remote machine.') 325 return 1 326 if not options.chromeos_root: 327 print('Please specify the ChromeOS root directory.') 328 return 1 329 330 fc = ToolchainComparator(options.board, options.remote, options.chromeos_root, 331 options.weekday, options.patches, options.noschedv2) 332 return fc.DoAll() 333 334 335 if __name__ == '__main__': 336 retval = Main(sys.argv) 337 sys.exit(retval) 338