xref: /aosp_15_r20/external/toolchain-utils/crosperf/experiment_status.py (revision 760c253c1ed00ce9abd48f8546f08516e57485fe)
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