[PATCH 7/8] perf, tools, stat: Implement --metric-only mode

From: Andi Kleen
Date: Tue Jan 26 2016 - 19:44:53 EST


From: Andi Kleen <ak@xxxxxxxxxxxxxxx>

Add a new mode to only print metrics. Sometimes we don't care about
the raw values, just want the computed metrics. This allows more
compact printing, so with -I each sample is only a single line.
This also allows easier plotting and processing with other tools.

The main target is with using --topdown, but it also works with
-T and standard perf stat. A few metrics are not supported.

To avoiding having to hardcode all the metrics in the code it uses
a two pass approach: first compute dummy metrics and only
print the headers in the print_metric callback. Then use the callback
to print the actual values. There are some additional changes
in the stat printout code to handle all metrics being on a single line.

Example:

% perf stat -a -I 1000 --metric-only
1.000604977 frontend cycles idle backend cycles idle insn per cycle stalled cycles per insn branch-misses of all branches
1.000604977 0.76 2.35%
2.000924680 0.72 2.34%
3.001139592 0.76 2.57%
4.001358452 0.73 2.44%

The output is fairly wide, but that's a trade off for the concise format.

v2: Lots of updates.
v3: Use slightly narrower columns
Signed-off-by: Andi Kleen <ak@xxxxxxxxxxxxxxx>
---
tools/perf/Documentation/perf-stat.txt | 4 +
tools/perf/builtin-stat.c | 210 +++++++++++++++++++++++++++++++--
2 files changed, 204 insertions(+), 10 deletions(-)

diff --git a/tools/perf/Documentation/perf-stat.txt b/tools/perf/Documentation/perf-stat.txt
index c85b568..1b13cdd 100644
--- a/tools/perf/Documentation/perf-stat.txt
+++ b/tools/perf/Documentation/perf-stat.txt
@@ -139,6 +139,10 @@ Print count deltas every N milliseconds (minimum: 10ms)
The overhead percentage could be high in some cases, for instance with small, sub 100ms intervals. Use with caution.
example: 'perf stat -I 1000 -e cycles -a sleep 5'

+--metric-only::
+Only print computed metrics. Print them in a single line.
+Don't show any raw values. Not supported with -A or --per-thread.
+
--per-socket::
Aggregate counts per processor socket for system-wide mode measurements. This
is a useful mode to detect imbalance between sockets. To enable this mode,
diff --git a/tools/perf/builtin-stat.c b/tools/perf/builtin-stat.c
index 607be16..97de140 100644
--- a/tools/perf/builtin-stat.c
+++ b/tools/perf/builtin-stat.c
@@ -122,6 +122,7 @@ static bool sync_run = false;
static unsigned int initial_delay = 0;
static unsigned int unit_width = 4; /* strlen("unit") */
static bool forever = false;
+static bool metric_only = false;
static struct timespec ref_time;
static struct cpu_map *aggr_map;
static aggr_get_id_t aggr_get_id;
@@ -828,6 +829,99 @@ static void print_metric_csv(void *ctx,
fprintf(out, "%s%s%s%s", csv_sep, vals, csv_sep, unit);
}

+#define METRIC_ONLY_LEN 20
+
+/* Filter out some columns that don't work well in metrics only mode */
+
+static bool valid_only_metric(const char *unit)
+{
+ if (!unit)
+ return false;
+ if (strstr(unit, "/sec") ||
+ strstr(unit, "hz") ||
+ strstr(unit, "Hz") ||
+ strstr(unit, "CPUs utilized"))
+ return false;
+ return true;
+}
+
+static const char *fixunit(char *buf, struct perf_evsel *evsel,
+ const char *unit)
+{
+ if (!strncmp(unit, "of all", 6)) {
+ snprintf(buf, 1024, "%s %s", perf_evsel__name(evsel),
+ unit);
+ return buf;
+ }
+ return unit;
+}
+
+static void print_metric_only(void *ctx, const char *color, const char *fmt,
+ const char *unit, double val)
+{
+ struct outstate *os = ctx;
+ FILE *out = os->fh;
+ int n;
+ char buf[1024];
+ unsigned mlen = METRIC_ONLY_LEN;
+
+ if (!valid_only_metric(unit))
+ return;
+ unit = fixunit(buf, os->evsel, unit);
+ if (color)
+ n = color_fprintf(out, color, fmt, val);
+ else
+ n = fprintf(out, fmt, val);
+ if (n > METRIC_ONLY_LEN)
+ n = METRIC_ONLY_LEN;
+ if (mlen < strlen(unit))
+ mlen = strlen(unit) + 1;
+ fprintf(out, "%*s", mlen - n, "");
+}
+
+static void print_metric_only_csv(void *ctx, const char *color __maybe_unused,
+ const char *fmt,
+ const char *unit, double val)
+{
+ struct outstate *os = ctx;
+ FILE *out = os->fh;
+ char buf[64], *vals, *ends;
+ char tbuf[1024];
+
+ if (!valid_only_metric(unit))
+ return;
+ unit = fixunit(tbuf, os->evsel, unit);
+ snprintf(buf, sizeof buf, fmt, val);
+ vals = buf;
+ while (isspace(*vals))
+ vals++;
+ ends = vals;
+ while (isdigit(*ends) || *ends == '.')
+ ends++;
+ *ends = 0;
+ fprintf(out, "%s%s", vals, csv_sep);
+}
+
+static void new_line_metric(void *ctx __maybe_unused)
+{
+}
+
+static void print_metric_header(void *ctx, const char *color __maybe_unused,
+ const char *fmt __maybe_unused,
+ const char *unit, double val __maybe_unused)
+{
+ struct outstate *os = ctx;
+ char tbuf[1024];
+
+ if (!valid_only_metric(unit))
+ return;
+ unit = fixunit(tbuf, os->evsel, unit);
+ if (csv_output)
+ fprintf(os->fh, "%s%s", unit, csv_sep);
+ else
+ fprintf(os->fh, "%-*s ", METRIC_ONLY_LEN, unit);
+}
+
static void nsec_printout(int id, int nr, struct perf_evsel *evsel, double avg)
{
FILE *output = stat_config.output;
@@ -916,9 +1010,16 @@ static void printout(int id, int nr, struct perf_evsel *counter, double uval,
print_metric_t pm = print_metric_std;
void (*nl)(void *);

- nl = new_line_std;
+ if (metric_only) {
+ nl = new_line_metric;
+ if (csv_output)
+ pm = print_metric_only_csv;
+ else
+ pm = print_metric_only;
+ } else
+ nl = new_line_std;

- if (csv_output) {
+ if (csv_output && !metric_only) {
static int aggr_fields[] = {
[AGGR_GLOBAL] = 0,
[AGGR_THREAD] = 1,
@@ -937,6 +1038,10 @@ static void printout(int id, int nr, struct perf_evsel *counter, double uval,
os.ena = ena;
}
if (run == 0 || ena == 0) {
+ if (metric_only) {
+ pm(&os, NULL, "", "", 0);
+ return;
+ }
aggr_printout(counter, id, nr);

fprintf(stat_config.output, "%*s%s",
@@ -962,7 +1067,9 @@ static void printout(int id, int nr, struct perf_evsel *counter, double uval,
return;
}

- if (nsec_counter(counter))
+ if (metric_only)
+ /* nothing */;
+ else if (nsec_counter(counter))
nsec_printout(id, nr, counter, uval);
else
abs_printout(id, nr, counter, uval);
@@ -975,8 +1082,11 @@ static void printout(int id, int nr, struct perf_evsel *counter, double uval,
stat_config.aggr_mode == AGGR_GLOBAL ? 0 :
first_shadow_cpu(counter, id),
&out);
- print_noise(counter, noise);
- print_running(run, ena);
+
+ if (!metric_only) {
+ print_noise(counter, noise);
+ print_running(run, ena);
+ }
}

static void aggr_update_shadow(void)
@@ -1010,6 +1120,7 @@ static void print_aggr(char *prefix)
int cpu, s, s2, id, nr;
double uval;
u64 ena, run, val;
+ bool first;

if (!(aggr_map || aggr_get_id))
return;
@@ -1017,7 +1128,11 @@ static void print_aggr(char *prefix)
aggr_update_shadow();

for (s = 0; s < aggr_map->nr; s++) {
+ if (prefix && metric_only)
+ fprintf(output, "%s", prefix);
+
id = aggr_map->map[s];
+ first = true;
evlist__for_each(evsel_list, counter) {
val = ena = run = 0;
nr = 0;
@@ -1030,13 +1145,20 @@ static void print_aggr(char *prefix)
run += perf_counts(counter->counts, cpu, 0)->run;
nr++;
}
- if (prefix)
+ if (first && metric_only) {
+ first = false;
+ aggr_printout(counter, id, nr);
+ }
+ if (prefix && !metric_only)
fprintf(output, "%s", prefix);

uval = val * counter->scale;
printout(id, nr, counter, uval, prefix, run, ena, 1.0);
- fputc('\n', output);
+ if (!metric_only)
+ fputc('\n', output);
}
+ if (metric_only)
+ fputc('\n', output);
}
}

@@ -1081,12 +1203,13 @@ static void print_counter_aggr(struct perf_evsel *counter, char *prefix)
avg_enabled = avg_stats(&ps->res_stats[1]);
avg_running = avg_stats(&ps->res_stats[2]);

- if (prefix)
+ if (prefix && !metric_only)
fprintf(output, "%s", prefix);

uval = avg * counter->scale;
printout(-1, 0, counter, uval, prefix, avg_running, avg_enabled, avg);
- fprintf(output, "\n");
+ if (!metric_only)
+ fprintf(output, "\n");
}

/*
@@ -1115,6 +1238,43 @@ static void print_counter(struct perf_evsel *counter, char *prefix)
}
}

+static int aggr_header_lens[] = {
+ [AGGR_CORE] = 18,
+ [AGGR_SOCKET] = 12,
+ [AGGR_NONE] = 15,
+ [AGGR_THREAD] = 24,
+ [AGGR_GLOBAL] = 0,
+};
+
+static void print_metric_headers(char *prefix)
+{
+ struct perf_stat_output_ctx out;
+ struct perf_evsel *counter;
+ struct outstate os = {
+ .fh = stat_config.output
+ };
+
+ if (prefix)
+ fprintf(stat_config.output, "%s", prefix);
+
+ if (!csv_output)
+ fprintf(stat_config.output, "%*s",
+ aggr_header_lens[stat_config.aggr_mode], "");
+
+ /* Print metrics headers only */
+ evlist__for_each(evsel_list, counter) {
+ os.evsel = counter;
+ out.ctx = &os;
+ out.print_metric = print_metric_header;
+ out.new_line = new_line_metric;
+ os.evsel = counter;
+ perf_stat__print_shadow_stats(counter, 0,
+ 0,
+ &out);
+ }
+ fputc('\n', stat_config.output);
+}
+
static void print_interval(char *prefix, struct timespec *ts)
{
FILE *output = stat_config.output;
@@ -1122,7 +1282,7 @@ static void print_interval(char *prefix, struct timespec *ts)

sprintf(prefix, "%6lu.%09lu%s", ts->tv_sec, ts->tv_nsec, csv_sep);

- if (num_print_interval == 0 && !csv_output) {
+ if (num_print_interval == 0 && !csv_output && !metric_only) {
switch (stat_config.aggr_mode) {
case AGGR_SOCKET:
fprintf(output, "# time socket cpus counts %*s events\n", unit_width, "unit");
@@ -1209,6 +1369,17 @@ static void print_counters(struct timespec *ts, int argc, const char **argv)
else
print_header(argc, argv);

+ if (metric_only) {
+ static int num_print_iv;
+
+ if (num_print_iv == 0)
+ print_metric_headers(prefix);
+ if (num_print_iv++ == 25)
+ num_print_iv = 0;
+ if (stat_config.aggr_mode == AGGR_GLOBAL && prefix)
+ fprintf(stat_config.output, "%s", prefix);
+ }
+
switch (stat_config.aggr_mode) {
case AGGR_CORE:
case AGGR_SOCKET:
@@ -1221,6 +1392,8 @@ static void print_counters(struct timespec *ts, int argc, const char **argv)
case AGGR_GLOBAL:
evlist__for_each(evsel_list, counter)
print_counter_aggr(counter, prefix);
+ if (metric_only)
+ fputc('\n', stat_config.output);
break;
case AGGR_NONE:
evlist__for_each(evsel_list, counter)
@@ -1345,6 +1518,8 @@ static const struct option stat_options[] = {
"aggregate counts per thread", AGGR_THREAD),
OPT_UINTEGER('D', "delay", &initial_delay,
"ms to wait before starting measurement after program start"),
+ OPT_BOOLEAN(0, "metric-only", &metric_only,
+ "Only print computed metrics. No raw values"),
OPT_END()
};

@@ -1966,6 +2141,21 @@ int cmd_stat(int argc, const char **argv, const char *prefix __maybe_unused)
goto out;
}

+ if (metric_only && stat_config.aggr_mode == AGGR_THREAD) {
+ fprintf(stderr, "--metric-only is not supported with --per-thread\n");
+ goto out;
+ }
+
+ if (metric_only && stat_config.aggr_mode == AGGR_NONE) {
+ fprintf(stderr, "--metric-only is not supported with -A\n");
+ goto out;
+ }
+
+ if (metric_only && run_count > 1) {
+ fprintf(stderr, "--metric-only is not supported with -r\n");
+ goto out;
+ }
+
if (output_fd < 0) {
fprintf(stderr, "argument to --log-fd must be a > 0\n");
parse_options_usage(stat_usage, stat_options, "log-fd", 0);
--
2.4.3