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 | |
38 | inline void step(NumTy Quantity) { |
39 | Accumulated += Quantity; |
40 | ++Steps; |
41 | } |
42 | |
43 | inline NumTy getAccumulated() const { return Accumulated; } |
44 | |
45 | inline DenTy getNumSteps() const { return Steps; } |
46 | |
47 | template <typename AvgTy = NumTy> |
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: |
78 | inline ProgressMeterStep(ProgressMeter *P_) |
79 | : P(P_), Begin(P ? ProgressMeter<ClockType>::ClockType::now() |
80 | : TimePointType()) {} |
81 | |
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()) |
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: |
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(Fmt: ", ETA %02d:%02d" , Vals: MinutesTotal, Vals: Seconds); |
124 | } |
125 | Out << "\n" ; |
126 | Out.flush(); |
127 | } |
128 | |
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 | |