1 #!/usr/bin/python 2 # 3 # Copyright 2010 Google Inc. All Rights Reserved. 4 5 import datetime 6 import optparse 7 import os 8 import smtplib 9 import sys 10 import time 11 from email.mime.text import MIMEText 12 13 from autotest_gatherer import AutotestGatherer as AutotestGatherer 14 from autotest_run import AutotestRun as AutotestRun 15 from machine_manager_singleton import MachineManagerSingleton as MachineManagerSingleton 16 from cros_utils import logger 17 from cros_utils.file_utils import FileUtils 18 19 20 def CanonicalizeChromeOSRoot(chromeos_root): 21 chromeos_root = os.path.expanduser(chromeos_root) 22 if os.path.isfile(os.path.join(chromeos_root, 'src/scripts/enter_chroot.sh')): 23 return chromeos_root 24 else: 25 return None 26 27 28 class Autotest(object): 29 30 def __init__(self, autotest_string): 31 self.name = None 32 self.iterations = None 33 self.args = None 34 fields = autotest_string.split(',', 1) 35 self.name = fields[0] 36 if len(fields) > 1: 37 autotest_string = fields[1] 38 fields = autotest_string.split(',', 1) 39 else: 40 return 41 self.iterations = int(fields[0]) 42 if len(fields) > 1: 43 self.args = fields[1] 44 else: 45 return 46 47 def __str__(self): 48 return '\n'.join([self.name, self.iterations, self.args]) 49 50 51 def CreateAutotestListFromString(autotest_strings, default_iterations=None): 52 autotest_list = [] 53 for autotest_string in autotest_strings.split(':'): 54 autotest = Autotest(autotest_string) 55 if default_iterations and not autotest.iterations: 56 autotest.iterations = default_iterations 57 58 autotest_list.append(autotest) 59 return autotest_list 60 61 62 def CreateAutotestRuns(images, 63 autotests, 64 remote, 65 board, 66 exact_remote, 67 rerun, 68 rerun_if_failed, 69 main_chromeos_root=None): 70 autotest_runs = [] 71 for image in images: 72 logger.GetLogger().LogOutput('Computing md5sum of: %s' % image) 73 image_checksum = FileUtils().Md5File(image) 74 logger.GetLogger().LogOutput('md5sum %s: %s' % (image, image_checksum)) 75 ### image_checksum = "abcdefghi" 76 77 chromeos_root = main_chromeos_root 78 if not main_chromeos_root: 79 image_chromeos_root = os.path.join( 80 os.path.dirname(image), '../../../../..') 81 chromeos_root = CanonicalizeChromeOSRoot(image_chromeos_root) 82 assert chromeos_root, 'chromeos_root: %s invalid' % image_chromeos_root 83 else: 84 chromeos_root = CanonicalizeChromeOSRoot(main_chromeos_root) 85 assert chromeos_root, 'chromeos_root: %s invalid' % main_chromeos_root 86 87 # We just need a single ChromeOS root in the MachineManagerSingleton. It is 88 # needed because we can save re-image time by checking the image checksum at 89 # the beginning and assigning autotests to machines appropriately. 90 if not MachineManagerSingleton().chromeos_root: 91 MachineManagerSingleton().chromeos_root = chromeos_root 92 93 for autotest in autotests: 94 for iteration in range(autotest.iterations): 95 autotest_run = AutotestRun(autotest, 96 chromeos_root=chromeos_root, 97 chromeos_image=image, 98 board=board, 99 remote=remote, 100 iteration=iteration, 101 image_checksum=image_checksum, 102 exact_remote=exact_remote, 103 rerun=rerun, 104 rerun_if_failed=rerun_if_failed) 105 autotest_runs.append(autotest_run) 106 return autotest_runs 107 108 109 def GetNamesAndIterations(autotest_runs): 110 strings = [] 111 for autotest_run in autotest_runs: 112 strings.append('%s:%s' % (autotest_run.autotest.name, 113 autotest_run.iteration)) 114 return ' %s (%s)' % (len(strings), ' '.join(strings)) 115 116 117 def GetStatusString(autotest_runs): 118 status_bins = {} 119 for autotest_run in autotest_runs: 120 if autotest_run.status not in status_bins: 121 status_bins[autotest_run.status] = [] 122 status_bins[autotest_run.status].append(autotest_run) 123 124 status_strings = [] 125 for key, val in status_bins.items(): 126 status_strings.append('%s: %s' % (key, GetNamesAndIterations(val))) 127 return 'Thread Status:\n%s' % '\n'.join(status_strings) 128 129 130 def GetProgressBar(num_done, num_total): 131 ret = 'Done: %s%%' % int(100.0 * num_done / num_total) 132 bar_length = 50 133 done_char = '>' 134 undone_char = ' ' 135 num_done_chars = bar_length * num_done / num_total 136 num_undone_chars = bar_length - num_done_chars 137 ret += ' [%s%s]' % (num_done_chars * done_char, 138 num_undone_chars * undone_char) 139 return ret 140 141 142 def GetProgressString(start_time, num_remain, num_total): 143 current_time = time.time() 144 elapsed_time = current_time - start_time 145 try: 146 eta_seconds = float(num_remain) * elapsed_time / (num_total - num_remain) 147 eta_seconds = int(eta_seconds) 148 eta = datetime.timedelta(seconds=eta_seconds) 149 except ZeroDivisionError: 150 eta = 'Unknown' 151 strings = [] 152 strings.append('Current time: %s Elapsed: %s ETA: %s' % 153 (datetime.datetime.now(), 154 datetime.timedelta(seconds=int(elapsed_time)), eta)) 155 strings.append(GetProgressBar(num_total - num_remain, num_total)) 156 return '\n'.join(strings) 157 158 159 def RunAutotestRunsInParallel(autotest_runs): 160 start_time = time.time() 161 active_threads = [] 162 for autotest_run in autotest_runs: 163 # Set threads to daemon so program exits when ctrl-c is pressed. 164 autotest_run.daemon = True 165 autotest_run.start() 166 active_threads.append(autotest_run) 167 168 print_interval = 30 169 last_printed_time = time.time() 170 while active_threads: 171 try: 172 active_threads = [t for t in active_threads 173 if t is not None and t.isAlive()] 174 for t in active_threads: 175 t.join(1) 176 if time.time() - last_printed_time > print_interval: 177 border = '==============================' 178 logger.GetLogger().LogOutput(border) 179 logger.GetLogger().LogOutput(GetProgressString(start_time, len( 180 [t for t in autotest_runs if t.status not in ['SUCCEEDED', 'FAILED'] 181 ]), len(autotest_runs))) 182 logger.GetLogger().LogOutput(GetStatusString(autotest_runs)) 183 logger.GetLogger().LogOutput('%s\n' % 184 MachineManagerSingleton().AsString()) 185 logger.GetLogger().LogOutput(border) 186 last_printed_time = time.time() 187 except KeyboardInterrupt: 188 print 'C-c received... cleaning up threads.' 189 for t in active_threads: 190 t.terminate = True 191 return 1 192 return 0 193 194 195 def RunAutotestRunsSerially(autotest_runs): 196 for autotest_run in autotest_runs: 197 retval = autotest_run.Run() 198 if retval: 199 return retval 200 201 202 def ProduceTables(autotest_runs, full_table, fit_string): 203 l = logger.GetLogger() 204 ags_dict = {} 205 for autotest_run in autotest_runs: 206 name = autotest_run.full_name 207 if name not in ags_dict: 208 ags_dict[name] = AutotestGatherer() 209 ags_dict[name].runs.append(autotest_run) 210 output = '' 211 for b, ag in ags_dict.items(): 212 output += 'Benchmark: %s\n' % b 213 output += ag.GetFormattedMainTable(percents_only=not full_table, 214 fit_string=fit_string) 215 output += '\n' 216 217 summary = '' 218 for b, ag in ags_dict.items(): 219 summary += 'Benchmark Summary Table: %s\n' % b 220 summary += ag.GetFormattedSummaryTable(percents_only=not full_table, 221 fit_string=fit_string) 222 summary += '\n' 223 224 output += summary 225 output += ('Number of re-images performed: %s' % 226 MachineManagerSingleton().num_reimages) 227 l.LogOutput(output) 228 229 if autotest_runs: 230 board = autotest_runs[0].board 231 else: 232 board = '' 233 234 subject = '%s: %s' % (board, ', '.join(ags_dict.keys())) 235 236 if any(autotest_run.run_completed for autotest_run in autotest_runs): 237 SendEmailToUser(subject, summary) 238 239 240 def SendEmailToUser(subject, text_to_send): 241 # Email summary to the current user. 242 msg = MIMEText(text_to_send) 243 244 # me == the sender's email address 245 # you == the recipient's email address 246 me = os.path.basename(__file__) 247 you = os.getlogin() 248 msg['Subject'] = '[%s] %s' % (os.path.basename(__file__), subject) 249 msg['From'] = me 250 msg['To'] = you 251 252 # Send the message via our own SMTP server, but don't include the 253 # envelope header. 254 s = smtplib.SMTP('localhost') 255 s.sendmail(me, [you], msg.as_string()) 256 s.quit() 257 258 259 def Main(argv): 260 """The main function.""" 261 # Common initializations 262 ### command_executer.InitCommandExecuter(True) 263 l = logger.GetLogger() 264 265 parser = optparse.OptionParser() 266 parser.add_option('-t', 267 '--tests', 268 dest='tests', 269 help=('Tests to compare.' 270 'Optionally specify per-test iterations by:' 271 '<test>,<iter>:<args>')) 272 parser.add_option('-c', 273 '--chromeos_root', 274 dest='chromeos_root', 275 help='A *single* chromeos_root where scripts can be found.') 276 parser.add_option('-n', 277 '--iterations', 278 dest='iterations', 279 help='Iterations to run per benchmark.', 280 default=1) 281 parser.add_option('-r', 282 '--remote', 283 dest='remote', 284 help='The remote chromeos machine.') 285 parser.add_option('-b', '--board', dest='board', help='The remote board.') 286 parser.add_option('--full_table', 287 dest='full_table', 288 help='Print full tables.', 289 action='store_true', 290 default=True) 291 parser.add_option('--exact_remote', 292 dest='exact_remote', 293 help='Run tests on the exact remote.', 294 action='store_true', 295 default=False) 296 parser.add_option('--fit_string', 297 dest='fit_string', 298 help='Fit strings to fixed sizes.', 299 action='store_true', 300 default=False) 301 parser.add_option('--rerun', 302 dest='rerun', 303 help='Re-run regardless of cache hit.', 304 action='store_true', 305 default=False) 306 parser.add_option('--rerun_if_failed', 307 dest='rerun_if_failed', 308 help='Re-run if previous run was a failure.', 309 action='store_true', 310 default=False) 311 parser.add_option('--no_lock', 312 dest='no_lock', 313 help='Do not lock the machine before running the tests.', 314 action='store_true', 315 default=False) 316 l.LogOutput(' '.join(argv)) 317 [options, args] = parser.parse_args(argv) 318 319 if options.remote is None: 320 l.LogError('No remote machine specified.') 321 parser.print_help() 322 return 1 323 324 if not options.board: 325 l.LogError('No board specified.') 326 parser.print_help() 327 return 1 328 329 remote = options.remote 330 tests = options.tests 331 board = options.board 332 exact_remote = options.exact_remote 333 iterations = int(options.iterations) 334 335 autotests = CreateAutotestListFromString(tests, iterations) 336 337 main_chromeos_root = options.chromeos_root 338 images = args[1:] 339 fit_string = options.fit_string 340 full_table = options.full_table 341 rerun = options.rerun 342 rerun_if_failed = options.rerun_if_failed 343 344 MachineManagerSingleton().no_lock = options.no_lock 345 346 # Now try creating all the Autotests 347 autotest_runs = CreateAutotestRuns(images, autotests, remote, board, 348 exact_remote, rerun, rerun_if_failed, 349 main_chromeos_root) 350 351 try: 352 # At this point we have all the autotest runs. 353 for machine in remote.split(','): 354 MachineManagerSingleton().AddMachine(machine) 355 356 retval = RunAutotestRunsInParallel(autotest_runs) 357 if retval: 358 return retval 359 360 # Now print tables 361 ProduceTables(autotest_runs, full_table, fit_string) 362 finally: 363 # not sure why this isn't called at the end normally... 364 MachineManagerSingleton().__del__() 365 366 return 0 367 368 369 if __name__ == '__main__': 370 sys.exit(Main(sys.argv)) 371