16#include <spdlog/common.h>
17#include <spdlog/details/console_globals.h>
18#include <spdlog/details/null_mutex.h>
19#include <spdlog/details/os.h>
20#include <spdlog/fmt/chrono.h>
21#include <spdlog/pattern_formatter.h>
22#include <spdlog/sinks/ansicolor_sink.h>
23#include <spdlog/sinks/basic_file_sink.h>
24#include <spdlog/sinks/sink.h>
25#include <spdlog/sinks/stdout_sinks.h>
26#include <spdlog/spdlog.h>
31#if defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || defined(__NT__)
32#error Progress sink only work on Linux platform
44#define SPDMON_DECLARE_NON_COPYABLE(classInst) \
45 classInst(const classInst &other) = delete; \
46 classInst &operator=(const classInst &other) = delete;
52#define SPDMON_DECLARE_NON_MOVEABLE(classInst) \
53 classInst(classInst &&other) noexcept = delete; \
54 classInst &operator=(classInst &&other) noexcept = delete;
60#define SPDMON_DECLARE_DEFAULT_COPYABLE(classInst) \
61 classInst(const classInst &other) = default; \
62 classInst &operator=(const classInst &other) = default;
68#define SPDMON_DECLARE_DEFAULT_MOVEABLE(classInst) \
69 classInst(classInst &&other) noexcept = default; \
70 classInst &operator=(classInst &&other) noexcept = default;
89 "5",
"6",
"7",
"8",
"9",
"#"};
97 using clock_t = std::chrono::steady_clock;
103 : total_(total), desc_(desc),
110 void Restart(std::string desc =
"",
unsigned int total = 0) {
128 unsigned int Size() {
return total_; }
130 void FormatBarTo(fmt::memory_buffer &buf,
unsigned int width,
float frac) {
135 buf.reserve(buf.size() + width * 3);
136 auto it = fmt::appender(buf);
138 const auto complete =
139 static_cast<unsigned int>(frac *
static_cast<float>(width * nsyms_));
140 const size_t full_blocks = complete / nsyms_;
141 const size_t frac_block = complete % nsyms_;
142 size_t fill_length = width - full_blocks;
146 auto append_n = [&](
size_t n,
size_t idx) {
147 size_t len_sym = strlen(bar_syms_[idx]);
149 std::fill_n(it, n, bar_syms_[idx][0]);
152 for (
size_t i = 0; i < n; i++)
153 std::copy_n(bar_syms_[idx], len_sym, it);
157 append_n(full_blocks, nsyms_ - 1);
158 if (frac_block > 0) {
159 append_n(1, frac_block);
162 append_n(fill_length, 0);
168 std::lock_guard<std::mutex> lock(mutex_);
172 if (n_ >= last_print_n_ + miniterations_) {
175 if (delta_time > mininterval_) {
176 std::lock_guard<std::mutex> lock(mutex_);
178 miniterations_ = (n_.load() - last_print_n_) *
179 static_cast<unsigned int>(mininterval_ / delta_time);
186 fmt::memory_buffer &buf) {
191 const auto elapsed = now - started_at_;
194 fmt::format_to(fmt::appender(buf), kNoTotalFmt, fmt::arg(
"desc", desc_),
195 fmt::arg(
"n", n_.load()), fmt::arg(
"elapsed", elapsed),
196 fmt::arg(
"eol", kTermEol));
201 static_cast<float>(n_.load()) /
static_cast<float>(total_);
203 const auto remaining =
204 (frac > 0) ? elapsed * (1 / frac - 1) :
duration_t(0);
207 static_cast<float>(n_.load()) /
208 std::chrono::duration_cast<std::chrono::seconds>(elapsed).count();
210 fmt::memory_buffer right;
212 const float percent = frac * 100;
213 fmt::format_to(fmt::appender(buf), kLbarFmt, fmt::arg(
"desc", desc_),
214 fmt::arg(
"frac", percent));
215 fmt::format_to(fmt::appender(right), kRbarFmt, fmt::arg(
"n", n_.load()),
216 fmt::arg(
"total", total_), fmt::arg(
"elapsed", elapsed),
217 fmt::arg(
"remaining", remaining), fmt::arg(
"eol", kTermEol),
218 fmt::arg(
"speed", speed));
220 const auto space_for_bar =
221 static_cast<int>(width - buf.size() - right.size() + kTermEol.size());
222 if (space_for_bar > 0) {
225 buf.reserve(buf.size() + right.size());
226 std::copy(right.begin(), right.end(), std::back_inserter(buf));
232 std::atomic<unsigned int> n_{0};
233 unsigned int last_print_n_{0};
234 unsigned int total_{0};
235 std::string desc_{
""};
236 const char **bar_syms_{
nullptr};
237 unsigned int nsyms_{0};
240 duration_t mininterval_{std::chrono::milliseconds(10)};
241 unsigned int miniterations_{1};
242 std::string bar_tpl_{
""};
245 static constexpr std::string_view kTermEraseLine =
"\x1B[0K";
246 static constexpr std::string_view kTermEol =
"\n";
247 static constexpr std::string_view kLbarFmt =
"{desc}: {frac:3.0f}%|";
250 static constexpr std::string_view kRbarFmt =
251 "| {n}/{total} [{elapsed:.3%T}<{remaining:.3%T}, {speed:.2f}it/s]{eol}";
252 static constexpr std::string_view kNoTotalFmt =
253 "{desc}: {n} [{elapsed:%T}]{eol}";
259 explicit Progress(std::string desc =
"",
unsigned int total = 0,
260 bool ascii =
false, FILE *file = stderr,
261 unsigned int width = 0)
262 :
BaseProgress(desc, total, ascii), width_(width), file_(file)
276 int fd = fileno(file_);
278 if (ioctl(fd, TIOCGWINSZ, &size) == 0) {
279 width_ = size.ws_col;
286 fmt::memory_buffer buf;
289 fwrite(buf.data(), 1, buf.size(), file_);
314 std::remove(status_lines_.begin(), status_lines_.end(), msg),
315 status_lines_.end());
322 std::vector<StatusLine *> status_lines_ = {};
323 int change_since_last_print_{0};
328 explicit StatusLine(spdlog::level::level_enum level) : level_(level) {}
331 if (logger_.get() ==
nullptr)
335 for (
auto &sink : logger_->sinks()) {
338 ptr->RemoveStatusLine(
this);
349 for (
auto &sink : logger_->sinks()) {
350 auto ptr = dynamic_cast<StatusLineRegistry *>(sink.get());
352 ptr->AddStatusLine(this);
359 for (
const auto &sink : logger_->sinks()) {
363 ptr->PrintStatusLineRegistry();
370 unsigned int width) = 0;
373 static const char *file =
"PROGRESS MONITOR";
377 bool ShouldLog() {
return logger_->should_log(level_); }
379 spdlog::level::level_enum
GetLevel() {
return level_; }
382 spdlog::source_loc loc;
384 spdlog::details::log_msg msg{loc, logger_->name(), level_,
385 spdlog::string_view_t{buf.data(), buf.size()}};
386 for (
const auto &sink : logger_->sinks()) {
394 std::shared_ptr<spdlog::logger> logger_{
nullptr};
395 spdlog::level::level_enum level_{spdlog::level::off};
408 Instances().push_back(
this);
414 if (GotSigwinch() != 0) {
418 if (needs_update_ > 0) {
426 static sig_atomic_t &GotSigwinch() {
427 static sig_atomic_t flag;
431 static std::list<SigwinchMixin *> &Instances() {
432 static std::list<SigwinchMixin *> list;
436 static void HandleSigwinch(
int) { GotSigwinch() = 1; }
438 static void InstallHandler() {
440 memset(&sa, 0,
sizeof(sa));
441 if (sigaction(SIGWINCH,
nullptr, &sa)) {
444 if (sa.sa_handler == HandleSigwinch) {
447 memset(&sa, 0,
sizeof(sa));
448 sa.sa_handler = HandleSigwinch;
449 sigfillset(&sa.sa_mask);
450 if (sigaction(SIGWINCH, &sa,
nullptr)) {
455 static void NotifyInstances() {
457 for (
auto &instance : Instances()) {
458 instance->needs_update_ =
true;
462 std::atomic_bool needs_update_{
false};
465template <
class ConsoleMutex>
466class TerminalSink final :
public spdlog::sinks::ansicolor_sink<ConsoleMutex>,
470 using log_sink = spdlog::sinks::ansicolor_sink<ConsoleMutex>;
471 using mutex_t =
typename ConsoleMutex::mutex_t;
474 :
log_sink(stdout, spdlog::color_mode::automatic),
476 if (log_sink::should_color()) {
487 void log(const spdlog::details::log_msg &msg) final {
Print(&msg); }
492 void Print(
const spdlog::details::log_msg *msg) {
493 if (not log_sink::should_color()) {
494 if (msg !=
nullptr) {
505 fmt::memory_buffer status_text;
506 unsigned int nlines = 0;
508 if (log_sink::should_log(line->GetLevel())) {
509 line->RenderStatusLine(status_text, ncols_);
515 std::lock_guard<mutex_t> lock(this->mutex_);
516 for (
unsigned int i = 0; i < last_status_lines_; ++i) {
519 last_status_lines_ = nlines;
527 fwrite(status_text.data(), 1, status_text.size(), stdout);
532 int fd = fileno(stdout);
534 if (ioctl(fd, TIOCGWINSZ, &size) == 0) {
536 std::lock_guard<mutex_t> lock(this->mutex_);
537 ncols_ = size.ws_col;
545 unsigned int ncols_{60};
546 unsigned int last_status_lines_{0};
557template <
typename Factory = spdlog::default_factory>
558inline std::shared_ptr<spdlog::logger>
560 return Factory::template create<terminal_stdout_sink_mt>(logger_name);
563template <
typename Factory = spdlog::default_factory>
564inline std::shared_ptr<spdlog::logger>
566 return Factory::template create<terminal_stderr_sink_mt>(logger_name);
569template <
typename Factory = spdlog::default_factory>
570inline std::shared_ptr<spdlog::logger>
572 return Factory::template create<terminal_stdout_sink_st>(logger_name);
574template <
typename Factory = spdlog::default_factory>
575inline std::shared_ptr<spdlog::logger>
577 return Factory::template create<terminal_stderr_sink_st>(logger_name);
580#define IS_ONE_OF_TYPE(sink) \
581 (typeid(*(sink)) == typeid(spdlog::sinks::stdout_sink_st)) || \
582 (typeid(*(sink)) == typeid(spdlog::sinks::stdout_sink_mt)) || \
583 (typeid(*(sink)) == typeid(spdlog::sinks::ansicolor_stdout_sink_st)) || \
584 (typeid(*(sink)) == typeid(spdlog::sinks::ansicolor_stdout_sink_mt))
589 std::string desc =
"",
unsigned int total = 0,
590 bool ascii =
false,
unsigned int = 0,
591 spdlog::level::level_enum level = spdlog::level::warn)
593 auto progress_logger = std::make_shared<spdmon::terminal_stdout_sink_mt>();
595 std::vector<spdlog::sink_ptr> sinks;
596 for (
const auto &sink : logger->sinks()) {
598 sinks.push_back(sink);
601 sinks.push_back(progress_logger);
603 std::make_shared<spdlog::logger>(desc, sinks.begin(), sinks.end());
605 default_logger_ = spdlog::default_logger();
606 spdlog::set_default_logger(custom_logger_);
611 bool ascii =
false,
unsigned int = 0,
612 spdlog::level::level_enum level = spdlog::level::warn)
614 default_logger_ = spdlog::default_logger();
616 custom_logger_->set_pattern(
617 "[%Y-%m-%d %H:%M:%S.%e] [%n] [%=8t] [%^%=7l%$] %v");
618 custom_logger_->set_level(spdlog::get_level());
619 spdlog::set_default_logger(custom_logger_);
624 spdlog::set_default_logger(default_logger_);
625 spdlog::drop(custom_logger_->name());
636 const duration_t delta_time = now - last_update_;
638 if (delta_time > mininterval_) {
641 fmt::memory_buffer buf;
651 std::shared_ptr<spdlog::logger>
GetLogger() {
return this->custom_logger_; }
654 timepoint_t last_update_{std::chrono::seconds(0)};
655 duration_t mininterval_{std::chrono::milliseconds(500)};
656 std::shared_ptr<spdlog::logger> default_logger_{
nullptr};
657 std::shared_ptr<spdlog::logger> custom_logger_{
nullptr};
662 std::shared_ptr<LoggerProgress> progress_logger_;
665 decltype(std::begin(iter_)) begin_;
666 const decltype(std::end(iter_)) end_;
670 : iter_(iter), size_(0), begin_(std::
begin(iter)), end_(std::
end(iter)) {
672 std::make_shared<LoggerProgress>(name, std::distance(begin_, end_));
680 return begin_ != end_;
684 ++(*progress_logger_);
690 -> std::pair<std::shared_ptr<spdlog::logger>, decltype(*begin_)> {
691 return {progress_logger_->GetLogger(), *begin_};
695template <
typename Iterable>
697 return {std::forward<Iterable>(iter)};
clock_t::duration duration_t
void operator+=(unsigned int n)
void SetTotal(unsigned int n)
void FormatBarTo(fmt::memory_buffer &buf, unsigned int width, float frac)
virtual void ShowProgress(timepoint_t now=clock_t::now())=0
std::chrono::steady_clock clock_t
virtual ~BaseProgress()=default
void RenderProgress(timepoint_t now, unsigned int width, fmt::memory_buffer &buf)
void Update(unsigned int n=1)
BaseProgress(std::string desc="", unsigned int total=0, bool ascii=false)
void Restart(std::string desc="", unsigned int total=0)
BaseProgress & operator++()
clock_t::time_point timepoint_t
const IterableProgressMonitor & begin() const
bool operator!=(const IterableProgressMonitor &) const
const IterableProgressMonitor & end() const
auto operator*() const -> std::pair< std::shared_ptr< spdlog::logger >, decltype(*begin_)>
IterableProgressMonitor(Iterable iter, std::string name="Progress logger")
LoggerProgress(std::shared_ptr< spdlog::logger > logger, std::string desc="", unsigned int total=0, bool ascii=false, unsigned int=0, spdlog::level::level_enum level=spdlog::level::warn)
void RenderStatusLine(fmt::memory_buffer &buf, unsigned int width) final
void ShowProgress(timepoint_t now=clock_t::now()) final
std::shared_ptr< spdlog::logger > GetLogger()
LoggerProgress(std::string desc="", unsigned int total=0, bool ascii=false, unsigned int=0, spdlog::level::level_enum level=spdlog::level::warn)
~Progress() override=default
const std::string kTermMoveUp
void ShowProgress(timepoint_t now=clock_t::now()) final
Progress(std::string desc="", unsigned int total=0, bool ascii=false, FILE *file=stderr, unsigned int width=0)
SigwinchMixin(const SigwinchMixin &other)=delete
const std::vector< StatusLine * > & GetStatusLines()
void AddStatusLine(StatusLine *msg)
virtual ~StatusLineRegistry()=default
void RemoveStatusLine(StatusLine *msg)
virtual void PrintStatusLineRegistry()=0
static const char * MagicFilename()
StatusLine(spdlog::level::level_enum level)
void SendLogMsg(fmt::memory_buffer &buf)
virtual void RenderStatusLine(fmt::memory_buffer &buf, unsigned int width)=0
void TriggerPrintStatusLines()
void RegisterSinks(std::shared_ptr< spdlog::logger > logger)
spdlog::level::level_enum GetLevel()
void log(const spdlog::details::log_msg &msg) final
const std::string kTermMoveUp
void Print(const spdlog::details::log_msg *msg)
const std::string term_clear_line
~TerminalSink() final=default
typename ConsoleMutex::mutex_t mutex_t
spdlog::sinks::ansicolor_sink< ConsoleMutex > log_sink
void PrintStatusLineRegistry() final
static const char * kBarSymsUnicode[]
static const char * kBarSymsAscii[]
auto LogProgress(Iterable &&iter) -> IterableProgressMonitor< Iterable >
std::shared_ptr< spdlog::logger > stderr_terminal_st(const std::string &logger_name)
std::shared_ptr< spdlog::logger > stderr_terminal_mt(const std::string &logger_name)
TerminalSink< spdlog::details::console_nullmutex > terminal_stdout_sink_st
std::shared_ptr< spdlog::logger > stdout_terminal_mt(const std::string &logger_name)
TerminalSink< spdlog::details::console_mutex > terminal_stdout_sink_mt
TerminalSink< spdlog::details::console_mutex > terminal_stderr_sink_mt
std::shared_ptr< spdlog::logger > stdout_terminal_st(const std::string &logger_name)
TerminalSink< spdlog::details::console_nullmutex > terminal_stderr_sink_st
#define IS_ONE_OF_TYPE(sink)
#define SPDMON_DECLARE_NON_MOVEABLE(classInst)
#define SPDMON_DECLARE_NON_COPYABLE(classInst)