diff --git a/llvm/tools/llvm-exegesis/lib/ProgressMeter.h b/llvm/tools/llvm-exegesis/lib/ProgressMeter.h new file mode 100644 --- /dev/null +++ b/llvm/tools/llvm-exegesis/lib/ProgressMeter.h @@ -0,0 +1,145 @@ +//===-- 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 +#include +#include +#include +#include + +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 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 + inline std::optional getAverage() const { + if (Steps == 0) + return std::nullopt; + return AvgTy(Accumulated) / Steps; + }; +}; + +template > +class ProgressMeter { +public: + using ClockType = ClockTypeTy; + using TimePointType = std::chrono::time_point; + using DurationType = std::chrono::duration; + using CompetionPercentage = int; + using Sec = std::chrono::duration; + +private: + raw_ostream &Out; + const int NumStepsTotal; + SimpleMovingAverage ElapsedTotal; + +public: + friend class ProgressMeterStep; + class ProgressMeterStep { + ProgressMeter *P; + const TimePointType Begin; + + public: + inline ProgressMeterStep(ProgressMeter *P_) + : P(P_), Begin(P ? ProgressMeter::ClockType::now() + : TimePointType()) {} + + inline ~ProgressMeterStep() { + if (!P) + return; + const TimePointType End = ProgressMeter::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> eta() const { + CompetionPercentage Progress = + (100 * ElapsedTotal.getNumSteps()) / NumStepsTotal; + + std::optional ETA; + if (std::optional AverageStepDuration = + ElapsedTotal.template getAverage()) + ETA = (NumStepsTotal - ElapsedTotal.getNumSteps()) * *AverageStepDuration; + + return {Progress, ETA}; + } +}; + +} // namespace exegesis +} // namespace llvm + +#endif diff --git a/llvm/tools/llvm-exegesis/llvm-exegesis.cpp b/llvm/tools/llvm-exegesis/llvm-exegesis.cpp --- a/llvm/tools/llvm-exegesis/llvm-exegesis.cpp +++ b/llvm/tools/llvm-exegesis/llvm-exegesis.cpp @@ -18,6 +18,7 @@ #include "lib/Error.h" #include "lib/LlvmState.h" #include "lib/PerfHelper.h" +#include "lib/ProgressMeter.h" #include "lib/SnippetFile.h" #include "lib/SnippetRepetitor.h" #include "lib/Target.h" @@ -114,6 +115,11 @@ "All of the above and take the minimum of measurements")), cl::init(exegesis::InstructionBenchmark::Duplicate)); +static cl::opt BenchmarkMeasurementsPrintProgress( + "measurements-print-progress", + cl::desc("Produce progress indicator when performing measurements"), + cl::cat(BenchmarkOptions), cl::init(false)); + static cl::opt BenchmarkSkipMeasurements( "skip-measurements", cl::desc("do everything except actually performing the measurements"), @@ -395,7 +401,11 @@ if (BenchmarkFile.empty()) BenchmarkFile = "-"; + std::optional> Meter; + if (BenchmarkMeasurementsPrintProgress) + Meter.emplace(Configurations.size()); for (const BenchmarkCode &Conf : Configurations) { + ProgressMeter<>::ProgressMeterStep MeterStep(Meter ? &*Meter : nullptr); InstructionBenchmark Result = ExitOnErr(Runner->runConfiguration( Conf, NumRepetitions, LoopBodySize, Repetitors, DumpObjectToDisk)); ExitOnFileError(BenchmarkFile, Result.writeYaml(State, BenchmarkFile)); diff --git a/llvm/unittests/tools/llvm-exegesis/CMakeLists.txt b/llvm/unittests/tools/llvm-exegesis/CMakeLists.txt --- a/llvm/unittests/tools/llvm-exegesis/CMakeLists.txt +++ b/llvm/unittests/tools/llvm-exegesis/CMakeLists.txt @@ -14,6 +14,7 @@ BenchmarkRunnerTest.cpp ClusteringTest.cpp PerfHelperTest.cpp + ProgressMeterTest.cpp RegisterValueTest.cpp ) diff --git a/llvm/unittests/tools/llvm-exegesis/ProgressMeterTest.cpp b/llvm/unittests/tools/llvm-exegesis/ProgressMeterTest.cpp new file mode 100644 --- /dev/null +++ b/llvm/unittests/tools/llvm-exegesis/ProgressMeterTest.cpp @@ -0,0 +1,61 @@ +//===-- ProgressMeterTest.cpp -----------------------------------*- 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 +// +//===----------------------------------------------------------------------===// + +#include "ProgressMeter.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +namespace llvm { +namespace exegesis { + +namespace { + +struct PreprogrammedClock { + using duration = std::chrono::seconds; + using rep = duration::rep; + using period = duration::period; + using time_point = std::chrono::time_point; + + static constexpr bool is_steady = true; + + static time_point now() noexcept; +}; + +static int CurrentTimePoint = 0; +auto Pt(int i) { + return PreprogrammedClock::time_point(PreprogrammedClock::duration(i)); +} +static const std::array TimePoints = { + Pt(0), Pt(5), Pt(6), Pt(20), Pt(20), + Pt(35), Pt(37), Pt(75), Pt(77), Pt(100)}; + +PreprogrammedClock::time_point PreprogrammedClock::now() noexcept { + time_point p = TimePoints[CurrentTimePoint]; + ++CurrentTimePoint; + return p; +} + +TEST(ProgressMeterTest, Integration) { + CurrentTimePoint = 0; + std::string TempString; + raw_string_ostream SS(TempString); + ProgressMeter m(5, SS); + for (int i = 0; i != 5; ++i) + decltype(m)::ProgressMeterStep s(&m); + SS.flush(); + ASSERT_EQ("Processing... 20%, ETA 00:20\n" + "Processing... 40%, ETA 00:29\n" + "Processing... 60%, ETA 00:23\n" + "Processing... 80%, ETA 00:18\n" + "Processing... 100%, ETA 00:00\n", + TempString); +} + +} // namespace +} // namespace exegesis +} // namespace llvm