mirror of
https://github.com/torvalds/linux.git
synced 2026-11-07 06:17:02 +01:00
perf report: Add --progress option
Processing a large session with stdio output gives no feedback about which phase perf is in or how far along it is: ui_progress updates are only shown by the TUI. Add --progress, installing a stdio backend (ui/stdio/progress.c) that prints the phase title, percentage and counts: Processing events... [ 42.3%] 317M / 746M Phases can be nested, so the backend tracks the ones started to complete the right one on ui_progress__finish(); that requires init()/finish() pairs, fixed in ordered-events.c and the pipe and directory event processing. With a pager both stdout and stderr lead to it, so isatty(stderr) turns false and the updates end up printed one per line in the pager's output: use /dev/tty in that case, so that the updates keep updating in place, untouched by the pager. Assisted-by: LLM Reviewed-by: Namhyung Kim <namhyung@kernel.org> Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
This commit is contained in:
parent
3f881d589c
commit
44ce6270eb
|
|
@ -29,6 +29,20 @@ OPTIONS
|
|||
--quiet::
|
||||
Do not show any warnings or messages. (Suppress -v)
|
||||
|
||||
--progress::
|
||||
Show progress for each of the processing phases, printing the
|
||||
percentage done and the current/total counts: the first phase
|
||||
counts the bytes of the perf.data file processed so far, the
|
||||
merge and sort phases count hist entries, e.g.:
|
||||
|
||||
Processing events... [ 42.3%] 4G / 10G
|
||||
Merging related events... [ 7.1%] 1024 / 14387
|
||||
Sorting events for output... [ 98.2%] 14132 / 14387
|
||||
|
||||
It is a no-op when using the TUI or GTK browsers, that already
|
||||
present progress information, or when --quiet is used, that asks
|
||||
for no messages at all.
|
||||
|
||||
-n::
|
||||
--show-nr-samples::
|
||||
Show the number of samples for each symbol
|
||||
|
|
|
|||
|
|
@ -87,6 +87,7 @@ struct report {
|
|||
bool use_gtk;
|
||||
#endif
|
||||
bool use_stdio;
|
||||
bool progress;
|
||||
bool show_full_info;
|
||||
bool show_threads;
|
||||
bool inverted_callchain;
|
||||
|
|
@ -1384,6 +1385,8 @@ int cmd_report(int argc, const char **argv)
|
|||
"Use the stdio interface"),
|
||||
OPT_BOOLEAN(0, "weights", &symbol_conf.annotate_weight,
|
||||
"Show or hide weight columns in annotation. Default show if non-zero."),
|
||||
OPT_BOOLEAN(0, "progress", &report.progress,
|
||||
"Show progress while processing the perf.data file"),
|
||||
OPT_BOOLEAN(0, "header", &report.header, "Show data header."),
|
||||
OPT_BOOLEAN(0, "header-only", &report.header_only,
|
||||
"Show only data header."),
|
||||
|
|
@ -1790,6 +1793,9 @@ int cmd_report(int argc, const char **argv)
|
|||
else
|
||||
use_browser = 0;
|
||||
|
||||
if (report.progress && !quiet && use_browser == 0)
|
||||
stdio_progress__init();
|
||||
|
||||
if (report.data_type && use_browser == 1) {
|
||||
symbol_conf.annotate_data_member = true;
|
||||
symbol_conf.annotate_data_sample = true;
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ perf-ui-y += progress.o
|
|||
perf-ui-y += util.o
|
||||
perf-ui-y += hist.o
|
||||
perf-ui-y += stdio/hist.o
|
||||
perf-ui-y += stdio/progress.o
|
||||
|
||||
CFLAGS_setup.o += -DLIBDIR="BUILD_STR($(LIBDIR))"
|
||||
|
||||
|
|
|
|||
|
|
@ -23,6 +23,8 @@ void __ui_progress__init(struct ui_progress *p, u64 total,
|
|||
|
||||
void ui_progress__update(struct ui_progress *p, u64 adv);
|
||||
|
||||
void stdio_progress__init(void);
|
||||
|
||||
struct ui_progress_ops {
|
||||
void (*init)(struct ui_progress *p);
|
||||
void (*update)(struct ui_progress *p);
|
||||
|
|
|
|||
170
tools/perf/ui/stdio/progress.c
Normal file
170
tools/perf/ui/stdio/progress.c
Normal file
|
|
@ -0,0 +1,170 @@
|
|||
// 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;
|
||||
}
|
||||
|
|
@ -237,14 +237,16 @@ static int do_flush(struct ordered_events *oe, bool show_progress)
|
|||
ui_progress__init(&prog, oe->nr_events, "Processing time ordered events...");
|
||||
|
||||
list_for_each_entry_safe(iter, tmp, head, list) {
|
||||
if (session_done())
|
||||
return 0;
|
||||
if (session_done()) {
|
||||
ret = 0;
|
||||
goto out_progress;
|
||||
}
|
||||
|
||||
if (iter->timestamp > limit)
|
||||
break;
|
||||
ret = oe->deliver(oe, iter);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
goto out_progress;
|
||||
|
||||
ordered_events__delete(oe, iter);
|
||||
oe->last_flush = iter->timestamp;
|
||||
|
|
@ -258,10 +260,16 @@ static int do_flush(struct ordered_events *oe, bool show_progress)
|
|||
else if (last_ts <= limit)
|
||||
oe->last = list_entry(head->prev, struct ordered_event, list);
|
||||
|
||||
ret = 0;
|
||||
out_progress:
|
||||
/*
|
||||
* Always pair ui_progress__init() with ui_progress__finish(), the
|
||||
* stdio backend tracks the phases on a stack.
|
||||
*/
|
||||
if (show_progress)
|
||||
ui_progress__finish();
|
||||
|
||||
return 0;
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int __ordered_events__flush(struct ordered_events *oe, enum oe_flush how,
|
||||
|
|
|
|||
|
|
@ -3261,8 +3261,12 @@ static int __perf_session__process_pipe_events(struct perf_session *session)
|
|||
cur_size = sizeof(union perf_event);
|
||||
|
||||
buf = malloc(cur_size);
|
||||
if (!buf)
|
||||
return -errno;
|
||||
if (!buf) {
|
||||
err = -errno;
|
||||
if (update_prog)
|
||||
ui_progress__finish();
|
||||
return err;
|
||||
}
|
||||
ordered_events__set_copy_on_queue(oe, true);
|
||||
more:
|
||||
event = buf;
|
||||
|
|
@ -3761,8 +3765,10 @@ static int __perf_session__process_dir_events(struct perf_session *session)
|
|||
}
|
||||
|
||||
rd = calloc(nr_readers, sizeof(struct reader));
|
||||
if (!rd)
|
||||
if (!rd) {
|
||||
ui_progress__finish();
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
rd[0] = (struct reader) {
|
||||
.fd = perf_data__fd(session->data),
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user