Progress bar display for parallel workloads.
#ifndef PROGRESS_H_
#define PROGRESS_H_
#include <stdbool.h>
#include <stddef.h>
bool progress_start(size_t total, int threads, const char *message);
void progress_add(size_t amount);
void progress_flush(void);
bool progress_end(void);
#ifdef PROGRESS_EXTERN_DISABLE
extern bool progress_disable;
#endif
#endif
#if defined(PROGRESS_IMPLEMENTATION) && !defined(_PROGRESS_IMPLEMENTED)
#define _PROGRESS_IMPLEMENTED
#include <stdalign.h>
#include <stdatomic.h>
#include <threads.h>
#ifndef PROGRESS_PRINT_H
#define PROGRESS_PRINT_H 0
#include <stdio.h>
#else
#ifndef PRINT_H_
#error "Include print.h before including progress.h"
#endif
#undef PROGRESS_PRINT_H
#define PROGRESS_PRINT_H 1
#endif
#ifndef progress_bar
#if !PROGRESS_PRINT_H
#define progress_bar(pct, ...) \
do { \
printf("\r%3d%% ", pct); \
printf(__VA_ARGS__); \
fflush(stdout); \
} while (0)
#endif
#endif
#ifndef progress_err
#if PROGRESS_PRINT_H
#define progress_err(...) perr(__VA_ARGS__)
#else
#define progress_err(...) fprintf(stderr, __VA_ARGS__)
#endif
#endif
#ifndef progress_dev
#if PROGRESS_PRINT_H
#define progress_dev(...) pdev(__VA_ARGS__)
#else
#define progress_dev(...) fprintf(stderr, __VA_ARGS__)
#endif
#endif
static _Thread_local size_t t_done;
static _Alignas(64) _Atomic(size_t) p_done;
static thrd_t p_monitor_thrd;
static size_t p_total;
static size_t p_update_limit;
static const char *p_message;
static _Atomic(bool) p_running;
static bool progress_disable;
static mtx_t p_mutex;
static cnd_t p_cond;
static int p_monitor(void *arg)
{
(void)arg;
progress_bar(0, "%s", p_message);
struct timespec timeout;
size_t current = 0;
while (atomic_load_explicit(&p_running, memory_order_acquire)) {
current = atomic_load_explicit(&p_done, memory_order_relaxed);
progress_bar((int)(100 * current / p_total), "%s", p_message);
if (timespec_get(&timeout, TIME_UTC) == 0) {
timeout.tv_sec = 0;
timeout.tv_nsec = 0;
}
timeout.tv_nsec += 250000000;
if (timeout.tv_nsec >= 1000000000) {
timeout.tv_sec++;
timeout.tv_nsec -= 1000000000;
}
mtx_lock(&p_mutex);
cnd_timedwait(&p_cond, &p_mutex, &timeout);
mtx_unlock(&p_mutex);
}
progress_bar(100, "%s", p_message);
return thrd_success;
}
bool progress_start(size_t total, int threads, const char *message)
{
if (progress_disable)
return true;
if (atomic_load_explicit(&p_running, memory_order_relaxed))
goto p_monitor_running_error;
atomic_store_explicit(&p_running, true, memory_order_relaxed);
atomic_store_explicit(&p_done, 0, memory_order_relaxed);
p_message = message;
p_total = total;
if (!p_total)
p_total = 1;
p_update_limit = total / ((size_t)threads * 100);
if (!p_update_limit)
p_update_limit = 1;
if (mtx_init(&p_mutex, mtx_plain) != thrd_success)
goto p_monitor_mtx_error;
if (cnd_init(&p_cond) != thrd_success)
goto p_monitor_cnd_error;
if (thrd_create(&p_monitor_thrd, p_monitor, NULL) == thrd_success)
return true;
cnd_destroy(&p_cond);
p_monitor_cnd_error:
mtx_destroy(&p_mutex);
p_monitor_mtx_error:
atomic_store_explicit(&p_running, false, memory_order_relaxed);
p_monitor_running_error:
progress_err("Failed to create a progress bar display");
return false;
}
void progress_flush(void)
{
if (progress_disable || !t_done)
return;
atomic_fetch_add_explicit(&p_done, t_done, memory_order_relaxed);
t_done = 0;
}
void progress_add(size_t amount)
{
if (progress_disable || (t_done += amount) < p_update_limit)
return;
progress_flush();
}
bool progress_end(void)
{
if (progress_disable)
return true;
if (!atomic_load_explicit(&p_running, memory_order_relaxed)) {
progress_dev("Tried to end non-running progress monitor");
progress_err("Internal error during progress bar display");
return false;
}
progress_flush();
atomic_store_explicit(&p_done, p_total, memory_order_relaxed);
atomic_store_explicit(&p_running, false, memory_order_release);
cnd_signal(&p_cond);
thrd_join(p_monitor_thrd, NULL);
cnd_destroy(&p_cond);
mtx_destroy(&p_mutex);
return true;
}
#endif