From 5f9dd9c64d20e7cdf8b509421e28cfebf31b7c32 Mon Sep 17 00:00:00 2001 From: Adrian Vovk Date: Fri, 30 Aug 2024 22:39:17 -0400 Subject: [PATCH] progress-bar: Add unbuffered variant The progress_bar functions do their own buffering: they reconfigure stderr, then print, then flush and disable buffering on their own. In situations where multiple progress bars are being drawn at a time (for example, in updatectl), it's even more efficient to hoist the buffering and flushing to the call site, and avoid drawing each progress bar individually. To that end, new _unbuffered variants of the progress_bar functions. And we use them in updatectl. --- src/shared/pretty-print.c | 41 ++++++++++++++++++++++++--------------- src/shared/pretty-print.h | 2 ++ src/sysupdate/updatectl.c | 21 +++++++++++++------- 3 files changed, 41 insertions(+), 23 deletions(-) diff --git a/src/shared/pretty-print.c b/src/shared/pretty-print.c index b2a1393fce..cc226e72ea 100644 --- a/src/shared/pretty-print.c +++ b/src/shared/pretty-print.c @@ -460,14 +460,7 @@ bool shall_tint_background(void) { return cache != 0; } -void draw_progress_bar(const char *prefix, double percentage) { - - /* We are going output a bunch of small strings that shall appear as a single line to STDERR which is - * unbuffered by default. Let's temporarily turn on full buffering, so that this is passed to the tty - * as a single buffer, to make things more efficient. */ - char buffer[LONG_LINE_MAX]; - setvbuf(stderr, buffer, _IOFBF, sizeof(buffer)); - +void draw_progress_bar_unbuffered(const char *prefix, double percentage) { fputc('\r', stderr); if (prefix) { fputs(prefix, stderr); @@ -520,17 +513,10 @@ void draw_progress_bar(const char *prefix, double percentage) { fputs(ANSI_ERASE_TO_END_OF_LINE, stderr); fputc('\r', stderr); - fflush(stderr); - /* Disable buffering again */ - setvbuf(stderr, NULL, _IONBF, 0); } -void clear_progress_bar(const char *prefix) { - - char buffer[LONG_LINE_MAX]; - setvbuf(stderr, buffer, _IOFBF, sizeof(buffer)); - +void clear_progress_bar_unbuffered(const char *prefix) { fputc('\r', stderr); if (terminal_is_dumb()) @@ -542,8 +528,31 @@ void clear_progress_bar(const char *prefix) { fputs(ANSI_ERASE_TO_END_OF_LINE, stderr); fputc('\r', stderr); +} + +void draw_progress_bar(const char *prefix, double percentage) { + + /* We are going output a bunch of small strings that shall appear as a single line to STDERR which is + * unbuffered by default. Let's temporarily turn on full buffering, so that this is passed to the tty + * as a single buffer, to make things more efficient. */ + char buffer[LONG_LINE_MAX]; + setvbuf(stderr, buffer, _IOFBF, sizeof(buffer)); + + draw_progress_bar_unbuffered(prefix, percentage); + fflush(stderr); /* Disable buffering again */ setvbuf(stderr, NULL, _IONBF, 0); } + +void clear_progress_bar(const char *prefix) { + char buffer[LONG_LINE_MAX]; + setvbuf(stderr, buffer, _IOFBF, sizeof(buffer)); + + clear_progress_bar_unbuffered(prefix); + + fflush(stderr); + + setvbuf(stderr, NULL, _IONBF, 0); +} diff --git a/src/shared/pretty-print.h b/src/shared/pretty-print.h index 2c97c354ef..d3af149a2e 100644 --- a/src/shared/pretty-print.h +++ b/src/shared/pretty-print.h @@ -55,3 +55,5 @@ bool shall_tint_background(void); void draw_progress_bar(const char *prefix, double percentage); void clear_progress_bar(const char *prefix); +void draw_progress_bar_unbuffered(const char *prefix, double percentage); +void clear_progress_bar_unbuffered(const char *prefix); diff --git a/src/sysupdate/updatectl.c b/src/sysupdate/updatectl.c index 400e521c1b..c298b24464 100644 --- a/src/sysupdate/updatectl.c +++ b/src/sysupdate/updatectl.c @@ -13,6 +13,7 @@ #include "bus-map-properties.h" #include "bus-util.h" #include "errno-list.h" +#include "fileio.h" #include "format-table.h" #include "json-util.h" #include "main-func.h" @@ -795,6 +796,11 @@ static int update_render_progress(sd_event_source *source, void *userdata) { if (n == 0) return 0; + /* We're outputting lots of small strings to STDERR, which is unbuffered by default. So let's turn + * on full buffering, so we pass this all to the TTY in one go, to make things more efficient */ + char buffer[LONG_LINE_MAX]; + setvbuf(stderr, buffer, _IOFBF, sizeof(buffer)); + if (!terminal_is_dumb()) { for (size_t i = 0; i <= n; i++) fputs("\n", stderr); /* Possibly scroll the terminal to make room (including total)*/ @@ -809,23 +815,23 @@ static int update_render_progress(sd_event_source *source, void *userdata) { int progress = PTR_TO_INT(p); if (progress == UPDATE_PROGRESS_FAILED) { - clear_progress_bar(target); + clear_progress_bar_unbuffered(target); fprintf(stderr, "%s: %s Unknown failure\n", target, RED_CROSS_MARK()); total += 100; } else if (progress == -EALREADY) { - clear_progress_bar(target); + clear_progress_bar_unbuffered(target); fprintf(stderr, "%s: %s Already up-to-date\n", target, GREEN_CHECK_MARK()); n--; /* Don't consider this target in the total */ } else if (progress < 0) { - clear_progress_bar(target); + clear_progress_bar_unbuffered(target); fprintf(stderr, "%s: %s %s\n", target, RED_CROSS_MARK(), STRERROR(progress)); total += 100; } else if (progress == UPDATE_PROGRESS_DONE) { - clear_progress_bar(target); + clear_progress_bar_unbuffered(target); fprintf(stderr, "%s: %s Done\n", target, GREEN_CHECK_MARK()); total += 100; } else { - draw_progress_bar(target, progress); + draw_progress_bar_unbuffered(target, progress); fputs("\n", stderr); total += progress; } @@ -833,9 +839,9 @@ static int update_render_progress(sd_event_source *source, void *userdata) { if (n > 1) { if (exiting) - clear_progress_bar(target); + clear_progress_bar_unbuffered(target); else { - draw_progress_bar("Total", (double) total / n); + draw_progress_bar_unbuffered("Total", (double) total / n); if (terminal_is_dumb()) fputs("\n", stderr); } @@ -850,6 +856,7 @@ static int update_render_progress(sd_event_source *source, void *userdata) { fputs("------\n", stderr); fflush(stderr); + setvbuf(stderr, NULL, _IONBF, 0); /* Disable buffering again */ return 0; } -- 2.25.1