source: trunk/GDE/SINA/builddir/src/progress.h

Last change on this file was 19170, checked in by westram, 2 years ago
  • sina source
    • unpack + remove tarball
    • no longer ignore sina builddir.
File size: 16.8 KB
Line 
1/*
2Copyright (c) 2006-2018 Elmar Pruesse <elmar.pruesse@ucdenver.edu>
3
4This file is part of SINA.
5SINA is free software: you can redistribute it and/or modify it under
6the terms of the GNU General Public License as published by the Free
7Software Foundation, either version 3 of the License, or (at your
8option) any later version.
9
10SINA is distributed in the hope that it will be useful, but WITHOUT ANY
11WARRANTY; without even the implied warranty of MERCHANTABILITY or
12FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
13for more details.
14
15You should have received a copy of the GNU General Public License
16along with SINA.  If not, see <http://www.gnu.org/licenses/>.
17
18Additional permission under GNU GPL version 3 section 7
19
20If you modify SINA, or any covered work, by linking or combining it
21with components of ARB (or a modified version of that software),
22containing parts covered by the terms of the
23ARB-public-library-license, the licensors of SINA grant you additional
24permission to convey the resulting work. Corresponding Source for a
25non-source form of such a combination shall include the source code
26for 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
42namespace sina {
43
44static 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};
49static 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 */
56class base_progress {
57public:
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;
185private:
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 */
208class Progress final : public base_progress {
209public:
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";
240private:
241    unsigned int _width;
242    FILE* _file;
243};
244
245/******  spdlog integration ****/
246
247class status_line;
248
249class status_line_registry {
250public:
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
267private:
268    std::vector<status_line*> _status_lines;
269    int _change_since_last_print{0};
270};
271
272
273class status_line {
274public:
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
335private:
336    std::shared_ptr<spdlog::logger> _logger;
337    spdlog::level::level_enum _level{spdlog::level::off};
338};
339
340
341class sigwinch_mixin {
342protected:
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    }
363private:
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
402template<typename TargetStream, class ConsoleMutex>
403class terminal_sink final
404    : public spdlog::sinks::ansicolor_sink<TargetStream, ConsoleMutex>,
405      public status_line_registry, public sigwinch_mixin {
406public:
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";
477private:
478    unsigned int _ncols{60};
479    unsigned int _last_status_lines{0};
480};
481
482using terminal_stdout_sink_mt = terminal_sink<spdlog::details::console_stdout, spdlog::details::console_mutex>;
483using terminal_stdout_sink_st = terminal_sink<spdlog::details::console_stdout, spdlog::details::console_nullmutex>;
484using terminal_stderr_sink_mt = terminal_sink<spdlog::details::console_stderr, spdlog::details::console_mutex>;
485using terminal_stderr_sink_st = terminal_sink<spdlog::details::console_stderr, spdlog::details::console_nullmutex>;
486
487template<typename Factory = spdlog::default_factory>
488inline 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}
492template<typename Factory = spdlog::default_factory>
493inline 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
499class logger_progress final : public  base_progress, status_line {
500public:
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
533private:
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 :
Note: See TracBrowser for help on using the repository browser.