xref: /llvm-project/llvm/tools/llvm-exegesis/lib/ProgressMeter.h (revision faf675ce34ee1e2c6105e9a816f220412fd2f8d5)
1 //===-- ProgressMeter.h -----------------------------------------*- C++ -*-===//
2 //
3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4 // See https://llvm.org/LICENSE.txt for license information.
5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6 //
7 //===----------------------------------------------------------------------===//
8 
9 #ifndef LLVM_TOOLS_LLVM_EXEGESIS_PROGRESSMETER_H
10 #define LLVM_TOOLS_LLVM_EXEGESIS_PROGRESSMETER_H
11 
12 #include "llvm/Support/Format.h"
13 #include "llvm/Support/raw_ostream.h"
14 #include <cassert>
15 #include <chrono>
16 #include <cmath>
17 #include <optional>
18 #include <type_traits>
19 
20 namespace llvm {
21 namespace exegesis {
22 
23 /// Represents `\sum_{i=1..accumulated}{step_i} / accumulated`,
24 /// where `step_i` is the value passed to the `i`-th call to `step()`,
25 /// and `accumulated` is the total number of calls to `step()`.
26 template <typename NumTy, typename DenTy = int> class SimpleMovingAverage {
27   NumTy Accumulated = NumTy(0);
28   DenTy Steps = 0;
29 
30 public:
31   SimpleMovingAverage() = default;
32 
33   SimpleMovingAverage(const SimpleMovingAverage &) = delete;
34   SimpleMovingAverage(SimpleMovingAverage &&) = delete;
35   SimpleMovingAverage &operator=(const SimpleMovingAverage &) = delete;
36   SimpleMovingAverage &operator=(SimpleMovingAverage &&) = delete;
37 
step(NumTy Quantity)38   inline void step(NumTy Quantity) {
39     Accumulated += Quantity;
40     ++Steps;
41   }
42 
getAccumulated()43   inline NumTy getAccumulated() const { return Accumulated; }
44 
getNumSteps()45   inline DenTy getNumSteps() const { return Steps; }
46 
47   template <typename AvgTy = NumTy>
getAverage()48   inline std::optional<AvgTy> getAverage() const {
49     if (Steps == 0)
50       return std::nullopt;
51     return AvgTy(Accumulated) / Steps;
52   }
53 };
54 
55 template <typename ClockTypeTy = std::chrono::steady_clock,
56           typename = std::enable_if_t<ClockTypeTy::is_steady>>
57 class ProgressMeter {
58 public:
59   using ClockType = ClockTypeTy;
60   using TimePointType = std::chrono::time_point<ClockType>;
61   using DurationType = std::chrono::duration<typename ClockType::rep,
62                                              typename ClockType::period>;
63   using CompetionPercentage = int;
64   using Sec = std::chrono::duration<double, std::chrono::seconds::period>;
65 
66 private:
67   raw_ostream &Out;
68   const int NumStepsTotal;
69   SimpleMovingAverage<DurationType> ElapsedTotal;
70 
71 public:
72   friend class ProgressMeterStep;
73   class ProgressMeterStep {
74     ProgressMeter *P;
75     const TimePointType Begin;
76 
77   public:
ProgressMeterStep(ProgressMeter * P_)78     inline ProgressMeterStep(ProgressMeter *P_)
79         : P(P_), Begin(P ? ProgressMeter<ClockType>::ClockType::now()
80                          : TimePointType()) {}
81 
~ProgressMeterStep()82     inline ~ProgressMeterStep() {
83       if (!P)
84         return;
85       const TimePointType End = ProgressMeter<ClockType>::ClockType::now();
86       P->step(End - Begin);
87     }
88 
89     ProgressMeterStep(const ProgressMeterStep &) = delete;
90     ProgressMeterStep(ProgressMeterStep &&) = delete;
91     ProgressMeterStep &operator=(const ProgressMeterStep &) = delete;
92     ProgressMeterStep &operator=(ProgressMeterStep &&) = delete;
93   };
94 
95   ProgressMeter(int NumStepsTotal_, raw_ostream &out_ = errs())
Out(out_)96       : Out(out_), NumStepsTotal(NumStepsTotal_) {
97     assert(NumStepsTotal > 0 && "No steps are planned?");
98   }
99 
100   ProgressMeter(const ProgressMeter &) = delete;
101   ProgressMeter(ProgressMeter &&) = delete;
102   ProgressMeter &operator=(const ProgressMeter &) = delete;
103   ProgressMeter &operator=(ProgressMeter &&) = delete;
104 
105 private:
step(DurationType Elapsed)106   void step(DurationType Elapsed) {
107     assert((ElapsedTotal.getNumSteps() < NumStepsTotal) && "Step overflow!");
108     assert(Elapsed.count() >= 0 && "Negative time drift detected.");
109 
110     auto [OldProgress, OldEta] = eta();
111     ElapsedTotal.step(Elapsed);
112     auto [NewProgress, NewEta] = eta();
113 
114     if (NewProgress < OldProgress + 1)
115       return;
116 
117     Out << format("Processing... %*d%%", 3, NewProgress);
118     if (NewEta) {
119       int SecondsTotal = std::ceil(NewEta->count());
120       int Seconds = SecondsTotal % 60;
121       int MinutesTotal = SecondsTotal / 60;
122 
123       Out << format(", ETA %02d:%02d", MinutesTotal, Seconds);
124     }
125     Out << "\n";
126     Out.flush();
127   }
128 
eta()129   inline std::pair<CompetionPercentage, std::optional<Sec>> eta() const {
130     CompetionPercentage Progress =
131         (100 * ElapsedTotal.getNumSteps()) / NumStepsTotal;
132 
133     std::optional<Sec> ETA;
134     if (std::optional<Sec> AverageStepDuration =
135             ElapsedTotal.template getAverage<Sec>())
136       ETA = (NumStepsTotal - ElapsedTotal.getNumSteps()) * *AverageStepDuration;
137 
138     return {Progress, ETA};
139   }
140 };
141 
142 } // namespace exegesis
143 } // namespace llvm
144 
145 #endif
146