| 1 | /* |
|---|
| 2 | Copyright (c) 2006-2018 Elmar Pruesse <elmar.pruesse@ucdenver.edu> |
|---|
| 3 | |
|---|
| 4 | This file is part of SINA. |
|---|
| 5 | SINA is free software: you can redistribute it and/or modify it under |
|---|
| 6 | the terms of the GNU General Public License as published by the Free |
|---|
| 7 | Software Foundation, either version 3 of the License, or (at your |
|---|
| 8 | option) any later version. |
|---|
| 9 | |
|---|
| 10 | SINA is distributed in the hope that it will be useful, but WITHOUT ANY |
|---|
| 11 | WARRANTY; without even the implied warranty of MERCHANTABILITY or |
|---|
| 12 | FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
|---|
| 13 | for more details. |
|---|
| 14 | |
|---|
| 15 | You should have received a copy of the GNU General Public License |
|---|
| 16 | along with SINA. If not, see <http://www.gnu.org/licenses/>. |
|---|
| 17 | |
|---|
| 18 | Additional permission under GNU GPL version 3 section 7 |
|---|
| 19 | |
|---|
| 20 | If you modify SINA, or any covered work, by linking or combining it |
|---|
| 21 | with components of ARB (or a modified version of that software), |
|---|
| 22 | containing parts covered by the terms of the |
|---|
| 23 | ARB-public-library-license, the licensors of SINA grant you additional |
|---|
| 24 | permission to convey the resulting work. Corresponding Source for a |
|---|
| 25 | non-source form of such a combination shall include the source code |
|---|
| 26 | for the parts of ARB used as well as that of the covered work. |
|---|
| 27 | */ |
|---|
| 28 | |
|---|
| 29 | #ifndef _PROGRESS_H_ |
|---|
| 30 | #define _PROGRESS_H_ |
|---|
| 31 | |
|---|
| 32 | #include <mutex> |
|---|
| 33 | #include <list> |
|---|
| 34 | #include <unistd.h> |
|---|
| 35 | #include <sys/ioctl.h> |
|---|
| 36 | #include <signal.h> |
|---|
| 37 | |
|---|
| 38 | #include "spdlog/spdlog.h" |
|---|
| 39 | #include "spdlog/fmt/bundled/chrono.h" |
|---|
| 40 | #include "spdlog/sinks/ansicolor_sink.h" |
|---|
| 41 | |
|---|
| 42 | namespace sina { |
|---|
| 43 | |
|---|
| 44 | static const char* bar_syms_unicode[] = { |
|---|
| 45 | " ", |
|---|
| 46 | "\xE2\x96\x8F", "\xE2\x96\x8E", "\xE2\x96\x8D", "\xE2\x96\x8C", |
|---|
| 47 | "\xE2\x96\x8B", "\xE2\x96\x8A", "\xE2\x96\x89", "\xE2\x96\x88" |
|---|
| 48 | }; |
|---|
| 49 | static const char* bar_syms_ascii[] = { |
|---|
| 50 | " ", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "#" |
|---|
| 51 | }; |
|---|
| 52 | |
|---|
| 53 | |
|---|
| 54 | /* abstract base rendering progress bar |
|---|
| 55 | * needs show_progress() filled in */ |
|---|
| 56 | class base_progress { |
|---|
| 57 | public: |
|---|
| 58 | using clock_t = std::chrono::steady_clock; |
|---|
| 59 | using timepoint_t = clock_t::time_point; |
|---|
| 60 | using duration_t = clock_t::duration; |
|---|
| 61 | |
|---|
| 62 | base_progress(std::string desc="", unsigned int total=0, bool ascii=false) |
|---|
| 63 | : _total(total), |
|---|
| 64 | _desc(desc), |
|---|
| 65 | _bar_syms(ascii?bar_syms_ascii:bar_syms_unicode), |
|---|
| 66 | _nsyms(ascii?std::extent<decltype(bar_syms_ascii)>::value |
|---|
| 67 | :std::extent<decltype(bar_syms_unicode)>::value) |
|---|
| 68 | {} |
|---|
| 69 | |
|---|
| 70 | void restart(std::string desc="", unsigned int total=0) { |
|---|
| 71 | _n = 0; |
|---|
| 72 | _total = total; |
|---|
| 73 | _desc = desc; |
|---|
| 74 | show_progress(); |
|---|
| 75 | } |
|---|
| 76 | |
|---|
| 77 | unsigned int count() { |
|---|
| 78 | return _n; |
|---|
| 79 | } |
|---|
| 80 | |
|---|
| 81 | base_progress& operator++() { |
|---|
| 82 | update(); |
|---|
| 83 | return *this; |
|---|
| 84 | } |
|---|
| 85 | |
|---|
| 86 | void operator+=(unsigned int n) { |
|---|
| 87 | update(n); |
|---|
| 88 | } |
|---|
| 89 | |
|---|
| 90 | void set_total(unsigned int n) { |
|---|
| 91 | _total = n; |
|---|
| 92 | } |
|---|
| 93 | |
|---|
| 94 | unsigned int size() { |
|---|
| 95 | return _total; |
|---|
| 96 | } |
|---|
| 97 | |
|---|
| 98 | void format_bar_to(fmt::memory_buffer& buf, unsigned int width, float frac) { |
|---|
| 99 | if (width == 0) { |
|---|
| 100 | return; |
|---|
| 101 | } |
|---|
| 102 | buf.reserve(buf.size() + width * 3); |
|---|
| 103 | auto it = std::back_inserter(buf); |
|---|
| 104 | |
|---|
| 105 | size_t complete = frac * width * _nsyms; |
|---|
| 106 | size_t full_blocks = complete / _nsyms; |
|---|
| 107 | size_t frac_block = complete % _nsyms; |
|---|
| 108 | size_t fill_length = width - full_blocks; |
|---|
| 109 | |
|---|
| 110 | auto append_n = [&](size_t n, unsigned int idx) { |
|---|
| 111 | size_t len_sym = strlen(_bar_syms[idx]); |
|---|
| 112 | if (len_sym == 1) { |
|---|
| 113 | std::fill_n(it, n, _bar_syms[idx][0]); |
|---|
| 114 | } else { |
|---|
| 115 | for (size_t i=0; i<n; i++) |
|---|
| 116 | std::copy_n(_bar_syms[idx], len_sym, it); |
|---|
| 117 | } |
|---|
| 118 | }; |
|---|
| 119 | |
|---|
| 120 | append_n(full_blocks, _nsyms-1); |
|---|
| 121 | if (frac_block) { |
|---|
| 122 | append_n(1, frac_block); |
|---|
| 123 | --fill_length; |
|---|
| 124 | } |
|---|
| 125 | append_n(fill_length, 0); |
|---|
| 126 | } |
|---|
| 127 | |
|---|
| 128 | void update(unsigned int n=1) { |
|---|
| 129 | _n += n; |
|---|
| 130 | if (_n == _total) { |
|---|
| 131 | std::lock_guard<std::mutex> lock(_mutex); |
|---|
| 132 | show_progress(); |
|---|
| 133 | return; |
|---|
| 134 | } |
|---|
| 135 | if (_n >= _last_print_n + _miniterations) { |
|---|
| 136 | timepoint_t now = clock_t::now(); |
|---|
| 137 | duration_t delta_time = now - _last_update; |
|---|
| 138 | if (delta_time > _mininterval) { |
|---|
| 139 | std::lock_guard<std::mutex> lock(_mutex); |
|---|
| 140 | _last_update = now; |
|---|
| 141 | _miniterations = (_n - _last_print_n) * _mininterval / delta_time; |
|---|
| 142 | show_progress(now); |
|---|
| 143 | } |
|---|
| 144 | } |
|---|
| 145 | } |
|---|
| 146 | |
|---|
| 147 | void render_progress(timepoint_t now, unsigned int width, fmt::memory_buffer& buf) { |
|---|
| 148 | _last_print_n = _n; |
|---|
| 149 | auto arg_desc = fmt::arg("desc", _desc); |
|---|
| 150 | auto elapsed = now - _started_at; |
|---|
| 151 | auto arg_elapsed = fmt::arg("elapsed", elapsed); |
|---|
| 152 | auto arg_eol = fmt::arg("eol", term_eol); |
|---|
| 153 | auto arg_n = fmt::arg("n", _n); |
|---|
| 154 | |
|---|
| 155 | if (_total == 0) { |
|---|
| 156 | fmt::format_to(buf, nototal_fmt, arg_desc, arg_elapsed, arg_eol, arg_n); |
|---|
| 157 | return; |
|---|
| 158 | } |
|---|
| 159 | |
|---|
| 160 | float frac = (float) _n / _total; |
|---|
| 161 | auto eta = elapsed * (1/frac -1); |
|---|
| 162 | auto remaining = (frac > 0) ? elapsed * (1/frac - 1) : duration_t(0); |
|---|
| 163 | |
|---|
| 164 | fmt::memory_buffer right; |
|---|
| 165 | |
|---|
| 166 | float percent = frac * 100; |
|---|
| 167 | auto arg_frac = fmt::arg("frac", percent); |
|---|
| 168 | auto arg_total = fmt::arg("total", _total); |
|---|
| 169 | auto arg_remain = fmt::arg("remaining", remaining); |
|---|
| 170 | auto args = fmt::make_format_args(arg_desc, arg_frac, arg_n, arg_total, |
|---|
| 171 | arg_elapsed, arg_remain, arg_eol); |
|---|
| 172 | |
|---|
| 173 | fmt::vformat_to(buf, lbar_fmt, args); |
|---|
| 174 | fmt::vformat_to(right, rbar_fmt, args); |
|---|
| 175 | |
|---|
| 176 | int space_for_bar = width - buf.size() - right.size() + term_eol.size(); |
|---|
| 177 | if (space_for_bar > 0) { |
|---|
| 178 | format_bar_to(buf, space_for_bar, frac); |
|---|
| 179 | } |
|---|
| 180 | buf.reserve(buf.size() + right.size()); |
|---|
| 181 | std::copy(right.begin(), right.end(), std::back_inserter(buf)); |
|---|
| 182 | } |
|---|
| 183 | |
|---|
| 184 | virtual void show_progress(timepoint_t now=clock_t::now()) = 0; |
|---|
| 185 | private: |
|---|
| 186 | |
|---|
| 187 | std::atomic<unsigned int> _n{0}; |
|---|
| 188 | unsigned int _last_print_n{0}; |
|---|
| 189 | unsigned int _total; |
|---|
| 190 | std::string _desc; |
|---|
| 191 | const char **_bar_syms; |
|---|
| 192 | unsigned int _nsyms; |
|---|
| 193 | timepoint_t _started_at{clock_t::now()}; |
|---|
| 194 | timepoint_t _last_update{std::chrono::seconds(0)}; |
|---|
| 195 | duration_t _mininterval{std::chrono::milliseconds(10)}; |
|---|
| 196 | unsigned int _miniterations{1}; |
|---|
| 197 | std::string _bar_tpl; |
|---|
| 198 | std::mutex _mutex; |
|---|
| 199 | |
|---|
| 200 | const std::string term_erase_line = "\x1B[0K"; |
|---|
| 201 | const std::string term_eol = "\n"; |
|---|
| 202 | const std::string lbar_fmt = "{desc}: {frac:3.0f}% |"; |
|---|
| 203 | const std::string rbar_fmt = "| {n}/{total} [{elapsed:%T} / {remaining:%T}]{eol}"; |
|---|
| 204 | const std::string nototal_fmt = "{desc}: {n} [{elapsed:%T}]{eol}"; |
|---|
| 205 | }; |
|---|
| 206 | |
|---|
| 207 | /* Progress monitor writing directly to a file */ |
|---|
| 208 | class Progress final : public base_progress { |
|---|
| 209 | public: |
|---|
| 210 | Progress(std::string desc="", unsigned int total=0, bool ascii=false, |
|---|
| 211 | FILE* file=stderr, unsigned int width=0) |
|---|
| 212 | : base_progress(desc, total, ascii), |
|---|
| 213 | _width(width), |
|---|
| 214 | _file(file) |
|---|
| 215 | |
|---|
| 216 | { |
|---|
| 217 | if (_width == 0) { |
|---|
| 218 | update_term_width(); |
|---|
| 219 | } |
|---|
| 220 | } |
|---|
| 221 | |
|---|
| 222 | void update_term_width() { |
|---|
| 223 | int fd = fileno(_file); |
|---|
| 224 | struct winsize size; |
|---|
| 225 | if (ioctl(fd, TIOCGWINSZ, &size) == 0) { |
|---|
| 226 | _width = size.ws_col; |
|---|
| 227 | } |
|---|
| 228 | } |
|---|
| 229 | |
|---|
| 230 | void show_progress(timepoint_t now=clock_t::now()) override final { |
|---|
| 231 | // called from base_progress when it decided the progress bar needs |
|---|
| 232 | // to be printed again |
|---|
| 233 | fmt::memory_buffer buf; |
|---|
| 234 | render_progress(now, _width, buf); |
|---|
| 235 | std::copy(term_move_up.begin(), term_move_up.end(), std::back_inserter(buf)); |
|---|
| 236 | fwrite(buf.data(), 1, buf.size(), _file); |
|---|
| 237 | fflush(_file); |
|---|
| 238 | } |
|---|
| 239 | const std::string term_move_up = "\x1B[A"; |
|---|
| 240 | private: |
|---|
| 241 | unsigned int _width; |
|---|
| 242 | FILE* _file; |
|---|
| 243 | }; |
|---|
| 244 | |
|---|
| 245 | /****** spdlog integration ****/ |
|---|
| 246 | |
|---|
| 247 | class status_line; |
|---|
| 248 | |
|---|
| 249 | class status_line_registry { |
|---|
| 250 | public: |
|---|
| 251 | virtual ~status_line_registry() {} |
|---|
| 252 | |
|---|
| 253 | void add_status_line(status_line *msg) { |
|---|
| 254 | _status_lines.push_back(msg); |
|---|
| 255 | } |
|---|
| 256 | void remove_status_line(status_line *msg) { |
|---|
| 257 | _status_lines.erase( |
|---|
| 258 | std::remove(_status_lines.begin(), _status_lines.end(), msg), |
|---|
| 259 | _status_lines.end()); |
|---|
| 260 | } |
|---|
| 261 | const std::vector<status_line*>& get_status_lines() { |
|---|
| 262 | return _status_lines; |
|---|
| 263 | } |
|---|
| 264 | |
|---|
| 265 | virtual void print_status_line_registry() = 0; |
|---|
| 266 | |
|---|
| 267 | private: |
|---|
| 268 | std::vector<status_line*> _status_lines; |
|---|
| 269 | int _change_since_last_print{0}; |
|---|
| 270 | }; |
|---|
| 271 | |
|---|
| 272 | |
|---|
| 273 | class status_line { |
|---|
| 274 | public: |
|---|
| 275 | status_line(std::shared_ptr<spdlog::logger> logger, |
|---|
| 276 | spdlog::level::level_enum level) |
|---|
| 277 | : _logger(logger), _level(level) |
|---|
| 278 | { |
|---|
| 279 | /* on creation, register it with all sinks that understand about us */ |
|---|
| 280 | for (auto &sink : _logger->sinks()) { |
|---|
| 281 | auto ptr = dynamic_cast<status_line_registry*>(sink.get()); |
|---|
| 282 | if (ptr) { |
|---|
| 283 | ptr->add_status_line(this); |
|---|
| 284 | } |
|---|
| 285 | } |
|---|
| 286 | } |
|---|
| 287 | |
|---|
| 288 | ~status_line() { |
|---|
| 289 | /* on deletion, deregister from all sinks */ |
|---|
| 290 | for (auto &sink : _logger->sinks()) { |
|---|
| 291 | auto ptr = dynamic_cast<status_line_registry*>(sink.get()); |
|---|
| 292 | if (ptr) { |
|---|
| 293 | ptr->remove_status_line(this); |
|---|
| 294 | } |
|---|
| 295 | } |
|---|
| 296 | } |
|---|
| 297 | |
|---|
| 298 | /* trigger (re)print of this status line */ |
|---|
| 299 | void trigger_print_status_lines() { |
|---|
| 300 | for (auto &sink : _logger->sinks()) { |
|---|
| 301 | if (sink->should_log(get_level())) { |
|---|
| 302 | auto ptr = dynamic_cast<status_line_registry*>(sink.get()); |
|---|
| 303 | if (ptr) { |
|---|
| 304 | ptr->print_status_line_registry(); |
|---|
| 305 | } |
|---|
| 306 | } |
|---|
| 307 | } |
|---|
| 308 | } |
|---|
| 309 | |
|---|
| 310 | virtual void render_status_line(fmt::memory_buffer &buf, unsigned int width) = 0; |
|---|
| 311 | |
|---|
| 312 | static const char* magic_filename() { |
|---|
| 313 | static const char* file = "PROGRESS MONITOR"; |
|---|
| 314 | return file; |
|---|
| 315 | } |
|---|
| 316 | bool should_log() { |
|---|
| 317 | return _logger->should_log(_level); |
|---|
| 318 | } |
|---|
| 319 | spdlog::level::level_enum get_level() { |
|---|
| 320 | return _level; |
|---|
| 321 | } |
|---|
| 322 | |
|---|
| 323 | void send_log_msg(fmt::memory_buffer &buf) { |
|---|
| 324 | using spdlog::details::fmt_helper::to_string_view; |
|---|
| 325 | spdlog::source_loc loc; |
|---|
| 326 | loc.filename = magic_filename(); |
|---|
| 327 | spdlog::details::log_msg msg{loc, &_logger->name(), _level, to_string_view(buf)}; |
|---|
| 328 | for (auto &sink : _logger->sinks()) { |
|---|
| 329 | if (sink->should_log(get_level())) { |
|---|
| 330 | sink->log(msg); |
|---|
| 331 | } |
|---|
| 332 | } |
|---|
| 333 | } |
|---|
| 334 | |
|---|
| 335 | private: |
|---|
| 336 | std::shared_ptr<spdlog::logger> _logger; |
|---|
| 337 | spdlog::level::level_enum _level{spdlog::level::off}; |
|---|
| 338 | }; |
|---|
| 339 | |
|---|
| 340 | |
|---|
| 341 | class sigwinch_mixin { |
|---|
| 342 | protected: |
|---|
| 343 | sigwinch_mixin(bool install) { |
|---|
| 344 | if (install) { |
|---|
| 345 | install_handler(); |
|---|
| 346 | } |
|---|
| 347 | instances().push_back(this); |
|---|
| 348 | } |
|---|
| 349 | virtual ~sigwinch_mixin() { |
|---|
| 350 | instances().remove(this); |
|---|
| 351 | } |
|---|
| 352 | inline bool check_got_sigwinch() { |
|---|
| 353 | if (got_sigwinch() != 0) { |
|---|
| 354 | got_sigwinch() = 0; |
|---|
| 355 | notify_instances(); |
|---|
| 356 | } |
|---|
| 357 | if (_needs_update) { |
|---|
| 358 | _needs_update = 0; |
|---|
| 359 | return true; |
|---|
| 360 | } |
|---|
| 361 | return false; |
|---|
| 362 | } |
|---|
| 363 | private: |
|---|
| 364 | static sig_atomic_t& got_sigwinch() { |
|---|
| 365 | static sig_atomic_t flag; |
|---|
| 366 | return flag; |
|---|
| 367 | } |
|---|
| 368 | static std::list<sigwinch_mixin*>& instances() { |
|---|
| 369 | static std::list<sigwinch_mixin*> list; |
|---|
| 370 | return list; |
|---|
| 371 | } |
|---|
| 372 | static void handle_sigwinch(int) { |
|---|
| 373 | got_sigwinch() = 1; |
|---|
| 374 | } |
|---|
| 375 | static void install_handler() { |
|---|
| 376 | struct sigaction sa; |
|---|
| 377 | memset(&sa, 0, sizeof(sa)); |
|---|
| 378 | if (sigaction(SIGWINCH, nullptr, &sa)) { |
|---|
| 379 | return; // failed |
|---|
| 380 | } |
|---|
| 381 | if (sa.sa_handler == handle_sigwinch) { |
|---|
| 382 | return; // already installed |
|---|
| 383 | } |
|---|
| 384 | memset(&sa, 0, sizeof(sa)); |
|---|
| 385 | sa.sa_handler = handle_sigwinch; |
|---|
| 386 | sigfillset(&sa.sa_mask); // block other signals during handler |
|---|
| 387 | if (sigaction(SIGWINCH, &sa, nullptr)) { |
|---|
| 388 | return; // failed |
|---|
| 389 | } |
|---|
| 390 | } |
|---|
| 391 | static void notify_instances() { |
|---|
| 392 | //std::lock_guard<mutex_t> lock(super::mutex_); |
|---|
| 393 | for (auto& instance : instances()) { |
|---|
| 394 | instance->_needs_update = true; |
|---|
| 395 | } |
|---|
| 396 | } |
|---|
| 397 | |
|---|
| 398 | bool _needs_update{false}; |
|---|
| 399 | }; |
|---|
| 400 | |
|---|
| 401 | |
|---|
| 402 | template<typename TargetStream, class ConsoleMutex> |
|---|
| 403 | class terminal_sink final |
|---|
| 404 | : public spdlog::sinks::ansicolor_sink<TargetStream, ConsoleMutex>, |
|---|
| 405 | public status_line_registry, public sigwinch_mixin { |
|---|
| 406 | public: |
|---|
| 407 | using super = spdlog::sinks::ansicolor_sink<TargetStream, ConsoleMutex>; |
|---|
| 408 | using mutex_t = typename ConsoleMutex::mutex_t; |
|---|
| 409 | |
|---|
| 410 | terminal_sink() : super(), sigwinch_mixin(super::should_do_colors_) { |
|---|
| 411 | if (super::should_do_colors_) { |
|---|
| 412 | update_term_width(); |
|---|
| 413 | } |
|---|
| 414 | } |
|---|
| 415 | ~terminal_sink() override = default; |
|---|
| 416 | |
|---|
| 417 | // normal log message coming in |
|---|
| 418 | void log(const spdlog::details::log_msg &msg) override final { |
|---|
| 419 | _print(&msg); |
|---|
| 420 | } |
|---|
| 421 | |
|---|
| 422 | // update to status coming in |
|---|
| 423 | void print_status_line_registry() override final { |
|---|
| 424 | _print(nullptr); |
|---|
| 425 | } |
|---|
| 426 | |
|---|
| 427 | void _print(const spdlog::details::log_msg *msg) { |
|---|
| 428 | if (not super::should_do_colors_) { // not a tty |
|---|
| 429 | if (msg != nullptr) { // got a msg |
|---|
| 430 | super::log(*msg); // pass it upstream |
|---|
| 431 | } |
|---|
| 432 | return; |
|---|
| 433 | } |
|---|
| 434 | |
|---|
| 435 | if (check_got_sigwinch()) { |
|---|
| 436 | update_term_width(); |
|---|
| 437 | } |
|---|
| 438 | |
|---|
| 439 | // render status lines |
|---|
| 440 | fmt::memory_buffer status_text; |
|---|
| 441 | int nlines = 0; |
|---|
| 442 | for (auto line : get_status_lines()) { |
|---|
| 443 | if (this->should_log(line->get_level())) { |
|---|
| 444 | line->render_status_line(status_text, _ncols); |
|---|
| 445 | ++ nlines; |
|---|
| 446 | } |
|---|
| 447 | } |
|---|
| 448 | |
|---|
| 449 | // move back up to last log line |
|---|
| 450 | std::lock_guard<mutex_t> lock(super::mutex_); |
|---|
| 451 | for (unsigned int i=0; i < _last_status_lines; ++i) { |
|---|
| 452 | fwrite(term_move_up.data(), 1, term_move_up.size(), super::target_file_); |
|---|
| 453 | } |
|---|
| 454 | _last_status_lines = nlines; |
|---|
| 455 | |
|---|
| 456 | // print message if we have one and it's not an update for other sinks |
|---|
| 457 | if (msg != nullptr && msg->source.filename != status_line::magic_filename()) { |
|---|
| 458 | fwrite(term_clear_line.data(), 1, term_clear_line.size(), super::target_file_); |
|---|
| 459 | super::log(*msg); |
|---|
| 460 | } |
|---|
| 461 | |
|---|
| 462 | fwrite(status_text.data(), 1, status_text.size(), super::target_file_); |
|---|
| 463 | fflush(super::target_file_); |
|---|
| 464 | } |
|---|
| 465 | |
|---|
| 466 | void update_term_width() { |
|---|
| 467 | int fd = fileno(super::target_file_); |
|---|
| 468 | struct winsize size; |
|---|
| 469 | if (ioctl(fd, TIOCGWINSZ, &size) == 0) { |
|---|
| 470 | std::lock_guard<mutex_t> lock(super::mutex_); |
|---|
| 471 | _ncols = size.ws_col; |
|---|
| 472 | } |
|---|
| 473 | } |
|---|
| 474 | |
|---|
| 475 | const std::string term_move_up = "\x1B[A"; |
|---|
| 476 | const std::string term_clear_line = "\x1B[K"; |
|---|
| 477 | private: |
|---|
| 478 | unsigned int _ncols{60}; |
|---|
| 479 | unsigned int _last_status_lines{0}; |
|---|
| 480 | }; |
|---|
| 481 | |
|---|
| 482 | using terminal_stdout_sink_mt = terminal_sink<spdlog::details::console_stdout, spdlog::details::console_mutex>; |
|---|
| 483 | using terminal_stdout_sink_st = terminal_sink<spdlog::details::console_stdout, spdlog::details::console_nullmutex>; |
|---|
| 484 | using terminal_stderr_sink_mt = terminal_sink<spdlog::details::console_stderr, spdlog::details::console_mutex>; |
|---|
| 485 | using terminal_stderr_sink_st = terminal_sink<spdlog::details::console_stderr, spdlog::details::console_nullmutex>; |
|---|
| 486 | |
|---|
| 487 | template<typename Factory = spdlog::default_factory> |
|---|
| 488 | inline std::shared_ptr<spdlog::logger> stdout_terminal_mt(const std::string &logger_name) |
|---|
| 489 | { |
|---|
| 490 | return Factory::template create<terminal_stdout_sink_mt>(logger_name); |
|---|
| 491 | } |
|---|
| 492 | template<typename Factory = spdlog::default_factory> |
|---|
| 493 | inline std::shared_ptr<spdlog::logger> stderr_terminal_mt(const std::string &logger_name) |
|---|
| 494 | { |
|---|
| 495 | return Factory::template create<terminal_stderr_sink_mt>(logger_name); |
|---|
| 496 | } |
|---|
| 497 | |
|---|
| 498 | |
|---|
| 499 | class logger_progress final : public base_progress, status_line { |
|---|
| 500 | public: |
|---|
| 501 | logger_progress(std::shared_ptr<spdlog::logger> logger, |
|---|
| 502 | std::string desc="", unsigned int total=0, bool ascii=false, |
|---|
| 503 | unsigned int /*width*/=0, |
|---|
| 504 | spdlog::level::level_enum level=spdlog::level::warn |
|---|
| 505 | ) |
|---|
| 506 | : base_progress(desc, total, ascii), |
|---|
| 507 | status_line(logger, level) |
|---|
| 508 | { |
|---|
| 509 | } |
|---|
| 510 | ~logger_progress() { |
|---|
| 511 | } |
|---|
| 512 | |
|---|
| 513 | void show_progress(timepoint_t now=clock_t::now()) override final { |
|---|
| 514 | if (!should_log()) { |
|---|
| 515 | // early quit if our logger has loglevel below ourselves |
|---|
| 516 | return; |
|---|
| 517 | } |
|---|
| 518 | duration_t delta_time = now - _last_update; |
|---|
| 519 | status_line::trigger_print_status_lines(); |
|---|
| 520 | if (delta_time > _mininterval) { |
|---|
| 521 | _last_update = now; |
|---|
| 522 | |
|---|
| 523 | fmt::memory_buffer buf; |
|---|
| 524 | base_progress::render_progress(now, 60, buf); |
|---|
| 525 | status_line::send_log_msg(buf); |
|---|
| 526 | } |
|---|
| 527 | } |
|---|
| 528 | |
|---|
| 529 | void render_status_line(fmt::memory_buffer &buf, unsigned int width) override final { |
|---|
| 530 | base_progress::render_progress(clock_t::now(), width, buf); |
|---|
| 531 | } |
|---|
| 532 | |
|---|
| 533 | private: |
|---|
| 534 | timepoint_t _last_update{std::chrono::seconds(0)}; |
|---|
| 535 | duration_t _mininterval{std::chrono::milliseconds(500)}; |
|---|
| 536 | |
|---|
| 537 | }; |
|---|
| 538 | |
|---|
| 539 | |
|---|
| 540 | |
|---|
| 541 | |
|---|
| 542 | } // namespace sina |
|---|
| 543 | |
|---|
| 544 | |
|---|
| 545 | #endif // _LOG_H_ |
|---|
| 546 | /* |
|---|
| 547 | Local Variables: |
|---|
| 548 | mode:c++ |
|---|
| 549 | c-file-style:"stroustrup" |
|---|
| 550 | c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . 0)) |
|---|
| 551 | indent-tabs-mode:nil |
|---|
| 552 | fill-column:99 |
|---|
| 553 | End: |
|---|
| 554 | */ |
|---|
| 555 | // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 : |
|---|