buildbot_test_toolchains.py revision e82513b0aec27bf5d3ca51789edc48dde5ee439b
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 benchmark: page_cycler_v2.typical_25 { 172 suite: telemetry_Crosperf 173 iterations: 2 174 run_local: False 175 retries: 0 176 } 177 """ 178 179 with open(experiment_file, 'w') as f: 180 f.write(experiment_header) 181 f.write(experiment_tests) 182 183 # Now add vanilla to test file. 184 official_image = """ 185 vanilla_image { 186 chromeos_root: %s 187 build: %s 188 compiler: gcc 189 } 190 """ % (self._chromeos_root, vanilla_image) 191 f.write(official_image) 192 193 # Now add non-AFDO image to test file. 194 if nonafdo_image: 195 official_nonafdo_image = """ 196 nonafdo_image { 197 chromeos_root: %s 198 build: %s 199 compiler: gcc 200 } 201 """ % (self._chromeos_root, nonafdo_image) 202 f.write(official_nonafdo_image) 203 204 label_string = '%s_trybot_image' % compiler_string 205 if USE_NEXT_GCC_PATCH in self._patches: 206 label_string = 'gcc_next_trybot_image' 207 208 experiment_image = """ 209 %s { 210 chromeos_root: %s 211 build: %s 212 compiler: %s 213 } 214 """ % (label_string, self._chromeos_root, trybot_image, 215 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 _CopyWeeklyReportFiles(self, trybot_image, vanilla_image, nonafdo_image): 237 """Put files in place for running seven-day reports. 238 239 Create tar files of the custom and official images and copy them 240 to the weekly reports directory, so they exist when the weekly report 241 gets generated. IMPORTANT NOTE: This function must run *after* 242 crosperf has been run; otherwise the vanilla images will not be there. 243 """ 244 245 dry_run = False 246 if os.getlogin() != ROLE_ACCOUNT: 247 self._l.LogOutput('Running this from non-role account; not copying ' 248 'tar files for weekly reports.') 249 dry_run = True 250 251 images_path = os.path.join( 252 os.path.realpath(self._chromeos_root), 'chroot/tmp') 253 254 data_dir = os.path.join(WEEKLY_REPORTS_ROOT, self._board) 255 dest_dir = os.path.join(data_dir, self._weekday) 256 if not os.path.exists(dest_dir): 257 os.makedirs(dest_dir) 258 259 # Make sure dest_dir is empty (clean out last week's data). 260 cmd = 'cd %s; rm -Rf %s_*_image*' % (dest_dir, self._weekday) 261 if dry_run: 262 print('CMD: %s' % cmd) 263 else: 264 self._ce.RunCommand(cmd) 265 266 # Now create new tar files and copy them over. 267 labels = {'test': trybot_image, 'vanilla': vanilla_image} 268 if nonafdo_image: 269 labels['nonafdo'] = nonafdo_image 270 for label_name, test_path in labels.iteritems(): 271 tar_file_name = '%s_%s_image.tar' % (self._weekday, label_name) 272 cmd = ('cd %s; tar -cvf %s %s/chromiumos_test_image.bin; ' 273 'cp %s %s/.') % (images_path, tar_file_name, test_path, 274 tar_file_name, dest_dir) 275 if dry_run: 276 print('CMD: %s' % cmd) 277 tar_ret = 0 278 else: 279 tar_ret = self._ce.RunCommand(cmd) 280 if tar_ret: 281 self._l.LogOutput('Error while creating/copying test tar file(%s).' % 282 tar_file_name) 283 284 def _SendEmail(self): 285 """Find email message generated by crosperf and send it.""" 286 filename = os.path.join(self._reports_dir, 'msg_body.html') 287 if (os.path.exists(filename) and 288 os.path.exists(os.path.expanduser(MAIL_PROGRAM))): 289 email_title = 'buildbot test results' 290 if self._patches_string == USE_LLVM_PATCH: 291 email_title = 'buildbot llvm test results' 292 command = ('cat %s | %s -s "%s, %s" -team -html' % 293 (filename, MAIL_PROGRAM, email_title, self._board)) 294 self._ce.RunCommand(command) 295 296 def DoAll(self): 297 """Main function inside ToolchainComparator class. 298 299 Launch trybot, get image names, create crosperf experiment file, run 300 crosperf, and copy images into seven-day report directories. 301 """ 302 date_str = datetime.date.today() 303 description = 'master_%s_%s_%s' % (self._patches_string, self._build, 304 date_str) 305 trybot_image = buildbot_utils.GetTrybotImage( 306 self._chromeos_root, 307 self._build, 308 self._patches, 309 description, 310 other_flags=['--notests'], 311 build_toolchain=True) 312 313 if len(trybot_image) == 0: 314 self._l.LogError('Unable to find trybot_image for %s!' % description) 315 return 1 316 317 vanilla_image = self._GetVanillaImageName(trybot_image) 318 nonafdo_image = self._GetNonAFDOImageName(trybot_image) 319 320 # The trybot image is ready here, in some cases, the vanilla image 321 # is not ready, so we need to make sure vanilla image is available. 322 buildbot_utils.WaitForImage(self._chromeos_root, vanilla_image) 323 print('trybot_image: %s' % trybot_image) 324 print('vanilla_image: %s' % vanilla_image) 325 print('nonafdo_image: %s' % nonafdo_image) 326 327 if os.getlogin() == ROLE_ACCOUNT: 328 self._FinishSetup() 329 330 self._TestImages(trybot_image, vanilla_image, nonafdo_image) 331 self._SendEmail() 332 if (self._patches_string == USE_NEXT_GCC_PATCH and 333 self._board in WEEKLY_REPORT_BOARDS): 334 # Only try to copy the image files if the test runs ran successfully. 335 self._CopyWeeklyReportFiles(trybot_image, vanilla_image, nonafdo_image) 336 return 0 337 338 339def Main(argv): 340 """The main function.""" 341 342 # Common initializations 343 command_executer.InitCommandExecuter() 344 parser = argparse.ArgumentParser() 345 parser.add_argument( 346 '--remote', dest='remote', help='Remote machines to run tests on.') 347 parser.add_argument( 348 '--board', dest='board', default='x86-zgb', help='The target board.') 349 parser.add_argument( 350 '--chromeos_root', 351 dest='chromeos_root', 352 help='The chromeos root from which to run tests.') 353 parser.add_argument( 354 '--weekday', 355 default='', 356 dest='weekday', 357 help='The day of the week for which to run tests.') 358 parser.add_argument( 359 '--patch', 360 dest='patches', 361 help='The patches to use for the testing, ' 362 "seprate the patch numbers with ',' " 363 'for more than one patches.') 364 parser.add_argument( 365 '--noschedv2', 366 dest='noschedv2', 367 action='store_true', 368 default=False, 369 help='Pass --noschedv2 to crosperf.') 370 371 options = parser.parse_args(argv[1:]) 372 if not options.board: 373 print('Please give a board.') 374 return 1 375 if not options.remote: 376 print('Please give at least one remote machine.') 377 return 1 378 if not options.chromeos_root: 379 print('Please specify the ChromeOS root directory.') 380 return 1 381 if options.patches: 382 patches = options.patches 383 else: 384 patches = USE_NEXT_GCC_PATCH 385 386 fc = ToolchainComparator(options.board, options.remote, options.chromeos_root, 387 options.weekday, patches, options.noschedv2) 388 return fc.DoAll() 389 390 391if __name__ == '__main__': 392 retval = Main(sys.argv) 393 sys.exit(retval) 394