| #!/usr/bin/python |
| # |
| # Copyright 2010 Google Inc. All Rights Reserved. |
| |
| import datetime |
| import optparse |
| import os |
| import smtplib |
| import sys |
| import time |
| from email.mime.text import MIMEText |
| |
| from autotest_gatherer import AutotestGatherer as AutotestGatherer |
| from autotest_run import AutotestRun as AutotestRun |
| from machine_manager_singleton import MachineManagerSingleton as MachineManagerSingleton |
| from cros_utils import logger |
| from cros_utils.file_utils import FileUtils |
| |
| |
| def CanonicalizeChromeOSRoot(chromeos_root): |
| chromeos_root = os.path.expanduser(chromeos_root) |
| if os.path.isfile(os.path.join(chromeos_root, 'src/scripts/enter_chroot.sh')): |
| return chromeos_root |
| else: |
| return None |
| |
| |
| class Autotest(object): |
| |
| def __init__(self, autotest_string): |
| self.name = None |
| self.iterations = None |
| self.args = None |
| fields = autotest_string.split(',', 1) |
| self.name = fields[0] |
| if len(fields) > 1: |
| autotest_string = fields[1] |
| fields = autotest_string.split(',', 1) |
| else: |
| return |
| self.iterations = int(fields[0]) |
| if len(fields) > 1: |
| self.args = fields[1] |
| else: |
| return |
| |
| def __str__(self): |
| return '\n'.join([self.name, self.iterations, self.args]) |
| |
| |
| def CreateAutotestListFromString(autotest_strings, default_iterations=None): |
| autotest_list = [] |
| for autotest_string in autotest_strings.split(':'): |
| autotest = Autotest(autotest_string) |
| if default_iterations and not autotest.iterations: |
| autotest.iterations = default_iterations |
| |
| autotest_list.append(autotest) |
| return autotest_list |
| |
| |
| def CreateAutotestRuns(images, |
| autotests, |
| remote, |
| board, |
| exact_remote, |
| rerun, |
| rerun_if_failed, |
| main_chromeos_root=None): |
| autotest_runs = [] |
| for image in images: |
| logger.GetLogger().LogOutput('Computing md5sum of: %s' % image) |
| image_checksum = FileUtils().Md5File(image) |
| logger.GetLogger().LogOutput('md5sum %s: %s' % (image, image_checksum)) |
| ### image_checksum = "abcdefghi" |
| |
| chromeos_root = main_chromeos_root |
| if not main_chromeos_root: |
| image_chromeos_root = os.path.join( |
| os.path.dirname(image), '../../../../..') |
| chromeos_root = CanonicalizeChromeOSRoot(image_chromeos_root) |
| assert chromeos_root, 'chromeos_root: %s invalid' % image_chromeos_root |
| else: |
| chromeos_root = CanonicalizeChromeOSRoot(main_chromeos_root) |
| assert chromeos_root, 'chromeos_root: %s invalid' % main_chromeos_root |
| |
| # We just need a single ChromeOS root in the MachineManagerSingleton. It is |
| # needed because we can save re-image time by checking the image checksum at |
| # the beginning and assigning autotests to machines appropriately. |
| if not MachineManagerSingleton().chromeos_root: |
| MachineManagerSingleton().chromeos_root = chromeos_root |
| |
| for autotest in autotests: |
| for iteration in range(autotest.iterations): |
| autotest_run = AutotestRun(autotest, |
| chromeos_root=chromeos_root, |
| chromeos_image=image, |
| board=board, |
| remote=remote, |
| iteration=iteration, |
| image_checksum=image_checksum, |
| exact_remote=exact_remote, |
| rerun=rerun, |
| rerun_if_failed=rerun_if_failed) |
| autotest_runs.append(autotest_run) |
| return autotest_runs |
| |
| |
| def GetNamesAndIterations(autotest_runs): |
| strings = [] |
| for autotest_run in autotest_runs: |
| strings.append('%s:%s' % (autotest_run.autotest.name, |
| autotest_run.iteration)) |
| return ' %s (%s)' % (len(strings), ' '.join(strings)) |
| |
| |
| def GetStatusString(autotest_runs): |
| status_bins = {} |
| for autotest_run in autotest_runs: |
| if autotest_run.status not in status_bins: |
| status_bins[autotest_run.status] = [] |
| status_bins[autotest_run.status].append(autotest_run) |
| |
| status_strings = [] |
| for key, val in status_bins.items(): |
| status_strings.append('%s: %s' % (key, GetNamesAndIterations(val))) |
| return 'Thread Status:\n%s' % '\n'.join(status_strings) |
| |
| |
| def GetProgressBar(num_done, num_total): |
| ret = 'Done: %s%%' % int(100.0 * num_done / num_total) |
| bar_length = 50 |
| done_char = '>' |
| undone_char = ' ' |
| num_done_chars = bar_length * num_done / num_total |
| num_undone_chars = bar_length - num_done_chars |
| ret += ' [%s%s]' % (num_done_chars * done_char, |
| num_undone_chars * undone_char) |
| return ret |
| |
| |
| def GetProgressString(start_time, num_remain, num_total): |
| current_time = time.time() |
| elapsed_time = current_time - start_time |
| try: |
| eta_seconds = float(num_remain) * elapsed_time / (num_total - num_remain) |
| eta_seconds = int(eta_seconds) |
| eta = datetime.timedelta(seconds=eta_seconds) |
| except ZeroDivisionError: |
| eta = 'Unknown' |
| strings = [] |
| strings.append('Current time: %s Elapsed: %s ETA: %s' % |
| (datetime.datetime.now(), |
| datetime.timedelta(seconds=int(elapsed_time)), eta)) |
| strings.append(GetProgressBar(num_total - num_remain, num_total)) |
| return '\n'.join(strings) |
| |
| |
| def RunAutotestRunsInParallel(autotest_runs): |
| start_time = time.time() |
| active_threads = [] |
| for autotest_run in autotest_runs: |
| # Set threads to daemon so program exits when ctrl-c is pressed. |
| autotest_run.daemon = True |
| autotest_run.start() |
| active_threads.append(autotest_run) |
| |
| print_interval = 30 |
| last_printed_time = time.time() |
| while active_threads: |
| try: |
| active_threads = [t for t in active_threads |
| if t is not None and t.isAlive()] |
| for t in active_threads: |
| t.join(1) |
| if time.time() - last_printed_time > print_interval: |
| border = '==============================' |
| logger.GetLogger().LogOutput(border) |
| logger.GetLogger().LogOutput(GetProgressString(start_time, len( |
| [t for t in autotest_runs if t.status not in ['SUCCEEDED', 'FAILED'] |
| ]), len(autotest_runs))) |
| logger.GetLogger().LogOutput(GetStatusString(autotest_runs)) |
| logger.GetLogger().LogOutput('%s\n' % |
| MachineManagerSingleton().AsString()) |
| logger.GetLogger().LogOutput(border) |
| last_printed_time = time.time() |
| except KeyboardInterrupt: |
| print 'C-c received... cleaning up threads.' |
| for t in active_threads: |
| t.terminate = True |
| return 1 |
| return 0 |
| |
| |
| def RunAutotestRunsSerially(autotest_runs): |
| for autotest_run in autotest_runs: |
| retval = autotest_run.Run() |
| if retval: |
| return retval |
| |
| |
| def ProduceTables(autotest_runs, full_table, fit_string): |
| l = logger.GetLogger() |
| ags_dict = {} |
| for autotest_run in autotest_runs: |
| name = autotest_run.full_name |
| if name not in ags_dict: |
| ags_dict[name] = AutotestGatherer() |
| ags_dict[name].runs.append(autotest_run) |
| output = '' |
| for b, ag in ags_dict.items(): |
| output += 'Benchmark: %s\n' % b |
| output += ag.GetFormattedMainTable(percents_only=not full_table, |
| fit_string=fit_string) |
| output += '\n' |
| |
| summary = '' |
| for b, ag in ags_dict.items(): |
| summary += 'Benchmark Summary Table: %s\n' % b |
| summary += ag.GetFormattedSummaryTable(percents_only=not full_table, |
| fit_string=fit_string) |
| summary += '\n' |
| |
| output += summary |
| output += ('Number of re-images performed: %s' % |
| MachineManagerSingleton().num_reimages) |
| l.LogOutput(output) |
| |
| if autotest_runs: |
| board = autotest_runs[0].board |
| else: |
| board = '' |
| |
| subject = '%s: %s' % (board, ', '.join(ags_dict.keys())) |
| |
| if any(autotest_run.run_completed for autotest_run in autotest_runs): |
| SendEmailToUser(subject, summary) |
| |
| |
| def SendEmailToUser(subject, text_to_send): |
| # Email summary to the current user. |
| msg = MIMEText(text_to_send) |
| |
| # me == the sender's email address |
| # you == the recipient's email address |
| me = os.path.basename(__file__) |
| you = os.getlogin() |
| msg['Subject'] = '[%s] %s' % (os.path.basename(__file__), subject) |
| msg['From'] = me |
| msg['To'] = you |
| |
| # Send the message via our own SMTP server, but don't include the |
| # envelope header. |
| s = smtplib.SMTP('localhost') |
| s.sendmail(me, [you], msg.as_string()) |
| s.quit() |
| |
| |
| def Main(argv): |
| """The main function.""" |
| # Common initializations |
| ### command_executer.InitCommandExecuter(True) |
| l = logger.GetLogger() |
| |
| parser = optparse.OptionParser() |
| parser.add_option('-t', |
| '--tests', |
| dest='tests', |
| help=('Tests to compare.' |
| 'Optionally specify per-test iterations by:' |
| '<test>,<iter>:<args>')) |
| parser.add_option('-c', |
| '--chromeos_root', |
| dest='chromeos_root', |
| help='A *single* chromeos_root where scripts can be found.') |
| parser.add_option('-n', |
| '--iterations', |
| dest='iterations', |
| help='Iterations to run per benchmark.', |
| default=1) |
| parser.add_option('-r', |
| '--remote', |
| dest='remote', |
| help='The remote chromeos machine.') |
| parser.add_option('-b', '--board', dest='board', help='The remote board.') |
| parser.add_option('--full_table', |
| dest='full_table', |
| help='Print full tables.', |
| action='store_true', |
| default=True) |
| parser.add_option('--exact_remote', |
| dest='exact_remote', |
| help='Run tests on the exact remote.', |
| action='store_true', |
| default=False) |
| parser.add_option('--fit_string', |
| dest='fit_string', |
| help='Fit strings to fixed sizes.', |
| action='store_true', |
| default=False) |
| parser.add_option('--rerun', |
| dest='rerun', |
| help='Re-run regardless of cache hit.', |
| action='store_true', |
| default=False) |
| parser.add_option('--rerun_if_failed', |
| dest='rerun_if_failed', |
| help='Re-run if previous run was a failure.', |
| action='store_true', |
| default=False) |
| parser.add_option('--no_lock', |
| dest='no_lock', |
| help='Do not lock the machine before running the tests.', |
| action='store_true', |
| default=False) |
| l.LogOutput(' '.join(argv)) |
| [options, args] = parser.parse_args(argv) |
| |
| if options.remote is None: |
| l.LogError('No remote machine specified.') |
| parser.print_help() |
| return 1 |
| |
| if not options.board: |
| l.LogError('No board specified.') |
| parser.print_help() |
| return 1 |
| |
| remote = options.remote |
| tests = options.tests |
| board = options.board |
| exact_remote = options.exact_remote |
| iterations = int(options.iterations) |
| |
| autotests = CreateAutotestListFromString(tests, iterations) |
| |
| main_chromeos_root = options.chromeos_root |
| images = args[1:] |
| fit_string = options.fit_string |
| full_table = options.full_table |
| rerun = options.rerun |
| rerun_if_failed = options.rerun_if_failed |
| |
| MachineManagerSingleton().no_lock = options.no_lock |
| |
| # Now try creating all the Autotests |
| autotest_runs = CreateAutotestRuns(images, autotests, remote, board, |
| exact_remote, rerun, rerun_if_failed, |
| main_chromeos_root) |
| |
| try: |
| # At this point we have all the autotest runs. |
| for machine in remote.split(','): |
| MachineManagerSingleton().AddMachine(machine) |
| |
| retval = RunAutotestRunsInParallel(autotest_runs) |
| if retval: |
| return retval |
| |
| # Now print tables |
| ProduceTables(autotest_runs, full_table, fit_string) |
| finally: |
| # not sure why this isn't called at the end normally... |
| MachineManagerSingleton().__del__() |
| |
| return 0 |
| |
| |
| if __name__ == '__main__': |
| sys.exit(Main(sys.argv)) |