// Copyright (C) 2010 Davis E. King (davis@dlib.net) // License: Boost Software License See LICENSE.txt for the full license. #ifndef DLIB_CONSOLE_PROGRESS_INDiCATOR_Hh_ #define DLIB_CONSOLE_PROGRESS_INDiCATOR_Hh_ #include <cmath> #include <chrono> #include <limits> #include <iostream> namespace dlib { // ---------------------------------------------------------------------------------------- class console_progress_indicator { /*! WHAT THIS OBJECT REPRESENTS This object is a tool for reporting how long a task will take to complete. For example, consider the following bit of code: console_progress_indicator pbar(100) for (int i = 1; i <= 100; ++i) { pbar.print_status(i); long_running_operation(); } The above code will print a message to the console each iteration which shows the current progress and how much time is remaining until the loop terminates. !*/ public: inline explicit console_progress_indicator ( double target_value ); /*! ensures - #target() == target_value !*/ inline void reset ( double target_value ); /*! ensures - #target() == target_value - performs the equivalent of: *this = console_progress_indicator(target_value) (i.e. resets this object with a new target value) !*/ inline double target ( ) const; /*! ensures - This object attempts to measure how much time is left until we reach a certain targeted value. This function returns that targeted value. !*/ inline bool print_status ( double cur, bool always_print = false, std::ostream& out = std::clog ); /*! ensures - print_status() assumes it is called with values which are linearly approaching target(). It will display the current progress and attempt to predict how much time is remaining until cur becomes equal to target(). - prints a status message to out which indicates how much more time is left until cur is equal to target() - if (always_print) then - This function prints to the screen each time it is called. - else - This function throttles the printing so that at most 1 message is printed each second. Note that it won't print anything to the screen until about one second has elapsed. This means that the first call to print_status() never prints to the screen. - This function returns true if it prints to the screen and false otherwise. !*/ inline void finish ( std::ostream& out = std::cout ) const; /*! ensures - This object prints the completed progress and the elapsed time to out. It is meant to be called after the loop we are tracking the progress of. !*/ private: double target_val; std::chrono::time_point<std::chrono::steady_clock> start_time; double first_val; double seen_first_val; std::chrono::time_point<std::chrono::steady_clock> last_time; }; // ---------------------------------------------------------------------------------------- // ---------------------------------------------------------------------------------------- // IMPLEMENTATION DETAILS // ---------------------------------------------------------------------------------------- // ---------------------------------------------------------------------------------------- console_progress_indicator:: console_progress_indicator ( double target_value ) : target_val(target_value), start_time(std::chrono::steady_clock::now()), first_val(0), seen_first_val(false), last_time(std::chrono::steady_clock::now()) { } // ---------------------------------------------------------------------------------------- bool console_progress_indicator:: print_status ( double cur, bool always_print, std::ostream& out ) { const auto cur_time = std::chrono::steady_clock::now(); // if this is the first time print_status has been called // then collect some information and exit. We will print status // on the next call. if (!seen_first_val) { start_time = cur_time; last_time = cur_time; first_val = cur; seen_first_val = true; return false; } if ((cur_time - last_time) >= std::chrono::seconds(1) || always_print) { last_time = cur_time; const auto delta_t = cur_time - start_time; double delta_val = std::abs(cur - first_val); // don't do anything if cur is equal to first_val if (delta_val < std::numeric_limits<double>::epsilon()) return false; const auto rem_time = delta_t / delta_val * std::abs(target_val - cur); const auto oldflags = out.flags(); out.setf(std::ios::fixed,std::ios::floatfield); std::streamsize ss; // adapt the precision based on whether the target val is an integer if (std::trunc(target_val) == target_val) ss = out.precision(0); else ss = out.precision(2); out << "Progress: " << cur << "/" << target_val; ss = out.precision(2); out << " (" << cur / target_val * 100. << "%). "; const auto hours = std::chrono::duration_cast<std::chrono::hours>(rem_time); const auto minutes = std::chrono::duration_cast<std::chrono::minutes>(rem_time) - hours; const auto seconds = std::chrono::duration_cast<std::chrono::seconds>(rem_time) - hours - minutes; out << "Time remaining: "; if (rem_time >= std::chrono::hours(1)) out << hours.count() << "h "; if (rem_time >= std::chrono::minutes(1)) out << minutes.count() << "min "; out << seconds.count() << "s. \r" << std::flush; // restore previous output flags and precision settings out.flags(oldflags); out.precision(ss); return true; } return false; } // ---------------------------------------------------------------------------------------- double console_progress_indicator:: target ( ) const { return target_val; } // ---------------------------------------------------------------------------------------- void console_progress_indicator:: reset ( double target_value ) { *this = console_progress_indicator(target_value); } // ---------------------------------------------------------------------------------------- void console_progress_indicator:: finish ( std::ostream& out ) const { const auto oldflags = out.flags(); out.setf(std::ios::fixed,std::ios::floatfield); std::streamsize ss; // adapt the precision based on whether the target val is an integer if (std::trunc(target_val) == target_val) ss = out.precision(0); else ss = out.precision(2); out << "Progress: " << target_val << "/" << target_val; out << " (100.00%). "; const auto delta_t = std::chrono::steady_clock::now() - start_time; const auto hours = std::chrono::duration_cast<std::chrono::hours>(delta_t); const auto minutes = std::chrono::duration_cast<std::chrono::minutes>(delta_t) - hours; const auto seconds = std::chrono::duration_cast<std::chrono::seconds>(delta_t) - hours - minutes; out << "Time elapsed: "; if (delta_t >= std::chrono::hours(1)) out << hours.count() << "h "; if (delta_t >= std::chrono::minutes(1)) out << minutes.count() << "min "; out << seconds.count() << "s. " << std::endl; // restore previous output flags and precision settings out.flags(oldflags); out.precision(ss); } // ---------------------------------------------------------------------------------------- } #endif // DLIB_CONSOLE_PROGRESS_INDiCATOR_Hh_