// SPDX-License-Identifier: GPL-2.0 /* * Progress feedback for the stdio (non-TUI/GTK) case, enabled with * 'perf report --progress'. */ #include #include #include #include #include #include "../../util/debug.h" #include "../../util/units.h" #include "../progress.h" /* * Phases can be nested, so keep track of the ones started so far to be * able to complete the right one on ui_progress__finish(), which gets * no arguments. */ #define STDIO_PROGRESS__MAX_DEPTH 8 struct stdio_progress_phase { struct ui_progress *p; u64 last_printed; size_t last_len; }; static struct stdio_progress_phase stdio_progress__stack[STDIO_PROGRESS__MAX_DEPTH]; static int stdio_progress__depth; static FILE *stdio_progress__out; static bool stdio_progress__is_tty; /* Phases that didn't fit on the stack are not shown. */ static int stdio_progress__dropped; static void stdio_progress__print_phase(struct stdio_progress_phase *phase, u64 curr) { struct ui_progress *p = phase->p; char buf_cur[20], buf_tot[20], buf[128]; double percent = p->total ? 100.0 * (double)curr / (double)p->total : 0.0; size_t len; /* * Only the completion line shows 100.0%: a 99.99% progress would round * up to it and look like a duplicate at finish time. */ if (curr < p->total && percent > 99.9) percent = 99.9; if (p->size) { unit_number__scnprintf(buf_cur, sizeof(buf_cur), curr); unit_number__scnprintf(buf_tot, sizeof(buf_tot), p->total); len = scnprintf(buf, sizeof(buf), "%s [%5.1f%%] %s / %s", p->title, percent, buf_cur, buf_tot); } else { len = scnprintf(buf, sizeof(buf), "%s [%5.1f%%] %" PRIu64 " / %" PRIu64, p->title, percent, curr, p->total); } if (!stdio_progress__is_tty) { fprintf(stdio_progress__out, "%s\n", buf); goto out; } /* Pad to the length of the previous line to erase its leftovers. */ fprintf(stdio_progress__out, "\r%s%*s", buf, (int)(len < phase->last_len ? phase->last_len - len : 0), ""); phase->last_len = len; out: phase->last_printed = curr; fflush(stdio_progress__out); } static void __stdio_progress__init(struct ui_progress *p) { /* The default step is meant for the TUI bar, use 1% steps for stdio. */ p->next = p->step = p->total / 100 ?: 1; if (stdio_progress__depth == STDIO_PROGRESS__MAX_DEPTH) { /* * Out of room: don't start this phase, its finish() is * swallowed and its updates ignored below. */ pr_warning("progress phases nested deeper than %d, not showing progress for %s\n", STDIO_PROGRESS__MAX_DEPTH, p->title); stdio_progress__dropped++; return; } /* Start a nested phase in a line of its own. */ if (stdio_progress__depth && stdio_progress__is_tty) fputc('\n', stdio_progress__out); stdio_progress__stack[stdio_progress__depth++] = (struct stdio_progress_phase) { .p = p, .last_printed = 0, .last_len = 0, }; stdio_progress__print_phase(&stdio_progress__stack[stdio_progress__depth - 1], p->curr); } static void stdio_progress__update(struct ui_progress *p) { /* * An update that doesn't match the innermost phase means something * is out of sync: print nothing rather than another phase's * numbers, or read past the stack. */ if (!stdio_progress__depth || stdio_progress__stack[stdio_progress__depth - 1].p != p) return; stdio_progress__print_phase(&stdio_progress__stack[stdio_progress__depth - 1], p->curr); } static void stdio_progress__finish(void) { struct stdio_progress_phase *phase; /* * Being the innermost phase, its finish() comes first: swallow it, or * it would complete the phase that encloses it. */ if (stdio_progress__dropped) { stdio_progress__dropped--; return; } if (!stdio_progress__depth) return; phase = &stdio_progress__stack[--stdio_progress__depth]; /* * The last line may have stopped short of the total, close this phase * showing it as complete unless that was already printed. */ if (phase->last_printed != phase->p->total) stdio_progress__print_phase(phase, phase->p->total); phase->last_printed = 0; phase->last_len = 0; if (stdio_progress__is_tty) fputc('\n', stdio_progress__out); fflush(stdio_progress__out); } static struct ui_progress_ops stdio_progress__ops = { .init = __stdio_progress__init, .update = stdio_progress__update, .finish = stdio_progress__finish, }; void stdio_progress__init(void) { if (pager_in_use()) { /* stderr leads to the pager, the updates go to the terminal. */ stdio_progress__out = fopen("/dev/tty", "w"); } if (!stdio_progress__out) stdio_progress__out = stderr; stdio_progress__is_tty = isatty(fileno(stdio_progress__out)) == 1; ui_progress__ops = &stdio_progress__ops; }