#!/usr/bin/env python2 # Script to test different toolchains against ChromeOS benchmarks. """Toolchain team nightly performance test script (local builds).""" from __future__ import print_function import argparse import datetime import os import sys import build_chromeos import setup_chromeos from cros_utils import command_executer from cros_utils import misc from cros_utils import logger CROSTC_ROOT = '/usr/local/google/crostc' MAIL_PROGRAM = '~/var/bin/mail-sheriff' PENDING_ARCHIVES_DIR = os.path.join(CROSTC_ROOT, 'pending_archives') NIGHTLY_TESTS_DIR = os.path.join(CROSTC_ROOT, 'nightly_test_reports') class GCCConfig(object): """GCC configuration class.""" def __init__(self, githash): self.githash = githash class ToolchainConfig(object): """Toolchain configuration class.""" def __init__(self, gcc_config=None): self.gcc_config = gcc_config class ChromeOSCheckout(object): """Main class for checking out, building and testing ChromeOS.""" def __init__(self, board, chromeos_root): self._board = board self._chromeos_root = chromeos_root self._ce = command_executer.GetCommandExecuter() self._l = logger.GetLogger() self._build_num = None def _DeleteChroot(self): command = 'cd %s; cros_sdk --delete' % self._chromeos_root return self._ce.RunCommand(command) def _DeleteCcahe(self): # crosbug.com/34956 command = 'sudo rm -rf %s' % os.path.join(self._chromeos_root, '.cache') return self._ce.RunCommand(command) def _GetBuildNumber(self): """Get the build number of the ChromeOS image from the chroot. This function assumes a ChromeOS image has been built in the chroot. It translates the 'latest' symlink in the <chroot>/src/build/images/<board> directory, to find the actual ChromeOS build number for the image that was built. For example, if src/build/image/lumpy/latest -> R37-5982.0.2014_06_23_0454-a1, then This function would parse it out and assign 'R37-5982' to self._build_num. This is used to determine the official, vanilla build to use for comparison tests. """ # Get the path to 'latest' sym_path = os.path.join( misc.GetImageDir(self._chromeos_root, self._board), 'latest') # Translate the symlink to its 'real' path. real_path = os.path.realpath(sym_path) # Break up the path and get the last piece # (e.g. 'R37-5982.0.2014_06_23_0454-a1" path_pieces = real_path.split('/') last_piece = path_pieces[-1] # Break this piece into the image number + other pieces, and get the # image number [ 'R37-5982', '0', '2014_06_23_0454-a1'] image_parts = last_piece.split('.') self._build_num = image_parts[0] def _BuildLabelName(self, config): pieces = config.split('/') compiler_version = pieces[-1] label = compiler_version + '_tot_afdo' return label def _BuildAndImage(self, label=''): if (not label or not misc.DoesLabelExist(self._chromeos_root, self._board, label)): build_chromeos_args = [ build_chromeos.__file__, '--chromeos_root=%s' % self._chromeos_root, '--board=%s' % self._board, '--rebuild' ] if self._public: build_chromeos_args.append('--env=USE=-chrome_internal') ret = build_chromeos.Main(build_chromeos_args) if ret != 0: raise RuntimeError("Couldn't build ChromeOS!") if not self._build_num: self._GetBuildNumber() # Check to see if we need to create the symbolic link for the vanilla # image, and do so if appropriate. if not misc.DoesLabelExist(self._chromeos_root, self._board, 'vanilla'): build_name = '%s-release/%s.0.0' % (self._board, self._build_num) full_vanilla_path = os.path.join(os.getcwd(), self._chromeos_root, 'chroot/tmp', build_name) misc.LabelLatestImage(self._chromeos_root, self._board, label, full_vanilla_path) else: misc.LabelLatestImage(self._chromeos_root, self._board, label) return label def _SetupBoard(self, env_dict, usepkg_flag, clobber_flag): env_string = misc.GetEnvStringFromDict(env_dict) command = ('%s %s' % (env_string, misc.GetSetupBoardCommand( self._board, usepkg=usepkg_flag, force=clobber_flag))) ret = self._ce.ChrootRunCommand(self._chromeos_root, command) error_str = "Could not setup board: '%s'" % command assert ret == 0, error_str def _UnInstallToolchain(self): command = ('sudo CLEAN_DELAY=0 emerge -C cross-%s/gcc' % misc.GetCtargetFromBoard(self._board, self._chromeos_root)) ret = self._ce.ChrootRunCommand(self._chromeos_root, command) if ret != 0: raise RuntimeError("Couldn't uninstall the toolchain!") def _CheckoutChromeOS(self): # TODO(asharif): Setup a fixed ChromeOS version (quarterly snapshot). if not os.path.exists(self._chromeos_root): setup_chromeos_args = ['--dir=%s' % self._chromeos_root] if self._public: setup_chromeos_args.append('--public') ret = setup_chromeos.Main(setup_chromeos_args) if ret != 0: raise RuntimeError("Couldn't run setup_chromeos!") def _BuildToolchain(self, config): # Call setup_board for basic, vanilla setup. self._SetupBoard({}, usepkg_flag=True, clobber_flag=False) # Now uninstall the vanilla compiler and setup/build our custom # compiler. self._UnInstallToolchain() envdict = { 'USE': 'git_gcc', 'GCC_GITHASH': config.gcc_config.githash, 'EMERGE_DEFAULT_OPTS': '--exclude=gcc' } self._SetupBoard(envdict, usepkg_flag=False, clobber_flag=False) class ToolchainComparator(ChromeOSCheckout): """Main class for running tests and generating reports.""" def __init__(self, board, remotes, configs, clean, public, force_mismatch, noschedv2=False): self._board = board self._remotes = remotes self._chromeos_root = 'chromeos' self._configs = configs self._clean = clean self._public = public self._force_mismatch = force_mismatch self._ce = command_executer.GetCommandExecuter() self._l = logger.GetLogger() timestamp = datetime.datetime.strftime(datetime.datetime.now(), '%Y-%m-%d_%H:%M:%S') self._reports_dir = os.path.join( NIGHTLY_TESTS_DIR, '%s.%s' % (timestamp, board),) self._noschedv2 = noschedv2 ChromeOSCheckout.__init__(self, board, self._chromeos_root) def _FinishSetup(self): # Get correct .boto file current_dir = os.getcwd() src = '/usr/local/google/home/mobiletc-prebuild/.boto' dest = os.path.join(current_dir, self._chromeos_root, 'src/private-overlays/chromeos-overlay/' 'googlestorage_account.boto') # Copy the file to the correct place copy_cmd = 'cp %s %s' % (src, dest) retv = self._ce.RunCommand(copy_cmd) if retv != 0: raise RuntimeError("Couldn't copy .boto file for google storage.") # Fix protections on ssh key command = ('chmod 600 /var/cache/chromeos-cache/distfiles/target' '/chrome-src-internal/src/third_party/chromite/ssh_keys' '/testing_rsa') retv = self._ce.ChrootRunCommand(self._chromeos_root, command) if retv != 0: raise RuntimeError('chmod for testing_rsa failed') def _TestLabels(self, labels): experiment_file = 'toolchain_experiment.txt' image_args = '' if self._force_mismatch: image_args = '--force-mismatch' experiment_header = """ board: %s remote: %s retries: 1 """ % (self._board, self._remotes) experiment_tests = """ benchmark: all_toolchain_perf { suite: telemetry_Crosperf iterations: 3 } """ with open(experiment_file, 'w') as f: f.write(experiment_header) f.write(experiment_tests) for label in labels: # TODO(asharif): Fix crosperf so it accepts labels with symbols crosperf_label = label crosperf_label = crosperf_label.replace('-', '_') crosperf_label = crosperf_label.replace('+', '_') crosperf_label = crosperf_label.replace('.', '') # Use the official build instead of building vanilla ourselves. if label == 'vanilla': build_name = '%s-release/%s.0.0' % (self._board, self._build_num) # Now add 'official build' to test file. official_image = """ official_image { chromeos_root: %s build: %s } """ % (self._chromeos_root, build_name) f.write(official_image) else: experiment_image = """ %s { chromeos_image: %s image_args: %s } """ % (crosperf_label, os.path.join( misc.GetImageDir(self._chromeos_root, self._board), label, 'chromiumos_test_image.bin'), image_args) f.write(experiment_image) crosperf = os.path.join(os.path.dirname(__file__), 'crosperf', 'crosperf') noschedv2_opts = '--noschedv2' if self._noschedv2 else '' command = ('{crosperf} --no_email=True --results_dir={r_dir} ' '--json_report=True {noschedv2_opts} {exp_file}').format( crosperf=crosperf, r_dir=self._reports_dir, noschedv2_opts=noschedv2_opts, exp_file=experiment_file) ret = self._ce.RunCommand(command) if ret != 0: raise RuntimeError('Crosperf execution error!') else: # Copy json report to pending archives directory. command = 'cp %s/*.json %s/.' % (self._reports_dir, PENDING_ARCHIVES_DIR) ret = self._ce.RunCommand(command) return def _SendEmail(self): """Find email msesage generated by crosperf and send it.""" filename = os.path.join(self._reports_dir, 'msg_body.html') if (os.path.exists(filename) and os.path.exists(os.path.expanduser(MAIL_PROGRAM))): command = ('cat %s | %s -s "Nightly test results, %s" -team -html' % (filename, MAIL_PROGRAM, self._board)) self._ce.RunCommand(command) def DoAll(self): self._CheckoutChromeOS() labels = [] labels.append('vanilla') for config in self._configs: label = self._BuildLabelName(config.gcc_config.githash) if not misc.DoesLabelExist(self._chromeos_root, self._board, label): self._BuildToolchain(config) label = self._BuildAndImage(label) labels.append(label) self._FinishSetup() self._TestLabels(labels) self._SendEmail() if self._clean: ret = self._DeleteChroot() if ret != 0: return ret ret = self._DeleteCcahe() if ret != 0: return ret return 0 def Main(argv): """The main function.""" # Common initializations ### command_executer.InitCommandExecuter(True) command_executer.InitCommandExecuter() parser = argparse.ArgumentParser() parser.add_argument( '--remote', dest='remote', help='Remote machines to run tests on.') parser.add_argument( '--board', dest='board', default='x86-alex', help='The target board.') parser.add_argument( '--githashes', dest='githashes', default='master', help='The gcc githashes to test.') parser.add_argument( '--clean', dest='clean', default=False, action='store_true', help='Clean the chroot after testing.') parser.add_argument( '--public', dest='public', default=False, action='store_true', help='Use the public checkout/build.') parser.add_argument( '--force-mismatch', dest='force_mismatch', default='', help='Force the image regardless of board mismatch') parser.add_argument( '--noschedv2', dest='noschedv2', action='store_true', default=False, help='Pass --noschedv2 to crosperf.') options = parser.parse_args(argv) if not options.board: print('Please give a board.') return 1 if not options.remote: print('Please give at least one remote machine.') return 1 toolchain_configs = [] for githash in options.githashes.split(','): gcc_config = GCCConfig(githash=githash) toolchain_config = ToolchainConfig(gcc_config=gcc_config) toolchain_configs.append(toolchain_config) fc = ToolchainComparator(options.board, options.remote, toolchain_configs, options.clean, options.public, options.force_mismatch, options.noschedv2) return fc.DoAll() if __name__ == '__main__': retval = Main(sys.argv[1:]) sys.exit(retval)