1*760c253cSXin Li# -*- coding: utf-8 -*- 2*760c253cSXin Li# Copyright 2011 The ChromiumOS Authors 3*760c253cSXin Li# Use of this source code is governed by a BSD-style license that can be 4*760c253cSXin Li# found in the LICENSE file. 5*760c253cSXin Li 6*760c253cSXin Li"""The class to show the banner.""" 7*760c253cSXin Li 8*760c253cSXin Li 9*760c253cSXin Liimport collections 10*760c253cSXin Liimport datetime 11*760c253cSXin Liimport time 12*760c253cSXin Li 13*760c253cSXin Li 14*760c253cSXin Liclass ExperimentStatus(object): 15*760c253cSXin Li """The status class.""" 16*760c253cSXin Li 17*760c253cSXin Li def __init__(self, experiment): 18*760c253cSXin Li self.experiment = experiment 19*760c253cSXin Li self.num_total = len(self.experiment.benchmark_runs) 20*760c253cSXin Li self.completed = 0 21*760c253cSXin Li self.new_job_start_time = time.time() 22*760c253cSXin Li self.log_level = experiment.log_level 23*760c253cSXin Li 24*760c253cSXin Li def _GetProgressBar(self, num_complete, num_total): 25*760c253cSXin Li ret = "Done: %s%%" % int(100.0 * num_complete / num_total) 26*760c253cSXin Li bar_length = 50 27*760c253cSXin Li done_char = ">" 28*760c253cSXin Li undone_char = " " 29*760c253cSXin Li num_complete_chars = bar_length * num_complete // num_total 30*760c253cSXin Li num_undone_chars = bar_length - num_complete_chars 31*760c253cSXin Li ret += " [%s%s]" % ( 32*760c253cSXin Li num_complete_chars * done_char, 33*760c253cSXin Li num_undone_chars * undone_char, 34*760c253cSXin Li ) 35*760c253cSXin Li return ret 36*760c253cSXin Li 37*760c253cSXin Li def GetProgressString(self): 38*760c253cSXin Li """Get the elapsed_time, ETA.""" 39*760c253cSXin Li current_time = time.time() 40*760c253cSXin Li if self.experiment.start_time: 41*760c253cSXin Li elapsed_time = current_time - self.experiment.start_time 42*760c253cSXin Li else: 43*760c253cSXin Li elapsed_time = 0 44*760c253cSXin Li try: 45*760c253cSXin Li if self.completed != self.experiment.num_complete: 46*760c253cSXin Li self.completed = self.experiment.num_complete 47*760c253cSXin Li self.new_job_start_time = current_time 48*760c253cSXin Li time_completed_jobs = elapsed_time - ( 49*760c253cSXin Li current_time - self.new_job_start_time 50*760c253cSXin Li ) 51*760c253cSXin Li # eta is calculated as: 52*760c253cSXin Li # ETA = (num_jobs_not_yet_started * estimated_time_per_job) 53*760c253cSXin Li # + time_left_for_current_job 54*760c253cSXin Li # 55*760c253cSXin Li # where 56*760c253cSXin Li # num_jobs_not_yet_started = (num_total - num_complete - 1) 57*760c253cSXin Li # 58*760c253cSXin Li # estimated_time_per_job = time_completed_jobs / num_run_complete 59*760c253cSXin Li # 60*760c253cSXin Li # time_left_for_current_job = estimated_time_per_job - 61*760c253cSXin Li # time_spent_so_far_on_current_job 62*760c253cSXin Li # 63*760c253cSXin Li # The biggest problem with this calculation is its assumption that 64*760c253cSXin Li # all jobs have roughly the same running time (blatantly false!). 65*760c253cSXin Li # 66*760c253cSXin Li # ETA can come out negative if the time spent on the current job is 67*760c253cSXin Li # greater than the estimated time per job (e.g. you're running the 68*760c253cSXin Li # first long job, after a series of short jobs). For now, if that 69*760c253cSXin Li # happens, we set the ETA to "Unknown." 70*760c253cSXin Li # 71*760c253cSXin Li eta_seconds = float( 72*760c253cSXin Li self.num_total - self.experiment.num_complete - 1 73*760c253cSXin Li ) * time_completed_jobs / self.experiment.num_run_complete + ( 74*760c253cSXin Li time_completed_jobs / self.experiment.num_run_complete 75*760c253cSXin Li - (current_time - self.new_job_start_time) 76*760c253cSXin Li ) 77*760c253cSXin Li 78*760c253cSXin Li eta_seconds = int(eta_seconds) 79*760c253cSXin Li if eta_seconds > 0: 80*760c253cSXin Li eta = datetime.timedelta(seconds=eta_seconds) 81*760c253cSXin Li else: 82*760c253cSXin Li eta = "Unknown" 83*760c253cSXin Li except ZeroDivisionError: 84*760c253cSXin Li eta = "Unknown" 85*760c253cSXin Li strings = [] 86*760c253cSXin Li strings.append( 87*760c253cSXin Li "Current time: %s Elapsed: %s ETA: %s" 88*760c253cSXin Li % ( 89*760c253cSXin Li datetime.datetime.now(), 90*760c253cSXin Li datetime.timedelta(seconds=int(elapsed_time)), 91*760c253cSXin Li eta, 92*760c253cSXin Li ) 93*760c253cSXin Li ) 94*760c253cSXin Li strings.append( 95*760c253cSXin Li self._GetProgressBar(self.experiment.num_complete, self.num_total) 96*760c253cSXin Li ) 97*760c253cSXin Li return "\n".join(strings) 98*760c253cSXin Li 99*760c253cSXin Li def GetStatusString(self): 100*760c253cSXin Li """Get the status string of all the benchmark_runs.""" 101*760c253cSXin Li status_bins = collections.defaultdict(list) 102*760c253cSXin Li for benchmark_run in self.experiment.benchmark_runs: 103*760c253cSXin Li status_bins[benchmark_run.timeline.GetLastEvent()].append( 104*760c253cSXin Li benchmark_run 105*760c253cSXin Li ) 106*760c253cSXin Li 107*760c253cSXin Li status_strings = [] 108*760c253cSXin Li for key, val in status_bins.items(): 109*760c253cSXin Li if key == "RUNNING": 110*760c253cSXin Li get_description = self._GetNamesAndIterations 111*760c253cSXin Li else: 112*760c253cSXin Li get_description = self._GetCompactNamesAndIterations 113*760c253cSXin Li status_strings.append("%s: %s" % (key, get_description(val))) 114*760c253cSXin Li 115*760c253cSXin Li thread_status = "" 116*760c253cSXin Li thread_status_format = "Thread Status: \n{}\n" 117*760c253cSXin Li if ( 118*760c253cSXin Li self.experiment.schedv2() is None 119*760c253cSXin Li and self.experiment.log_level == "verbose" 120*760c253cSXin Li ): 121*760c253cSXin Li # Add the machine manager status. 122*760c253cSXin Li thread_status = thread_status_format.format( 123*760c253cSXin Li self.experiment.machine_manager.AsString() 124*760c253cSXin Li ) 125*760c253cSXin Li elif self.experiment.schedv2(): 126*760c253cSXin Li # In schedv2 mode, we always print out thread status. 127*760c253cSXin Li thread_status = thread_status_format.format( 128*760c253cSXin Li self.experiment.schedv2().threads_status_as_string() 129*760c253cSXin Li ) 130*760c253cSXin Li 131*760c253cSXin Li result = "{}{}".format(thread_status, "\n".join(status_strings)) 132*760c253cSXin Li 133*760c253cSXin Li return result 134*760c253cSXin Li 135*760c253cSXin Li def _GetNamesAndIterations(self, benchmark_runs): 136*760c253cSXin Li strings = [] 137*760c253cSXin Li t = time.time() 138*760c253cSXin Li for benchmark_run in benchmark_runs: 139*760c253cSXin Li t_last = benchmark_run.timeline.GetLastEventTime() 140*760c253cSXin Li elapsed = str(datetime.timedelta(seconds=int(t - t_last))) 141*760c253cSXin Li strings.append("'{0}' {1}".format(benchmark_run.name, elapsed)) 142*760c253cSXin Li return " %s (%s)" % (len(strings), ", ".join(strings)) 143*760c253cSXin Li 144*760c253cSXin Li def _GetCompactNamesAndIterations(self, benchmark_runs): 145*760c253cSXin Li grouped_benchmarks = collections.defaultdict(list) 146*760c253cSXin Li for benchmark_run in benchmark_runs: 147*760c253cSXin Li grouped_benchmarks[benchmark_run.label.name].append(benchmark_run) 148*760c253cSXin Li 149*760c253cSXin Li output_segs = [] 150*760c253cSXin Li for label_name, label_runs in grouped_benchmarks.items(): 151*760c253cSXin Li strings = [] 152*760c253cSXin Li benchmark_iterations = collections.defaultdict(list) 153*760c253cSXin Li for benchmark_run in label_runs: 154*760c253cSXin Li assert benchmark_run.label.name == label_name 155*760c253cSXin Li benchmark_name = benchmark_run.benchmark.name 156*760c253cSXin Li benchmark_iterations[benchmark_name].append( 157*760c253cSXin Li benchmark_run.iteration 158*760c253cSXin Li ) 159*760c253cSXin Li for key, val in benchmark_iterations.items(): 160*760c253cSXin Li val.sort() 161*760c253cSXin Li iterations = ",".join(str(v) for v in val) 162*760c253cSXin Li strings.append("{} [{}]".format(key, iterations)) 163*760c253cSXin Li output_segs.append( 164*760c253cSXin Li " " + label_name + ": " + ", ".join(strings) + "\n" 165*760c253cSXin Li ) 166*760c253cSXin Li 167*760c253cSXin Li return " %s \n%s" % (len(benchmark_runs), "".join(output_segs)) 168