From d79b1edcc019cab0167d7e17e3de46f01cf5ed9a Mon Sep 17 00:00:00 2001 From: Jin Zhou Date: Tue, 14 Oct 2025 10:30:06 -0400 Subject: [PATCH 1/4] dttools: progress_bar src and test --- dttools/src/.gitignore | 3 +- dttools/src/Makefile | 5 +- dttools/src/progress_bar.c | 271 ++++++++++++++++++++++++++++++++ dttools/src/progress_bar.h | 86 ++++++++++ dttools/src/progress_bar_test.c | 33 ++++ 5 files changed, 396 insertions(+), 2 deletions(-) create mode 100644 dttools/src/progress_bar.c create mode 100644 dttools/src/progress_bar.h create mode 100644 dttools/src/progress_bar_test.c diff --git a/dttools/src/.gitignore b/dttools/src/.gitignore index 019e34fc65..1af0628763 100644 --- a/dttools/src/.gitignore +++ b/dttools/src/.gitignore @@ -41,4 +41,5 @@ bucketing_manager_test hash_table_fromkey_test hash_table_offset_test hash_table_benchmark -priority_queue_test \ No newline at end of file +priority_queue_test +progress_bar_test \ No newline at end of file diff --git a/dttools/src/Makefile b/dttools/src/Makefile index 829e42ef5c..c5103861b0 100644 --- a/dttools/src/Makefile +++ b/dttools/src/Makefile @@ -94,6 +94,8 @@ SOURCES = \ priority_queue.c \ priority_queue_test.c \ process.c \ + progress_bar.c \ + progress_bar_test.c \ random.c \ rmonitor.c \ rmonitor_poll.c \ @@ -164,6 +166,7 @@ HEADERS_PUBLIC = \ macros.h \ path.h \ priority_queue.h \ + progress_bar.h \ rmonitor_poll.h \ rmsummary.h \ stringtools.h \ @@ -193,7 +196,7 @@ PROGRAMS = $(MOST_PROGRAMS) catalog_query SCRIPTS = cctools_gpu_autodetect TARGETS = $(LIBRARIES) $(PRELOAD_LIBRARIES) $(PROGRAMS) $(TEST_PROGRAMS) -TEST_PROGRAMS = auth_test disk_alloc_test jx_test microbench multirun jx_count_obj_test jx_canonicalize_test jx_merge_test hash_table_offset_test hash_table_fromkey_test hash_table_benchmark histogram_test category_test jx_binary_test bucketing_base_test bucketing_manager_test priority_queue_test +TEST_PROGRAMS = auth_test disk_alloc_test jx_test microbench multirun jx_count_obj_test jx_canonicalize_test jx_merge_test hash_table_offset_test hash_table_fromkey_test hash_table_benchmark histogram_test category_test jx_binary_test bucketing_base_test bucketing_manager_test priority_queue_test progress_bar_test all: $(TARGETS) catalog_query diff --git a/dttools/src/progress_bar.c b/dttools/src/progress_bar.c new file mode 100644 index 0000000000..14e1995b42 --- /dev/null +++ b/dttools/src/progress_bar.c @@ -0,0 +1,271 @@ +/* +Copyright (C) 2025 The University of Notre Dame +This software is distributed under the GNU General Public License. +See the file COPYING for details. +*/ + +/** @file progress_bar.c +Implementation of a terminal progress bar with multiple parts. +*/ + +#include "progress_bar.h" +#include "xxmalloc.h" +#include "macros.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Max bar width (in block characters) for single-line rendering. */ +#define MAX_BAR_WIDTH 30 +/* Minimum redraw interval to avoid flicker. */ +#define PROGRESS_BAR_UPDATE_INTERVAL ((USECOND) * 0.1) + +#define COLOR_RESET "\033[0m" +#define COLOR_GREEN "\033[32m" +#define COLOR_CYAN "\033[38;2;0;255;255m" +#define COLOR_ORANGE "\033[38;2;255;165;0m" +#define COLOR_PURPLE "\033[38;2;128;0;128m" +#define COLOR_PINK "\033[38;2;255;192;203m" +#define COLOR_YELLOW "\033[38;2;255;255;0m" + +/** Get terminal width in columns; return 80 on failure. */ +static int get_terminal_width() +{ + struct winsize w; + + if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &w) == -1) { + return 80; + } + + return w.ws_col; +} + +/** Compute bar width based on terminal and labels; clamp to bounds. */ +static int compute_bar_width(const char *label, int part_text_len) +{ + if (!label) { + return 0; + } + + int term_width = get_terminal_width(); + int label_len = strlen(label); + int bar_width = term_width - label_len - part_text_len - 28; + + if (bar_width > MAX_BAR_WIDTH) { + bar_width = MAX_BAR_WIDTH; + } + + if (bar_width < 10) { + bar_width = 10; + } + + return (int)(bar_width * 0.8); +} + +/** Render one-line progress bar with aggregated totals, progress, and elapsed time. */ +static void print_progress_bar(struct ProgressBar *bar) +{ + if (!bar) { + return; + } + + bar->last_draw_time = timestamp_get(); + + char part_text[256]; + char *ptr = part_text; + int remain = sizeof(part_text); + int written = snprintf(ptr, remain, "["); + ptr += written; + remain -= written; + + uint64_t total_sum = 0; + uint64_t current_sum = 0; + + bool first = true; + struct ProgressBarPart *p; + LIST_ITERATE(bar->parts, p) + { + total_sum += p->total; + current_sum += p->current; + + if (!first) { + written = snprintf(ptr, remain, ", "); + ptr += written; + remain -= written; + } + + written = snprintf(ptr, remain, "%s: %" PRIu64 "/%" PRIu64, p->label, p->current, p->total); + ptr += written; + remain -= written; + + first = false; + } + snprintf(ptr, remain, "]"); + part_text[sizeof(part_text) - 1] = '\0'; + + float progress = (total_sum > 0) ? ((float)current_sum / total_sum) : 0.0f; + if (progress > 1.0f) { + progress = 1.0f; + } + + timestamp_t elapsed = timestamp_get() - bar->start_time; + int h = elapsed / (3600LL * USECOND); + int m = (elapsed % (3600LL * USECOND)) / (60LL * USECOND); + int s = (elapsed % (60LL * USECOND)) / USECOND; + + if (bar->has_drawn_once) { + printf("\r\033[2K"); + } else { + bar->has_drawn_once = 1; + } + + int part_text_len = (int)(ptr - part_text) + 1; + int bar_width = compute_bar_width(bar->label, part_text_len); + int filled = (int)(progress * bar_width); + + char bar_line[MAX_BAR_WIDTH * 3 + 1]; + int offset = 0; + const char *block = "━"; + + for (int i = 0; i < filled; ++i) { + memcpy(bar_line + offset, block, 3); + offset += 3; + } + + memset(bar_line + offset, ' ', (bar_width - filled)); + offset += (bar_width - filled); + bar_line[offset] = '\0'; + + printf("%s " COLOR_GREEN "%s %" PRIu64 "/%" PRIu64 COLOR_YELLOW " %s" COLOR_CYAN " %.1f%%" COLOR_ORANGE " %02d:%02d:%02d" COLOR_RESET, + bar->label ? bar->label : "", + bar_line, + current_sum, + total_sum, + part_text, + progress * 100, + h, + m, + s); + + fflush(stdout); +} + +/** Create and initialize a progress bar. */ +struct ProgressBar *progress_bar_init(const char *label) +{ + if (!label) { + return NULL; + } + + struct ProgressBar *bar = xxmalloc(sizeof(struct ProgressBar)); + + bar->label = xxstrdup(label); + bar->parts = list_create(); + bar->start_time = timestamp_get(); + bar->last_draw_time = timestamp_get(); + bar->has_drawn_once = 0; + + return bar; +} + +/** Create a new part. */ +struct ProgressBarPart *progress_bar_create_part(const char *label, uint64_t total) +{ + if (!label) { + return NULL; + } + + struct ProgressBarPart *part = xxmalloc(sizeof(struct ProgressBarPart)); + + part->label = xxstrdup(label); + part->total = total; + part->current = 0; + + return part; +} + +/** Bind a part to the progress bar. */ +void progress_bar_bind_part(struct ProgressBar *bar, struct ProgressBarPart *part) +{ + if (!bar || !part) { + return; + } + + list_push_tail(bar->parts, part); + print_progress_bar(bar); +} + +/** Set the total for a part. */ +void progress_bar_set_part_total(struct ProgressBar *bar, struct ProgressBarPart *part, uint64_t new_total) +{ + if (!bar || !part) { + return; + } + part->total = new_total; + + print_progress_bar(bar); +} + +/** Advance a part's current value, redraw if needed. */ +void progress_bar_update_part(struct ProgressBar *bar, struct ProgressBarPart *part, uint64_t increment) +{ + if (!bar || !part) { + return; + } + + part->current += increment; + if (part->current > part->total) { + part->current = part->total; + } + + if (timestamp_get() - bar->last_draw_time < PROGRESS_BAR_UPDATE_INTERVAL) { + return; + } + + print_progress_bar(bar); +} + +/** Set the start time for the progress bar. */ +void progress_bar_set_start_time(struct ProgressBar *bar, timestamp_t start_time) +{ + if (!bar) { + return; + } + + bar->start_time = start_time; +} + +/** Final render and newline. */ +void progress_bar_finish(struct ProgressBar *bar) +{ + if (!bar) { + return; + } + + print_progress_bar(bar); + printf("\n"); +} + +/** Free the progress bar, its parts, and internal resources. */ +void progress_bar_delete(struct ProgressBar *bar) +{ + if (!bar) { + return; + } + + free(bar->label); + struct ProgressBarPart *p; + LIST_ITERATE(bar->parts, p) + { + free(p->label); + free(p); + } + list_delete(bar->parts); + free(bar); +} diff --git a/dttools/src/progress_bar.h b/dttools/src/progress_bar.h new file mode 100644 index 0000000000..6a38a95424 --- /dev/null +++ b/dttools/src/progress_bar.h @@ -0,0 +1,86 @@ +/* +Copyright (C) 2025 The University of Notre Dame +This software is distributed under the GNU General Public License. +See the file COPYING for details. +*/ + +/** @file progress_bar.h +Terminal progress bar API with multiple parts. +*/ + +#ifndef PROGRESS_BAR_H +#define PROGRESS_BAR_H + +#include "list.h" +#include "timestamp.h" +#include +#include + +/** A part of a progress bar. */ +struct ProgressBarPart { + char *label; + uint64_t total; + uint64_t current; +}; + +/** Progress bar object. */ +struct ProgressBar { + char *label; + struct list *parts; + timestamp_t start_time; + timestamp_t last_draw_time; + int has_drawn_once; +}; + +/* Progress Bar Part API */ + +/** Create a progress bar. +@param label Progress bar label (internally duplicated). +@return New progress bar. +*/ +struct ProgressBar *progress_bar_init(const char *label); + +/** Create a new part. +@param label Part label (internally duplicated). +@param total Total units for the part. +@return New part. +*/ +struct ProgressBarPart *progress_bar_create_part(const char *label, uint64_t total); + +/** Bind a part to the progress bar. +@param bar Progress bar. +@param part Part to bind. +*/ +void progress_bar_bind_part(struct ProgressBar *bar, struct ProgressBarPart *part); + +/** Set the total for a part. +@param bar Progress bar. +@param part Part to update. +@param new_total New total units. +*/ +void progress_bar_set_part_total(struct ProgressBar *bar, struct ProgressBarPart *part, uint64_t new_total); + +/** Update the current value for a part, redraw if needed. +@param bar Progress bar. +@param part Part to advance. +@param increment Amount to add. +*/ +void progress_bar_update_part(struct ProgressBar *bar, struct ProgressBarPart *part, uint64_t increment); + +/** Set the start time for the progress bar. +@param bar Progress bar. +@param start_time Start timestamp. +*/ +void progress_bar_set_start_time(struct ProgressBar *bar, timestamp_t start_time); + +/** Finish the progress bar: draw once and print a newline. +@param bar Progress bar. +*/ +void progress_bar_finish(struct ProgressBar *bar); + +/** Delete the progress bar and free all parts. +@param bar Progress bar. +*/ +void progress_bar_delete(struct ProgressBar *bar); + +#endif diff --git a/dttools/src/progress_bar_test.c b/dttools/src/progress_bar_test.c new file mode 100644 index 0000000000..6eaab8510d --- /dev/null +++ b/dttools/src/progress_bar_test.c @@ -0,0 +1,33 @@ +#include "progress_bar.h" +#include "list.h" +#include "timestamp.h" +#include +#include + +int main() +{ + uint64_t total = 100000; + struct ProgressBarPart *part1 = progress_bar_create_part("step", total); + struct ProgressBarPart *part2 = progress_bar_create_part("fetch", total); + struct ProgressBarPart *part3 = progress_bar_create_part("commit", total); + + struct ProgressBar *bar = progress_bar_init("Compute"); + progress_bar_bind_part(bar, part1); + progress_bar_bind_part(bar, part2); + progress_bar_bind_part(bar, part3); + + timestamp_t start_time = timestamp_get(); + for (uint64_t i = 0; i < total; i++) { + progress_bar_update_part(bar, part1, 1); + progress_bar_update_part(bar, part2, 1); + progress_bar_update_part(bar, part3, 1); + } + + progress_bar_finish(bar); + progress_bar_delete(bar); + + timestamp_t end_time = timestamp_get(); + printf("time taken: %ld\n", end_time - start_time); + + return 0; +} From 354a6d4fe7f699894720c3ef480343598dcff571 Mon Sep 17 00:00:00 2001 From: Jin Zhou Date: Tue, 14 Oct 2025 10:32:13 -0400 Subject: [PATCH 2/4] add unit --- dttools/src/progress_bar_test.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dttools/src/progress_bar_test.c b/dttools/src/progress_bar_test.c index 6eaab8510d..29177fafb1 100644 --- a/dttools/src/progress_bar_test.c +++ b/dttools/src/progress_bar_test.c @@ -27,7 +27,7 @@ int main() progress_bar_delete(bar); timestamp_t end_time = timestamp_get(); - printf("time taken: %ld\n", end_time - start_time); + printf("time taken: %ld us\n", end_time - start_time); return 0; } From 81b228f04c1fb83a892f80c478a3e24548448003 Mon Sep 17 00:00:00 2001 From: Jin Zhou Date: Tue, 14 Oct 2025 10:38:21 -0400 Subject: [PATCH 3/4] format issue --- dttools/src/progress_bar_test.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dttools/src/progress_bar_test.c b/dttools/src/progress_bar_test.c index 29177fafb1..4c72d09425 100644 --- a/dttools/src/progress_bar_test.c +++ b/dttools/src/progress_bar_test.c @@ -27,7 +27,7 @@ int main() progress_bar_delete(bar); timestamp_t end_time = timestamp_get(); - printf("time taken: %ld us\n", end_time - start_time); + printf("time taken: %" PRIu64 "\n", end_time - start_time); return 0; } From 8280ef61982e3d4dc0d595201441deb66f91a01e Mon Sep 17 00:00:00 2001 From: Jin Zhou Date: Wed, 5 Nov 2025 11:55:32 -0500 Subject: [PATCH 4/4] fix flicker --- dttools/src/progress_bar.c | 48 ++++++++++++++++++++++++--------- dttools/src/progress_bar.h | 20 ++++++++++---- dttools/src/progress_bar_test.c | 4 ++- 3 files changed, 53 insertions(+), 19 deletions(-) diff --git a/dttools/src/progress_bar.c b/dttools/src/progress_bar.c index 14e1995b42..614d4f7439 100644 --- a/dttools/src/progress_bar.c +++ b/dttools/src/progress_bar.c @@ -11,6 +11,8 @@ Implementation of a terminal progress bar with multiple parts. #include "progress_bar.h" #include "xxmalloc.h" #include "macros.h" +#include "macros.h" + #include #include #include @@ -23,8 +25,9 @@ Implementation of a terminal progress bar with multiple parts. /* Max bar width (in block characters) for single-line rendering. */ #define MAX_BAR_WIDTH 30 -/* Minimum redraw interval to avoid flicker. */ -#define PROGRESS_BAR_UPDATE_INTERVAL ((USECOND) * 0.1) + +/* Minimum redraw interval to avoid flicker (200ms). */ +#define PROGRESS_BAR_UPDATE_INTERVAL_US (USECOND / 5) #define COLOR_RESET "\033[0m" #define COLOR_GREEN "\033[32m" @@ -75,7 +78,7 @@ static void print_progress_bar(struct ProgressBar *bar) return; } - bar->last_draw_time = timestamp_get(); + bar->last_draw_time_us = timestamp_get(); char part_text[256]; char *ptr = part_text; @@ -114,7 +117,7 @@ static void print_progress_bar(struct ProgressBar *bar) progress = 1.0f; } - timestamp_t elapsed = timestamp_get() - bar->start_time; + timestamp_t elapsed = timestamp_get() - bar->start_time_us; int h = elapsed / (3600LL * USECOND); int m = (elapsed % (3600LL * USECOND)) / (60LL * USECOND); int s = (elapsed % (60LL * USECOND)) / USECOND; @@ -167,13 +170,34 @@ struct ProgressBar *progress_bar_init(const char *label) bar->label = xxstrdup(label); bar->parts = list_create(); - bar->start_time = timestamp_get(); - bar->last_draw_time = timestamp_get(); + bar->start_time_us = timestamp_get(); + bar->last_draw_time_us = 0; + bar->update_interval_us = PROGRESS_BAR_UPDATE_INTERVAL_US; + bar->update_interval_sec = (double)bar->update_interval_us / USECOND; bar->has_drawn_once = 0; return bar; } +/** Set the update interval for the progress bar. */ +void progress_bar_set_update_interval(struct ProgressBar *bar, double update_interval_sec) +{ + if (!bar) { + return; + } + + if (update_interval_sec < 0) { + update_interval_sec = 0; + } + bar->update_interval_sec = update_interval_sec; + /* Convert seconds to microseconds with saturation to avoid overflow. */ + if (update_interval_sec >= (double)UINT64_MAX / (double)USECOND) { + bar->update_interval_us = (timestamp_t)UINT64_MAX; + } else { + bar->update_interval_us = (timestamp_t)(update_interval_sec * (double)USECOND); + } +} + /** Create a new part. */ struct ProgressBarPart *progress_bar_create_part(const char *label, uint64_t total) { @@ -207,9 +231,8 @@ void progress_bar_set_part_total(struct ProgressBar *bar, struct ProgressBarPart if (!bar || !part) { return; } - part->total = new_total; - print_progress_bar(bar); + part->total = new_total; } /** Advance a part's current value, redraw if needed. */ @@ -224,11 +247,10 @@ void progress_bar_update_part(struct ProgressBar *bar, struct ProgressBarPart *p part->current = part->total; } - if (timestamp_get() - bar->last_draw_time < PROGRESS_BAR_UPDATE_INTERVAL) { - return; + timestamp_t now_us = timestamp_get(); + if (!bar->has_drawn_once || (now_us - bar->last_draw_time_us) >= bar->update_interval_us) { + print_progress_bar(bar); } - - print_progress_bar(bar); } /** Set the start time for the progress bar. */ @@ -238,7 +260,7 @@ void progress_bar_set_start_time(struct ProgressBar *bar, timestamp_t start_time return; } - bar->start_time = start_time; + bar->start_time_us = start_time; } /** Final render and newline. */ diff --git a/dttools/src/progress_bar.h b/dttools/src/progress_bar.h index 6a38a95424..7fe3171a3f 100644 --- a/dttools/src/progress_bar.h +++ b/dttools/src/progress_bar.h @@ -25,11 +25,15 @@ struct ProgressBarPart { /** Progress bar object. */ struct ProgressBar { - char *label; - struct list *parts; - timestamp_t start_time; - timestamp_t last_draw_time; - int has_drawn_once; + /* User-facing interval in seconds; internal comparisons use *_us. */ + double update_interval_sec; + char *label; + struct list *parts; + /* Timestamps in microseconds. */ + timestamp_t start_time_us; + timestamp_t last_draw_time_us; + timestamp_t update_interval_us; + int has_drawn_once; }; /* Progress Bar Part API */ @@ -40,6 +44,12 @@ struct ProgressBar { */ struct ProgressBar *progress_bar_init(const char *label); +/** Set the update interval for the progress bar. +@param bar Progress bar. +@param update_interval_sec Update interval in seconds. +*/ +void progress_bar_set_update_interval(struct ProgressBar *bar, double update_interval_sec); + /** Create a new part. @param label Part label (internally duplicated). @param total Total units for the part. diff --git a/dttools/src/progress_bar_test.c b/dttools/src/progress_bar_test.c index 4c72d09425..0d3d3fa813 100644 --- a/dttools/src/progress_bar_test.c +++ b/dttools/src/progress_bar_test.c @@ -6,12 +6,14 @@ int main() { - uint64_t total = 100000; + uint64_t total = 100000000; struct ProgressBarPart *part1 = progress_bar_create_part("step", total); struct ProgressBarPart *part2 = progress_bar_create_part("fetch", total); struct ProgressBarPart *part3 = progress_bar_create_part("commit", total); struct ProgressBar *bar = progress_bar_init("Compute"); + progress_bar_set_update_interval(bar, 1); + progress_bar_bind_part(bar, part1); progress_bar_bind_part(bar, part2); progress_bar_bind_part(bar, part3);