|
@@ -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;
|
|
@@ -827,6 +828,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;
|
|
@@ -921,9 +1015,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,
|
|
@@ -940,6 +1041,10 @@ static void printout(int id, int nr, struct perf_evsel *counter, double uval,
|
|
|
os.nfields++;
|
|
|
}
|
|
|
if (run == 0 || ena == 0 || counter->counts->scaled == -1) {
|
|
|
+ if (metric_only) {
|
|
|
+ pm(&os, NULL, "", "", 0);
|
|
|
+ return;
|
|
|
+ }
|
|
|
aggr_printout(counter, id, nr);
|
|
|
|
|
|
fprintf(stat_config.output, "%*s%s",
|
|
@@ -968,7 +1073,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);
|
|
@@ -977,7 +1084,7 @@ static void printout(int id, int nr, struct perf_evsel *counter, double uval,
|
|
|
out.new_line = nl;
|
|
|
out.ctx = &os;
|
|
|
|
|
|
- if (csv_output) {
|
|
|
+ if (csv_output && !metric_only) {
|
|
|
print_noise(counter, noise);
|
|
|
print_running(run, ena);
|
|
|
}
|
|
@@ -985,7 +1092,7 @@ static void printout(int id, int nr, struct perf_evsel *counter, double uval,
|
|
|
perf_stat__print_shadow_stats(counter, uval,
|
|
|
first_shadow_cpu(counter, id),
|
|
|
&out);
|
|
|
- if (!csv_output) {
|
|
|
+ if (!csv_output && !metric_only) {
|
|
|
print_noise(counter, noise);
|
|
|
print_running(run, ena);
|
|
|
}
|
|
@@ -1021,14 +1128,23 @@ 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;
|
|
|
|
|
|
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++) {
|
|
|
+ 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;
|
|
@@ -1041,13 +1157,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);
|
|
|
}
|
|
|
}
|
|
|
|
|
@@ -1092,12 +1215,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");
|
|
|
}
|
|
|
|
|
|
/*
|
|
@@ -1126,6 +1250,73 @@ static void print_counter(struct perf_evsel *counter, char *prefix)
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+static void print_no_aggr_metric(char *prefix)
|
|
|
+{
|
|
|
+ int cpu;
|
|
|
+ int nrcpus = 0;
|
|
|
+ struct perf_evsel *counter;
|
|
|
+ u64 ena, run, val;
|
|
|
+ double uval;
|
|
|
+
|
|
|
+ nrcpus = evsel_list->cpus->nr;
|
|
|
+ for (cpu = 0; cpu < nrcpus; cpu++) {
|
|
|
+ bool first = true;
|
|
|
+
|
|
|
+ if (prefix)
|
|
|
+ fputs(prefix, stat_config.output);
|
|
|
+ evlist__for_each(evsel_list, counter) {
|
|
|
+ if (first) {
|
|
|
+ aggr_printout(counter, cpu, 0);
|
|
|
+ first = false;
|
|
|
+ }
|
|
|
+ val = perf_counts(counter->counts, cpu, 0)->val;
|
|
|
+ ena = perf_counts(counter->counts, cpu, 0)->ena;
|
|
|
+ run = perf_counts(counter->counts, cpu, 0)->run;
|
|
|
+
|
|
|
+ uval = val * counter->scale;
|
|
|
+ printout(cpu, 0, counter, uval, prefix, run, ena, 1.0);
|
|
|
+ }
|
|
|
+ fputc('\n', stat_config.output);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+static int aggr_header_lens[] = {
|
|
|
+ [AGGR_CORE] = 18,
|
|
|
+ [AGGR_SOCKET] = 12,
|
|
|
+ [AGGR_NONE] = 6,
|
|
|
+ [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;
|
|
@@ -1133,7 +1324,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");
|
|
@@ -1220,6 +1411,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:
|
|
@@ -1232,10 +1434,16 @@ 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)
|
|
|
- print_counter(counter, prefix);
|
|
|
+ if (metric_only)
|
|
|
+ print_no_aggr_metric(prefix);
|
|
|
+ else {
|
|
|
+ evlist__for_each(evsel_list, counter)
|
|
|
+ print_counter(counter, prefix);
|
|
|
+ }
|
|
|
break;
|
|
|
case AGGR_UNSET:
|
|
|
default:
|
|
@@ -1356,6 +1564,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()
|
|
|
};
|
|
|
|
|
@@ -1997,6 +2207,16 @@ 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 && 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);
|