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