import datetime import getpass import glob import os import pickle import re import threading import time import image_chromeos import machine_manager_singleton import table_formatter from cros_utils import command_executer from cros_utils import logger SCRATCH_DIR = '/home/%s/cros_scratch' % getpass.getuser() PICKLE_FILE = 'pickle.txt' VERSION = '1' def ConvertToFilename(text): ret = text ret = re.sub('/', '__', ret) ret = re.sub(' ', '_', ret) ret = re.sub('=', '', ret) ret = re.sub("\"", '', ret) return ret class AutotestRun(threading.Thread): def __init__(self, autotest, chromeos_root='', chromeos_image='', board='', remote='', iteration=0, image_checksum='', exact_remote=False, rerun=False, rerun_if_failed=False): self.autotest = autotest self.chromeos_root = chromeos_root self.chromeos_image = chromeos_image self.board = board self.remote = remote self.iteration = iteration l = logger.GetLogger() l.LogFatalIf(not image_checksum, "Checksum shouldn't be None") self.image_checksum = image_checksum self.results = {} threading.Thread.__init__(self) self.terminate = False self.retval = None self.status = 'PENDING' self.run_completed = False self.exact_remote = exact_remote self.rerun = rerun self.rerun_if_failed = rerun_if_failed self.results_dir = None self.full_name = None @staticmethod def MeanExcludingSlowest(array): mean = sum(array) / len(array) array2 = [] for v in array: if mean != 0 and abs(v - mean) / mean < 0.2: array2.append(v) if array2: return sum(array2) / len(array2) else: return mean @staticmethod def AddComposite(results_dict): composite_keys = [] composite_dict = {} for key in results_dict: mo = re.match('(.*){\d+}', key) if mo: composite_keys.append(mo.group(1)) for key in results_dict: for composite_key in composite_keys: if (key.count(composite_key) != 0 and table_formatter.IsFloat(results_dict[key])): if composite_key not in composite_dict: composite_dict[composite_key] = [] composite_dict[composite_key].append(float(results_dict[key])) break for composite_key in composite_dict: v = composite_dict[composite_key] results_dict['%s[c]' % composite_key] = sum(v) / len(v) mean_excluding_slowest = AutotestRun.MeanExcludingSlowest(v) results_dict['%s[ce]' % composite_key] = mean_excluding_slowest return results_dict def ParseOutput(self): p = re.compile('^-+.*?^-+', re.DOTALL | re.MULTILINE) matches = p.findall(self.out) for i in range(len(matches)): results = matches[i] results_dict = {} for line in results.splitlines()[1:-1]: mo = re.match('(.*\S)\s+\[\s+(PASSED|FAILED)\s+\]', line) if mo: results_dict[mo.group(1)] = mo.group(2) continue mo = re.match('(.*\S)\s+(.*)', line) if mo: results_dict[mo.group(1)] = mo.group(2) # Add a composite keyval for tests like startup. results_dict = AutotestRun.AddComposite(results_dict) self.results = results_dict # This causes it to not parse the table again # Autotest recently added a secondary table # That reports errors and screws up the final pretty output. break mo = re.search('Results placed in (\S+)', self.out) if mo: self.results_dir = mo.group(1) self.full_name = os.path.basename(self.results_dir) def GetCacheHashBase(self): ret = ('%s %s %s' % (self.image_checksum, self.autotest.name, self.iteration)) if self.autotest.args: ret += ' %s' % self.autotest.args ret += '-%s' % VERSION return ret def GetLabel(self): ret = '%s %s remote:%s' % (self.chromeos_image, self.autotest.name, self.remote) return ret def TryToLoadFromCache(self): base = self.GetCacheHashBase() if self.exact_remote: if not self.remote: return False cache_dir_glob = '%s_%s' % (ConvertToFilename(base), self.remote) else: cache_dir_glob = '%s*' % ConvertToFilename(base) cache_path_glob = os.path.join(SCRATCH_DIR, cache_dir_glob) matching_dirs = glob.glob(cache_path_glob) if matching_dirs: matching_dir = matching_dirs[0] cache_file = os.path.join(matching_dir, PICKLE_FILE) assert os.path.isfile(cache_file) self._logger.LogOutput('Trying to read from cache file: %s' % cache_file) return self.ReadFromCache(cache_file) self._logger.LogOutput('Cache miss. AM going to run: %s for: %s' % (self.autotest.name, self.chromeos_image)) return False def ReadFromCache(self, cache_file): with open(cache_file, 'rb') as f: self.retval = pickle.load(f) self.out = pickle.load(f) self.err = pickle.load(f) self._logger.LogOutput(self.out) return True return False def StoreToCache(self): base = self.GetCacheHashBase() self.cache_dir = os.path.join(SCRATCH_DIR, '%s_%s' % (ConvertToFilename(base), self.remote)) cache_file = os.path.join(self.cache_dir, PICKLE_FILE) command = 'mkdir -p %s' % os.path.dirname(cache_file) ret = self._ce.RunCommand(command) assert ret == 0, "Couldn't create cache dir" with open(cache_file, 'wb') as f: pickle.dump(self.retval, f) pickle.dump(self.out, f) pickle.dump(self.err, f) def run(self): self._logger = logger.Logger( os.path.dirname(__file__), '%s.%s' % (os.path.basename(__file__), self.name), True) self._ce = command_executer.GetCommandExecuter(self._logger) self.RunCached() def RunCached(self): self.status = 'WAITING' cache_hit = False if not self.rerun: cache_hit = self.TryToLoadFromCache() else: self._logger.LogOutput('--rerun passed. Not using cached results.') if self.rerun_if_failed and self.retval: self._logger.LogOutput('--rerun_if_failed passed and existing test ' 'failed. Rerunning...') cache_hit = False if not cache_hit: # Get machine while True: if self.terminate: return 1 self.machine = (machine_manager_singleton.MachineManagerSingleton( ).AcquireMachine(self.image_checksum)) if self.machine: self._logger.LogOutput('%s: Machine %s acquired at %s' % (self.name, self.machine.name, datetime.datetime.now())) break else: sleep_duration = 10 time.sleep(sleep_duration) try: self.remote = self.machine.name if self.machine.checksum != self.image_checksum: self.retval = self.ImageTo(self.machine.name) if self.retval: return self.retval self.machine.checksum = self.image_checksum self.machine.image = self.chromeos_image self.status = 'RUNNING: %s' % self.autotest.name [self.retval, self.out, self.err] = self.RunTestOn(self.machine.name) self.run_completed = True finally: self._logger.LogOutput('Releasing machine: %s' % self.machine.name) machine_manager_singleton.MachineManagerSingleton().ReleaseMachine( self.machine) self._logger.LogOutput('Released machine: %s' % self.machine.name) self.StoreToCache() if not self.retval: self.status = 'SUCCEEDED' else: self.status = 'FAILED' self.ParseOutput() # Copy results directory to the scratch dir if (not cache_hit and not self.retval and self.autotest.args and '--profile' in self.autotest.args): results_dir = os.path.join(self.chromeos_root, 'chroot', self.results_dir.lstrip('/')) tarball = os.path.join( self.cache_dir, os.path.basename(os.path.dirname(self.results_dir))) command = ('cd %s && tar cjf %s.tbz2 .' % (results_dir, tarball)) self._ce.RunCommand(command) perf_data_file = os.path.join(self.results_dir, self.full_name, 'profiling/iteration.1/perf.data') # Attempt to build a perf report and keep it with the results. command = ('cd %s/src/scripts &&' ' cros_sdk -- /usr/sbin/perf report --symfs=/build/%s' ' -i %s --stdio' % (self.chromeos_root, self.board, perf_data_file)) ret, out, err = self._ce.RunCommandWOutput(command) with open(os.path.join(self.cache_dir, 'perf.report'), 'wb') as f: f.write(out) return self.retval def ImageTo(self, machine_name): image_args = [image_chromeos.__file__, '--chromeos_root=%s' % self.chromeos_root, '--image=%s' % self.chromeos_image, '--remote=%s' % machine_name] if self.board: image_args.append('--board=%s' % self.board) ### devserver_port = 8080 ### mo = re.search("\d+", self.name) ### if mo: ### to_add = int(mo.group(0)) ### assert to_add < 100, "Too many threads launched!" ### devserver_port += to_add ### # I tried --noupdate_stateful, but that still fails when run in parallel. ### image_args.append("--image_to_live_args=\"--devserver_port=%s" ### " --noupdate_stateful\"" % devserver_port) ### image_args.append("--image_to_live_args=--devserver_port=%s" % ### devserver_port) # Currently can't image two machines at once. # So have to serialized on this lock. self.status = 'WAITING ON IMAGE_LOCK' with machine_manager_singleton.MachineManagerSingleton().image_lock: self.status = 'IMAGING' retval = self._ce.RunCommand(' '.join(['python'] + image_args)) machine_manager_singleton.MachineManagerSingleton().num_reimages += 1 if retval: self.status = 'ABORTED DUE TO IMAGE FAILURE' return retval def DoPowerdHack(self): command = 'sudo initctl stop powerd' self._ce.CrosRunCommand(command, machine=self.machine.name, chromeos_root=self.chromeos_root) def RunTestOn(self, machine_name): command = 'cd %s/src/scripts' % self.chromeos_root options = '' if self.board: options += ' --board=%s' % self.board if self.autotest.args: options += " --args='%s'" % self.autotest.args if 'tegra2' in self.board: self.DoPowerdHack() command += ('&& cros_sdk -- /usr/bin/test_that %s %s %s' % (options, machine_name, self.autotest.name)) return self._ce.RunCommand(command, True)