test_toolchains.py revision aa700b0a78b10b709b990bcb535bc5588ebb2bb4
1#!/usr/bin/python 2 3# Script to test different toolchains against ChromeOS benchmarks. 4import datetime 5import optparse 6import os 7import sys 8import build_chromeos 9import setup_chromeos 10import time 11from utils import command_executer 12from utils import misc 13from utils import logger 14 15 16WEEKLY_REPORTS_ROOT="/usr/local/google/crostc/weekly_test_data" 17AFDO_BOARDS = ["butterfly", "lumpy", "stumpy", "stout", "parrot", "parrot_ivb"] 18MAIL_PROGRAM = "~/var/bin/mail-sheriff" 19 20class GCCConfig(object): 21 def __init__(self, githash): 22 self.githash = githash 23 24 25class ToolchainConfig: 26 def __init__(self, gcc_config=None, binutils_config=None): 27 self.gcc_config = gcc_config 28 29 30class ChromeOSCheckout(object): 31 def __init__(self, board, chromeos_root): 32 self._board = board 33 self._chromeos_root = chromeos_root 34 self._ce = command_executer.GetCommandExecuter() 35 self._l = logger.GetLogger() 36 self._build_num = None 37 38 def _DeleteChroot(self): 39 command = "cd %s; cros_sdk --delete" % self._chromeos_root 40 return self._ce.RunCommand(command) 41 42 def _DeleteCcahe(self): 43 # crosbug.com/34956 44 command = "sudo rm -rf %s" % os.path.join(self._chromeos_root, ".cache") 45 return self._ce.RunCommand(command) 46 47 def _GetBuildNumber(self): 48 """ This function assumes a ChromeOS image has been built in the chroot. 49 It translates the 'latest' symlink in the 50 <chroot>/src/build/images/<board> directory, to find the actual 51 ChromeOS build number for the image that was built. For example, if 52 src/build/image/lumpy/latest -> R37-5982.0.2014_06_23_0454-a1, then 53 This function would parse it out and assign 'R37-5982' to self._build_num. 54 This is used to determine the official, vanilla build to use for 55 comparison tests. 56 """ 57 # Get the path to 'latest' 58 sym_path = os.path.join (misc.GetImageDir(self._chromeos_root, 59 self._board), 60 "latest") 61 # Translate the symlink to its 'real' path. 62 real_path = os.path.realpath(sym_path) 63 # Break up the path and get the last piece 64 # (e.g. 'R37-5982.0.2014_06_23_0454-a1" 65 path_pieces = real_path.split("/") 66 last_piece = path_pieces[-1] 67 # Break this piece into the image number + other pieces, and get the 68 # image number [ 'R37-5982', '0', '2014_06_23_0454-a1'] 69 image_parts = last_piece.split(".") 70 self._build_num = image_parts[0] 71 72 def _BuildAndImage(self, label=""): 73 if (not label or 74 not misc.DoesLabelExist(self._chromeos_root, self._board, label)): 75 build_chromeos_args = [build_chromeos.__file__, 76 "--chromeos_root=%s" % self._chromeos_root, 77 "--board=%s" % self._board, 78 "--rebuild"] 79 if self._public: 80 build_chromeos_args.append("--env=USE=-chrome_internal") 81 82 if self._board in AFDO_BOARDS: 83 build_chromeos_args.append("--env=USE=afdo_use") 84 85 ret = build_chromeos.Main(build_chromeos_args) 86 if ret != 0: 87 raise RuntimeError("Couldn't build ChromeOS!") 88 89 if not self._build_num: 90 self._GetBuildNumber() 91 # Check to see if we need to create the symbolic link for the vanilla 92 # image, and do so if appropriate. 93 if not misc.DoesLabelExist(self._chromeos_root, self._board, "vanilla"): 94 build_name = "%s-release/%s.0.0" % (self._board, self._build_num) 95 full_vanilla_path = os.path.join (os.getcwd(), self._chromeos_root, 96 'chroot/tmp', build_name) 97 misc.LabelLatestImage(self._chromeos_root, self._board, label, 98 full_vanilla_path) 99 else: 100 misc.LabelLatestImage(self._chromeos_root, self._board, label) 101 return label 102 103 def _SetupBoard(self, env_dict, usepkg_flag, clobber_flag): 104 env_string = misc.GetEnvStringFromDict(env_dict) 105 command = ("%s %s" % 106 (env_string, 107 misc.GetSetupBoardCommand(self._board, 108 usepkg=usepkg_flag, 109 force=clobber_flag))) 110 ret = self._ce.ChrootRunCommand(self._chromeos_root, 111 command) 112 error_str = "Could not setup board: '%s'" % command 113 assert ret == 0, error_str 114 115 def _UnInstallToolchain(self): 116 command = ("sudo CLEAN_DELAY=0 emerge -C cross-%s/gcc" % 117 misc.GetCtargetFromBoard(self._board, 118 self._chromeos_root)) 119 ret = self._ce.ChrootRunCommand(self._chromeos_root, 120 command) 121 if ret != 0: 122 raise RuntimeError("Couldn't uninstall the toolchain!") 123 124 def _CheckoutChromeOS(self): 125 # TODO(asharif): Setup a fixed ChromeOS version (quarterly snapshot). 126 if not os.path.exists(self._chromeos_root): 127 setup_chromeos_args = [setup_chromeos.__file__, 128 "--dir=%s" % self._chromeos_root] 129 if self._public: 130 setup_chromeos_args.append("--public") 131 ret = setup_chromeos.Main(setup_chromeos_args) 132 if ret != 0: 133 raise RuntimeError("Couldn't run setup_chromeos!") 134 135 136 def _BuildToolchain(self, config): 137 # Call setup_board for basic, vanilla setup. 138 self._SetupBoard({}, usepkg_flag=True, clobber_flag=False) 139 # Now uninstall the vanilla compiler and setup/build our custom 140 # compiler. 141 self._UnInstallToolchain() 142 envdict = {"USE": "git_gcc", 143 "GCC_GITHASH": config.gcc_config.githash, 144 "EMERGE_DEFAULT_OPTS": "--exclude=gcc"} 145 self._SetupBoard(envdict, usepkg_flag=False, clobber_flag=False) 146 147 148class ToolchainComparator(ChromeOSCheckout): 149 def __init__(self, board, remotes, configs, clean, public, force_mismatch): 150 self._board = board 151 self._remotes = remotes 152 self._chromeos_root = "chromeos" 153 self._configs = configs 154 self._clean = clean 155 self._public = public 156 self._force_mismatch = force_mismatch 157 self._ce = command_executer.GetCommandExecuter() 158 self._l = logger.GetLogger() 159 timestamp = datetime.datetime.strftime(datetime.datetime.now(), 160 "%Y-%m-%d_%H:%M:%S") 161 self._reports_dir = os.path.join( 162 os.path.expanduser("~/nightly_test_reports"), 163 "%s.%s" % (timestamp, board), 164 ) 165 ChromeOSCheckout.__init__(self, board, self._chromeos_root) 166 167 168 def _FinishSetup(self): 169 # Get correct .boto file 170 current_dir = os.getcwd() 171 src = "/home/mobiletc-prebuild/.boto" 172 dest = os.path.join(current_dir, self._chromeos_root, 173 "src/private-overlays/chromeos-overlay/" 174 "googlestorage_account.boto") 175 # Copy the file to the correct place 176 copy_cmd = "cp %s %s" % (src, dest) 177 retval = self._ce.RunCommand(copy_cmd) 178 if retval != 0: 179 raise RuntimeError("Couldn't copy .boto file for google storage.") 180 181 # Fix protections on ssh key 182 command = ("chmod 600 /var/cache/chromeos-cache/distfiles/target" 183 "/chrome-src-internal/src/third_party/chromite/ssh_keys" 184 "/testing_rsa") 185 retval = self._ce.ChrootRunCommand(self._chromeos_root, command) 186 if retval != 0: 187 raise RuntimeError("chmod for testing_rsa failed") 188 189 def _TestLabels(self, labels): 190 experiment_file = "toolchain_experiment.txt" 191 image_args = "" 192 if self._force_mismatch: 193 image_args = "--force-mismatch" 194 experiment_header = """ 195 board: %s 196 remote: %s 197 """ % (self._board, self._remotes) 198 experiment_tests = """ 199 benchmark: all_perfv2 { 200 suite: telemetry_Crosperf 201 iterations: 3 202 } 203 """ 204 with open(experiment_file, "w") as f: 205 print >>f, experiment_header 206 print >>f, experiment_tests 207 for label in labels: 208 # TODO(asharif): Fix crosperf so it accepts labels with symbols 209 crosperf_label = label 210 crosperf_label = crosperf_label.replace("-", "minus") 211 crosperf_label = crosperf_label.replace("+", "plus") 212 crosperf_label = crosperf_label.replace(".", "") 213 214 # Use the official build instead of building vanilla ourselves. 215 if label == "vanilla": 216 build_name = '%s-release/%s.0.0' % (self._board, self._build_num) 217 218 # Now add 'official build' to test file. 219 official_image = """ 220 official_image { 221 chromeos_root: %s 222 build: %s 223 } 224 """ % (self._chromeos_root, build_name) 225 print >>f, official_image 226 227 else: 228 experiment_image = """ 229 %s { 230 chromeos_image: %s 231 image_args: %s 232 } 233 """ % (crosperf_label, 234 os.path.join(misc.GetImageDir(self._chromeos_root, 235 self._board), 236 label, "chromiumos_test_image.bin"), 237 image_args) 238 print >>f, experiment_image 239 240 crosperf = os.path.join(os.path.dirname(__file__), 241 "crosperf", 242 "crosperf") 243 command = ("%s --no_email=True --use_file_locks=True --results_dir=%s %s" % 244 (crosperf, self._reports_dir, experiment_file) 245 246 ret = self._ce.RunCommand(command) 247 if ret != 0: 248 raise RuntimeError("Couldn't run crosperf!") 249 return 250 251 252 def _CopyWeeklyReportFiles(self, labels): 253 """Create tar files of the custom and official images and copy them 254 to the weekly reports directory, so they exist when the weekly report 255 gets generated. IMPORTANT NOTE: This function must run *after* 256 crosperf has been run; otherwise the vanilla images will not be there. 257 """ 258 images_path = os.path.join(os.path.realpath(self._chromeos_root), 259 "src/build/images", self._board) 260 weekday = time.strftime("%a") 261 data_dir = os.path.join(WEEKLY_REPORTS_ROOT, self._board) 262 dest_dir = os.path.join (data_dir, weekday) 263 if not os.path.exists(dest_dir): 264 os.makedirs(dest_dir) 265 # Make sure dest_dir is empty (clean out last week's data). 266 cmd = "cd %s; rm -Rf %s_*_image*" % (dest_dir, weekday) 267 self._ce.RunCommand(cmd) 268 # Now create new tar files and copy them over. 269 for l in labels: 270 test_path = os.path.join(images_path, l) 271 if os.path.exists(test_path): 272 if l != "vanilla": 273 label_name = "test" 274 else: 275 label_name = "vanilla" 276 tar_file_name = "%s_%s_image.tar" % (weekday, label_name) 277 cmd = ("cd %s; tar -cvf %s %s/chromiumos_test_image.bin; " 278 "cp %s %s/.") % (images_path, 279 tar_file_name, 280 l, tar_file_name, 281 dest_dir) 282 tar_ret = self._ce.RunCommand(cmd) 283 if tar_ret != 0: 284 self._l.LogOutput("Error while creating/copying test tar file(%s)." 285 % tar_file_name) 286 287 def _SendEmail(self): 288 """Find email msesage generated by crosperf and send it.""" 289 filename = os.path.join(self._reports_dir, "msg_body.html") 290 if (os.path.exists(filename) and 291 os.path.exists(os.path.expanduser(MAIL_PROGRAM))): 292 command = ('cat %s | %s -s "Nightly test results, %s" -team -html' 293 % (filename, MAIL_PROGRAM, self._board)) 294 self._ce.RunCommand(command) 295 296 def DoAll(self): 297 self._CheckoutChromeOS() 298 labels = [] 299 labels.append("vanilla") 300 for config in self._configs: 301 label = misc.GetFilenameFromString(config.gcc_config.githash) 302 if (not misc.DoesLabelExist(self._chromeos_root, 303 self._board, 304 label)): 305 self._BuildToolchain(config) 306 label = self._BuildAndImage(label) 307 labels.append(label) 308 self._FinishSetup() 309 self._TestLabels(labels) 310 self._SendEmail() 311 # Only try to copy the image files if the test runs ran successfully. 312 self._CopyWeeklyReportFiles(labels) 313 if self._clean: 314 ret = self._DeleteChroot() 315 if ret != 0: 316 return ret 317 ret = self._DeleteCcahe() 318 if ret != 0: 319 return ret 320 return 0 321 322 323def Main(argv): 324 """The main function.""" 325 # Common initializations 326### command_executer.InitCommandExecuter(True) 327 command_executer.InitCommandExecuter() 328 parser = optparse.OptionParser() 329 parser.add_option("--remote", 330 dest="remote", 331 help="Remote machines to run tests on.") 332 parser.add_option("--board", 333 dest="board", 334 default="x86-zgb", 335 help="The target board.") 336 parser.add_option("--githashes", 337 dest="githashes", 338 default="master", 339 help="The gcc githashes to test.") 340 parser.add_option("--clean", 341 dest="clean", 342 default=False, 343 action="store_true", 344 help="Clean the chroot after testing.") 345 parser.add_option("--public", 346 dest="public", 347 default=False, 348 action="store_true", 349 help="Use the public checkout/build.") 350 parser.add_option("--force-mismatch", 351 dest="force_mismatch", 352 default="", 353 help="Force the image regardless of board mismatch") 354 options, _ = parser.parse_args(argv) 355 if not options.board: 356 print "Please give a board." 357 return 1 358 if not options.remote: 359 print "Please give at least one remote machine." 360 return 1 361 toolchain_configs = [] 362 for githash in options.githashes.split(","): 363 gcc_config = GCCConfig(githash=githash) 364 toolchain_config = ToolchainConfig(gcc_config=gcc_config) 365 toolchain_configs.append(toolchain_config) 366 fc = ToolchainComparator(options.board, options.remote, toolchain_configs, 367 options.clean, options.public, 368 options.force_mismatch) 369 return fc.DoAll() 370 371 372if __name__ == "__main__": 373 retval = Main(sys.argv) 374 sys.exit(retval) 375