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.
This commit is contained in:
Adrian Vovk
2024-08-30 22:50:24 -04:00
parent ca7490c5ad
commit 5f9dd9c64d
3 changed files with 41 additions and 23 deletions
+25 -16
View File
@@ -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);
}
+2
View File
@@ -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);
+14 -7
View File
@@ -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;
}