| 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 | |