| 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) |