blob: 57f615c455b0ca7d8b58abcbf93a4926e08d78a1 [file] [edit]
// SPDX-License-Identifier: GPL-2.0
/*
* Progress feedback for the stdio (non-TUI/GTK) case, enabled with
* 'perf report --progress'.
*/
#include <inttypes.h>
#include <stdio.h>
#include <unistd.h>
#include <linux/kernel.h>
#include <subcmd/pager.h>
#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;
}