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