//===-- ProgressMeter.h -----------------------------------------*- C++ -*-===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // //===----------------------------------------------------------------------===// #ifndef LLVM_TOOLS_LLVM_EXEGESIS_PROGRESSMETER_H #define LLVM_TOOLS_LLVM_EXEGESIS_PROGRESSMETER_H #include "llvm/Support/Format.h" #include "llvm/Support/raw_ostream.h" #include <cassert> #include <chrono> #include <cmath> #include <optional> #include <type_traits> namespace llvm { namespace exegesis { /// Represents `\sum_{i=1..accumulated}{step_i} / accumulated`, /// where `step_i` is the value passed to the `i`-th call to `step()`, /// and `accumulated` is the total number of calls to `step()`. template <typename NumTy, typename DenTy = int> class SimpleMovingAverage { NumTy Accumulated = NumTy(0); DenTy Steps = 0; public: SimpleMovingAverage() = default; SimpleMovingAverage(const SimpleMovingAverage &) = delete; SimpleMovingAverage(SimpleMovingAverage &&) = delete; SimpleMovingAverage &operator=(const SimpleMovingAverage &) = delete; SimpleMovingAverage &operator=(SimpleMovingAverage &&) = delete; inline void step(NumTy Quantity) { Accumulated += Quantity; ++Steps; } inline NumTy getAccumulated() const { return Accumulated; } inline DenTy getNumSteps() const { return Steps; } template <typename AvgTy = NumTy> inline std::optional<AvgTy> getAverage() const { if (Steps == 0) return std::nullopt; return AvgTy(Accumulated) / Steps; } }; template <typename ClockTypeTy = std::chrono::steady_clock, typename = std::enable_if_t<ClockTypeTy::is_steady>> class ProgressMeter { public: using ClockType = ClockTypeTy; using TimePointType = std::chrono::time_point<ClockType>; using DurationType = std::chrono::duration<typename ClockType::rep, typename ClockType::period>; using CompetionPercentage = int; using Sec = std::chrono::duration<double, std::chrono::seconds::period>; private: raw_ostream &Out; const int NumStepsTotal; SimpleMovingAverage<DurationType> ElapsedTotal; public: friend class ProgressMeterStep; class ProgressMeterStep { ProgressMeter *P; const TimePointType Begin; public: inline ProgressMeterStep(ProgressMeter *P_) : P(P_), Begin(P ? ProgressMeter<ClockType>::ClockType::now() : TimePointType()) {} inline ~ProgressMeterStep() { if (!P) return; const TimePointType End = ProgressMeter<ClockType>::ClockType::now(); P->step(End - Begin); } ProgressMeterStep(const ProgressMeterStep &) = delete; ProgressMeterStep(ProgressMeterStep &&) = delete; ProgressMeterStep &operator=(const ProgressMeterStep &) = delete; ProgressMeterStep &operator=(ProgressMeterStep &&) = delete; }; ProgressMeter(int NumStepsTotal_, raw_ostream &out_ = llvm::errs()) : Out(out_), NumStepsTotal(NumStepsTotal_) { assert(NumStepsTotal > 0 && "No steps are planned?"); } ProgressMeter(const ProgressMeter &) = delete; ProgressMeter(ProgressMeter &&) = delete; ProgressMeter &operator=(const ProgressMeter &) = delete; ProgressMeter &operator=(ProgressMeter &&) = delete; private: void step(DurationType Elapsed) { assert((ElapsedTotal.getNumSteps() < NumStepsTotal) && "Step overflow!"); assert(Elapsed.count() >= 0 && "Negative time drift detected."); auto [OldProgress, OldEta] = eta(); ElapsedTotal.step(Elapsed); auto [NewProgress, NewEta] = eta(); if (NewProgress < OldProgress + 1) return; Out << format("Processing... %*d%%", 3, NewProgress); if (NewEta) { int SecondsTotal = std::ceil(NewEta->count()); int Seconds = SecondsTotal % 60; int MinutesTotal = SecondsTotal / 60; Out << format(", ETA %02d:%02d", MinutesTotal, Seconds); } Out << "\n"; Out.flush(); } inline std::pair<CompetionPercentage, std::optional<Sec>> eta() const { CompetionPercentage Progress = (100 * ElapsedTotal.getNumSteps()) / NumStepsTotal; std::optional<Sec> ETA; if (std::optional<Sec> AverageStepDuration = ElapsedTotal.template getAverage<Sec>()) ETA = (NumStepsTotal - ElapsedTotal.getNumSteps()) * *AverageStepDuration; return {Progress, ETA}; } }; } // namespace exegesis } // namespace llvm #endif