Commit 54b50916 authored by Andi Kleen's avatar Andi Kleen Committed by Arnaldo Carvalho de Melo

perf stat: Implement --metric-only mode

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.

One issue is that the column code doesn't know in advance what events
are not supported by the CPU, and it would be hard to find out as this
could change based on dynamic conditions. That causes empty columns in
some cases.

The output can be fairly wide, often you may need more than 80 columns.

Example:

% perf stat -a -I 1000 --metric-only
     1.001452803 frontend cycles idle insn per cycle       stalled cycles per insn branch-misses of all branches
     1.001452803  158.91%               0.66                2.39                    2.92%
     2.002192321  180.63%               0.76                2.08                    2.96%
     3.003088282  150.59%               0.62                2.57                    2.84%
     4.004369835  196.20%               0.98                1.62                    3.79%
     5.005227314  231.98%               0.84                1.90                    4.71%

v2: Lots of updates.
v3: Use slightly narrower columns
v4: Add comment
Signed-off-by: default avatarAndi Kleen <ak@linux.intel.com>
Acked-by: default avatarJiri Olsa <jolsa@kernel.org>
Link: http://lkml.kernel.org/r/1457049458-28956-6-git-send-email-andi@firstfloor.orgSigned-off-by: default avatarArnaldo Carvalho de Melo <acme@redhat.com>
parent 6b45f7b2
...@@ -147,6 +147,10 @@ Print count deltas every N milliseconds (minimum: 10ms) ...@@ -147,6 +147,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. 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' 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:: --per-socket::
Aggregate counts per processor socket for system-wide mode measurements. This Aggregate counts per processor socket for system-wide mode measurements. This
is a useful mode to detect imbalance between sockets. To enable this mode, is a useful mode to detect imbalance between sockets. To enable this mode,
......
...@@ -122,6 +122,7 @@ static bool sync_run = false; ...@@ -122,6 +122,7 @@ static bool sync_run = false;
static unsigned int initial_delay = 0; static unsigned int initial_delay = 0;
static unsigned int unit_width = 4; /* strlen("unit") */ static unsigned int unit_width = 4; /* strlen("unit") */
static bool forever = false; static bool forever = false;
static bool metric_only = false;
static struct timespec ref_time; static struct timespec ref_time;
static struct cpu_map *aggr_map; static struct cpu_map *aggr_map;
static aggr_get_id_t aggr_get_id; static aggr_get_id_t aggr_get_id;
...@@ -827,6 +828,99 @@ static void print_metric_csv(void *ctx, ...@@ -827,6 +828,99 @@ static void print_metric_csv(void *ctx,
fprintf(out, "%s%s%s%s", csv_sep, vals, csv_sep, unit); 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) static void nsec_printout(int id, int nr, struct perf_evsel *evsel, double avg)
{ {
FILE *output = stat_config.output; FILE *output = stat_config.output;
...@@ -921,9 +1015,16 @@ static void printout(int id, int nr, struct perf_evsel *counter, double uval, ...@@ -921,9 +1015,16 @@ static void printout(int id, int nr, struct perf_evsel *counter, double uval,
print_metric_t pm = print_metric_std; print_metric_t pm = print_metric_std;
void (*nl)(void *); void (*nl)(void *);
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; nl = new_line_std;
if (csv_output) { if (csv_output && !metric_only) {
static int aggr_fields[] = { static int aggr_fields[] = {
[AGGR_GLOBAL] = 0, [AGGR_GLOBAL] = 0,
[AGGR_THREAD] = 1, [AGGR_THREAD] = 1,
...@@ -940,6 +1041,10 @@ static void printout(int id, int nr, struct perf_evsel *counter, double uval, ...@@ -940,6 +1041,10 @@ static void printout(int id, int nr, struct perf_evsel *counter, double uval,
os.nfields++; os.nfields++;
} }
if (run == 0 || ena == 0 || counter->counts->scaled == -1) { if (run == 0 || ena == 0 || counter->counts->scaled == -1) {
if (metric_only) {
pm(&os, NULL, "", "", 0);
return;
}
aggr_printout(counter, id, nr); aggr_printout(counter, id, nr);
fprintf(stat_config.output, "%*s%s", fprintf(stat_config.output, "%*s%s",
...@@ -968,7 +1073,9 @@ static void printout(int id, int nr, struct perf_evsel *counter, double uval, ...@@ -968,7 +1073,9 @@ static void printout(int id, int nr, struct perf_evsel *counter, double uval,
return; return;
} }
if (nsec_counter(counter)) if (metric_only)
/* nothing */;
else if (nsec_counter(counter))
nsec_printout(id, nr, counter, uval); nsec_printout(id, nr, counter, uval);
else else
abs_printout(id, nr, counter, uval); abs_printout(id, nr, counter, uval);
...@@ -977,7 +1084,7 @@ static void printout(int id, int nr, struct perf_evsel *counter, double uval, ...@@ -977,7 +1084,7 @@ static void printout(int id, int nr, struct perf_evsel *counter, double uval,
out.new_line = nl; out.new_line = nl;
out.ctx = &os; out.ctx = &os;
if (csv_output) { if (csv_output && !metric_only) {
print_noise(counter, noise); print_noise(counter, noise);
print_running(run, ena); print_running(run, ena);
} }
...@@ -985,7 +1092,7 @@ static void printout(int id, int nr, struct perf_evsel *counter, double uval, ...@@ -985,7 +1092,7 @@ static void printout(int id, int nr, struct perf_evsel *counter, double uval,
perf_stat__print_shadow_stats(counter, uval, perf_stat__print_shadow_stats(counter, uval,
first_shadow_cpu(counter, id), first_shadow_cpu(counter, id),
&out); &out);
if (!csv_output) { if (!csv_output && !metric_only) {
print_noise(counter, noise); print_noise(counter, noise);
print_running(run, ena); print_running(run, ena);
} }
...@@ -1021,14 +1128,23 @@ static void print_aggr(char *prefix) ...@@ -1021,14 +1128,23 @@ static void print_aggr(char *prefix)
int cpu, s, s2, id, nr; int cpu, s, s2, id, nr;
double uval; double uval;
u64 ena, run, val; u64 ena, run, val;
bool first;
if (!(aggr_map || aggr_get_id)) if (!(aggr_map || aggr_get_id))
return; return;
aggr_update_shadow(); aggr_update_shadow();
/*
* With metric_only everything is on a single line.
* Without each counter has its own line.
*/
for (s = 0; s < aggr_map->nr; s++) { for (s = 0; s < aggr_map->nr; s++) {
if (prefix && metric_only)
fprintf(output, "%s", prefix);
id = aggr_map->map[s]; id = aggr_map->map[s];
first = true;
evlist__for_each(evsel_list, counter) { evlist__for_each(evsel_list, counter) {
val = ena = run = 0; val = ena = run = 0;
nr = 0; nr = 0;
...@@ -1041,13 +1157,20 @@ static void print_aggr(char *prefix) ...@@ -1041,13 +1157,20 @@ static void print_aggr(char *prefix)
run += perf_counts(counter->counts, cpu, 0)->run; run += perf_counts(counter->counts, cpu, 0)->run;
nr++; nr++;
} }
if (prefix) if (first && metric_only) {
first = false;
aggr_printout(counter, id, nr);
}
if (prefix && !metric_only)
fprintf(output, "%s", prefix); fprintf(output, "%s", prefix);
uval = val * counter->scale; uval = val * counter->scale;
printout(id, nr, counter, uval, prefix, run, ena, 1.0); printout(id, nr, counter, uval, prefix, run, ena, 1.0);
if (!metric_only)
fputc('\n', output); fputc('\n', output);
} }
if (metric_only)
fputc('\n', output);
} }
} }
...@@ -1092,11 +1215,12 @@ static void print_counter_aggr(struct perf_evsel *counter, char *prefix) ...@@ -1092,11 +1215,12 @@ static void print_counter_aggr(struct perf_evsel *counter, char *prefix)
avg_enabled = avg_stats(&ps->res_stats[1]); avg_enabled = avg_stats(&ps->res_stats[1]);
avg_running = avg_stats(&ps->res_stats[2]); avg_running = avg_stats(&ps->res_stats[2]);
if (prefix) if (prefix && !metric_only)
fprintf(output, "%s", prefix); fprintf(output, "%s", prefix);
uval = avg * counter->scale; uval = avg * counter->scale;
printout(-1, 0, counter, uval, prefix, avg_running, avg_enabled, avg); printout(-1, 0, counter, uval, prefix, avg_running, avg_enabled, avg);
if (!metric_only)
fprintf(output, "\n"); fprintf(output, "\n");
} }
...@@ -1126,6 +1250,43 @@ static void print_counter(struct perf_evsel *counter, char *prefix) ...@@ -1126,6 +1250,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) static void print_interval(char *prefix, struct timespec *ts)
{ {
FILE *output = stat_config.output; FILE *output = stat_config.output;
...@@ -1133,7 +1294,7 @@ static void print_interval(char *prefix, struct timespec *ts) ...@@ -1133,7 +1294,7 @@ static void print_interval(char *prefix, struct timespec *ts)
sprintf(prefix, "%6lu.%09lu%s", ts->tv_sec, ts->tv_nsec, csv_sep); 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) { switch (stat_config.aggr_mode) {
case AGGR_SOCKET: case AGGR_SOCKET:
fprintf(output, "# time socket cpus counts %*s events\n", unit_width, "unit"); fprintf(output, "# time socket cpus counts %*s events\n", unit_width, "unit");
...@@ -1220,6 +1381,17 @@ static void print_counters(struct timespec *ts, int argc, const char **argv) ...@@ -1220,6 +1381,17 @@ static void print_counters(struct timespec *ts, int argc, const char **argv)
else else
print_header(argc, argv); 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) { switch (stat_config.aggr_mode) {
case AGGR_CORE: case AGGR_CORE:
case AGGR_SOCKET: case AGGR_SOCKET:
...@@ -1232,6 +1404,8 @@ static void print_counters(struct timespec *ts, int argc, const char **argv) ...@@ -1232,6 +1404,8 @@ static void print_counters(struct timespec *ts, int argc, const char **argv)
case AGGR_GLOBAL: case AGGR_GLOBAL:
evlist__for_each(evsel_list, counter) evlist__for_each(evsel_list, counter)
print_counter_aggr(counter, prefix); print_counter_aggr(counter, prefix);
if (metric_only)
fputc('\n', stat_config.output);
break; break;
case AGGR_NONE: case AGGR_NONE:
evlist__for_each(evsel_list, counter) evlist__for_each(evsel_list, counter)
...@@ -1356,6 +1530,8 @@ static const struct option stat_options[] = { ...@@ -1356,6 +1530,8 @@ static const struct option stat_options[] = {
"aggregate counts per thread", AGGR_THREAD), "aggregate counts per thread", AGGR_THREAD),
OPT_UINTEGER('D', "delay", &initial_delay, OPT_UINTEGER('D', "delay", &initial_delay,
"ms to wait before starting measurement after program start"), "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() OPT_END()
}; };
...@@ -1997,6 +2173,21 @@ int cmd_stat(int argc, const char **argv, const char *prefix __maybe_unused) ...@@ -1997,6 +2173,21 @@ int cmd_stat(int argc, const char **argv, const char *prefix __maybe_unused)
goto out; 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) { if (output_fd < 0) {
fprintf(stderr, "argument to --log-fd must be a > 0\n"); fprintf(stderr, "argument to --log-fd must be a > 0\n");
parse_options_usage(stat_usage, stat_options, "log-fd", 0); parse_options_usage(stat_usage, stat_options, "log-fd", 0);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment