linux-kernel.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCH 1/1] perf stat: Add JSON output option and testing.
@ 2021-08-11 22:43 Claire Jensen
  2021-08-12 18:23 ` Arnaldo Carvalho de Melo
  0 siblings, 1 reply; 2+ messages in thread
From: Claire Jensen @ 2021-08-11 22:43 UTC (permalink / raw)
  To: peterz, mingo, acme, mark.rutland, alexander.shishkin, jolsa,
	namhyung, yao.jin, song, andi, adrian.hunter, kan.liang,
	james.clark, alexander.antonov, changbin.du, liuqi115, irogers,
	eranian, linux-kernel, linux-perf-users
  Cc: Claire Jensen

CSV output is tricky to format and column layout changes are susceptible
to breaking parsers. New JSON-formatted output has variable names to
identify fields that are consistent and informative, making
the output parseable.

CSV output example:

1.20,msec,task-clock:u,1204272,100.00,0.697,CPUs utilized
0,,context-switches:u,1204272,100.00,0.000,/sec
0,,cpu-migrations:u,1204272,100.00,0.000,/sec
70,,page-faults:u,1204272,100.00,58.126,K/sec

JSON output example:

{"counter-value" : "3805.723968", "unit" : "msec", "event" :
"cpu-clock", "event-runtime" : 3805731510100.00, "pcnt-running"
: 100.00, "metric-value" : 4.007571, "metric-unit" : "CPUs utilized"}
{"counter-value" : "6166.000000", "unit" : "", "event" :
"context-switches", "event-runtime" : 3805723045100.00, "pcnt-running"
: 100.00, "metric-value" : 1.620191, "metric-unit" : "K/sec"}
{"counter-value" : "466.000000", "unit" : "", "event" :
"cpu-migrations", "event-runtime" : 3805727613100.00, "pcnt-running"
: 100.00, "metric-value" : 122.447136, "metric-unit" : "/sec"}
{"counter-value" : "208.000000", "unit" : "", "event" :
"page-faults", "event-runtime" : 3805726799100.00, "pcnt-running"
: 100.00, "metric-value" : 54.654516, "metric-unit" : "/sec"}

Add field checking tests for perf stat JSON output.
Counts number of fields to make sure expected fields are present.

Also added documentation for JSON option.

Signed-off-by: Claire Jensen <cjense@google.com>
---
 tools/perf/Documentation/perf-stat.txt        |  21 +
 tools/perf/builtin-stat.c                     |   2 +
 .../tests/shell/lib/perf_json_output_test.py  |  49 ++
 tools/perf/tests/shell/stat+json_output.sh    | 114 +++++
 tools/perf/util/stat-display.c                | 429 +++++++++++++-----
 tools/perf/util/stat-shadow.c                 |   5 +-
 tools/perf/util/stat.h                        |   1 +
 7 files changed, 502 insertions(+), 119 deletions(-)
 create mode 100644 tools/perf/tests/shell/lib/perf_json_output_test.py
 create mode 100644 tools/perf/tests/shell/stat+json_output.sh

diff --git a/tools/perf/Documentation/perf-stat.txt b/tools/perf/Documentation/perf-stat.txt
index 45c2467e4eb2..80ae3802a904 100644
--- a/tools/perf/Documentation/perf-stat.txt
+++ b/tools/perf/Documentation/perf-stat.txt
@@ -554,6 +554,27 @@ Additional metrics may be printed with all earlier fields being empty.
 
 include::intel-hybrid.txt[]
 
+JSON FORMAT
+-----------
+
+With -j, perf stat is able to print out a JSON format output
+that can be used for parsing.
+
+- timestamp : optional usec time stamp in fractions of second (with -I)
+- optional aggregate options:
+		- core : core identifier (with --per-core)
+		- die : die identifier (with --per-die)
+		- socket : socket identifier (with --per-socket)
+		- node : node identifier (with --per-node)
+		- thread : thread identifier (with --per-thread)
+- counter-value : counter value
+- unit : unit of the counter value or empty
+- event : event name
+- variance : optional variance if multiple values are collected (with -r)
+- runtime : run time of counter
+- metric-value : optional metric value
+- metric-unit : optional unit of metric
+
 SEE ALSO
 --------
 linkperf:perf-top[1], linkperf:perf-list[1]
diff --git a/tools/perf/builtin-stat.c b/tools/perf/builtin-stat.c
index 634375937db9..71b8418a2a06 100644
--- a/tools/perf/builtin-stat.c
+++ b/tools/perf/builtin-stat.c
@@ -1218,6 +1218,8 @@ static struct option stat_options[] = {
 	OPT_BOOLEAN(0, "no-merge", &stat_config.no_merge, "Do not merge identical named events"),
 	OPT_STRING('x', "field-separator", &stat_config.csv_sep, "separator",
 		   "print counts with custom separator"),
+	OPT_BOOLEAN('j', "json-output", &stat_config.json_output,
+		   "print counts in JSON format"),
 	OPT_CALLBACK('G', "cgroup", &evsel_list, "name",
 		     "monitor event in cgroup name only", parse_stat_cgroups),
 	OPT_STRING(0, "for-each-cgroup", &stat_config.cgroup_list, "name",
diff --git a/tools/perf/tests/shell/lib/perf_json_output_test.py b/tools/perf/tests/shell/lib/perf_json_output_test.py
new file mode 100644
index 000000000000..93328e1d9f48
--- /dev/null
+++ b/tools/perf/tests/shell/lib/perf_json_output_test.py
@@ -0,0 +1,49 @@
+#!/usr/bin/python
+# SPDX-License-Identifier: GPL-2.0
+
+from __future__ import print_function
+import argparse
+import sys
+
+# Basic sanity check of perf JSON output as specified in the man page.
+# Currently just checks the number of fields per line in output.
+
+ap = argparse.ArgumentParser()
+ap.add_argument('--no-args', action='store_true')
+ap.add_argument('--interval', action='store_true')
+ap.add_argument('--all-cpus-no-aggr', action='store_true')
+ap.add_argument('--all-cpus', action='store_true')
+ap.add_argument('--event', action='store_true')
+ap.add_argument('--per-core', action='store_true')
+ap.add_argument('--per-thread', action='store_true')
+ap.add_argument('--per-die', action='store_true')
+ap.add_argument('--per-node', action='store_true')
+ap.add_argument('--per-socket', action='store_true')
+args = ap.parse_args()
+
+Lines = sys.stdin.readlines()
+ch = ','
+
+
+def check_json_output(exp):
+  for line in Lines:
+    if 'failed' not in line:
+      count = 0
+      count = line.count(ch)
+      if count != exp:
+        sys.stdout.write(''.join(Lines))
+        raise RuntimeError('wrong number of fields. counted {0}'
+                           ' expected {1} in {2}\n'.format(count, exp, line))
+
+
+try:
+  if args.no_args or args.all_cpus or args.event:
+    check_json_output(6)
+  if args.interval or args.per_thread:
+    check_json_output(7)
+  if args.per_core or args.per_socket or args.per_node or args.per_die:
+    check_json_output(8)
+
+except:
+  sys.stdout.write('Test failed for input:\n' + ''.join(Lines))
+  raise
diff --git a/tools/perf/tests/shell/stat+json_output.sh b/tools/perf/tests/shell/stat+json_output.sh
new file mode 100644
index 000000000000..bb05d5060680
--- /dev/null
+++ b/tools/perf/tests/shell/stat+json_output.sh
@@ -0,0 +1,114 @@
+#!/bin/bash
+# SPDX-License-Identifier: GPL-2.0
+# perf stat JSON output linter
+# Tests various perf stat JSON output commands for the
+# correct number of fields.
+
+set -e
+set -x
+
+pythonchecker=$(dirname $0)/lib/perf_json_output_lint.py
+file="/proc/sys/kernel/perf_event_paranoid"
+paranoia=$(cat "$file" | grep -o -E '[0-9]+')
+
+check_no_args()
+{
+	perf stat -j sleep 1 2>&1 | \
+	python $pythonchecker --no-args
+}
+
+if [ $paranoia -gt 0 ];
+then
+	echo check_all_cpus test skipped because of paranoia level.
+else
+	check_all_cpus()
+	{
+		perf stat -j -a 2>&1 sleep 1 | \
+		python $pythonchecker --all-cpus
+	}
+	check_all_cpus
+fi
+
+check_interval()
+{
+	perf stat -j -I 1000 2>&1 sleep 1 | \
+	python $pythonchecker --interval
+}
+
+check_all_cpus_no_aggr()
+{
+	perf stat -j -A -a --no-merge 2>&1 sleep 1 | \
+	python $pythonchecker --all-cpus-no-aggr
+}
+
+check_event()
+{
+	perf stat -j -e cpu-clock 2>&1 sleep 1 | \
+	python $pythonchecker --event
+}
+
+if [ $paranoia -gt 0 ];
+then
+	echo check_all_cpus test skipped because of paranoia level.
+else
+	check_per_core()
+	{
+		perf stat -j --per-core -a 2>&1 sleep 1 | \
+		python $pythonchecker --per-core
+	}
+	check_per_core
+fi
+
+if [ $paranoia -gt 0 ];
+then
+	echo check_all_cpus test skipped because of paranoia level.
+else
+	check_per_thread()
+	{
+		perf stat -j --per-thread -a 2>&1 sleep 1 | \
+		python $pythonchecker --per-thread
+	}
+	check_per_thread
+fi
+
+if [ $paranoia -gt 0 ];
+then
+	echo check_per_die test skipped because of paranoia level.
+else
+	check_per_die()
+	{
+		perf stat -j --per-die -a 2>&1 sleep 1 | \
+		python $pythonchecker --per-die
+	}
+	check_per_die
+fi
+
+if [ $paranoia -gt 0 ];
+then
+	echo check_per_node test skipped because of paranoia level.
+else
+	check_per_node()
+	{
+		perf stat -j --per-node -a 2>&1 sleep 1 | \
+		python $pythonchecker --per-node
+	}
+	check_per_node
+fi
+
+if [ $paranoia -gt 0 ];
+then
+	echo check_per_socket test skipped because of paranoia level.
+else
+	check_per_socket()
+	{
+		perf stat -j --per-socket -a 2>&1 sleep 1 | \
+		python $pythonchecker --per-socket
+	}
+	check_per_socket
+fi
+
+check_no_args
+check_interval
+check_all_cpus_no_aggr
+check_event
+exit 0
diff --git a/tools/perf/util/stat-display.c b/tools/perf/util/stat-display.c
index 588601000f3f..82d23fd0df46 100644
--- a/tools/perf/util/stat-display.c
+++ b/tools/perf/util/stat-display.c
@@ -1,25 +1,25 @@
 #include <stdlib.h>
 #include <stdio.h>
 #include <inttypes.h>
-#include <linux/string.h>
-#include <linux/time64.h>
+#include "third_party/linux_tools/src/tools/include/linux/string.h"
+#include "third_party/linux_tools/src/tools/include/linux/time64.h"
 #include <math.h>
-#include "color.h"
-#include "counts.h"
-#include "evlist.h"
-#include "evsel.h"
-#include "stat.h"
-#include "top.h"
-#include "thread_map.h"
-#include "cpumap.h"
-#include "string2.h"
-#include <linux/ctype.h>
-#include "cgroup.h"
-#include <api/fs/fs.h>
-#include "util.h"
-#include "iostat.h"
-#include "pmu-hybrid.h"
-#include "evlist-hybrid.h"
+#include "third_party/linux_tools/src/tools/perf/util/color.h"
+#include "third_party/linux_tools/src/tools/perf/util/counts.h"
+#include "third_party/linux_tools/src/tools/perf/util/evlist.h"
+#include "third_party/linux_tools/src/tools/perf/util/evsel.h"
+#include "third_party/linux_tools/src/tools/perf/util/stat.h"
+#include "third_party/linux_tools/src/tools/perf/util/top.h"
+#include "third_party/linux_tools/src/tools/perf/util/thread_map.h"
+#include "third_party/linux_tools/src/tools/perf/util/cpumap.h"
+#include "third_party/linux_tools/src/tools/perf/util/string2.h"
+#include "third_party/linux_tools/src/tools/include/linux/ctype.h"
+#include "third_party/linux_tools/src/tools/perf/util/cgroup.h"
+#include "third_party/linux_tools/src/tools/lib/api/fs/fs.h"
+#include "third_party/linux_tools/src/tools/perf/util/util.h"
+#include "third_party/linux_tools/src/tools/perf/util/iostat.h"
+#include "third_party/linux_tools/src/tools/perf/util/pmu-hybrid.h"
+#include "third_party/linux_tools/src/tools/perf/util/evlist-hybrid.h"
 
 #define CNTR_NOT_SUPPORTED	"<not supported>"
 #define CNTR_NOT_COUNTED	"<not counted>"
@@ -27,15 +27,21 @@
 static void print_running(struct perf_stat_config *config,
 			  u64 run, u64 ena)
 {
-	if (config->csv_output) {
-		fprintf(config->output, "%s%" PRIu64 "%s%.2f",
-					config->csv_sep,
-					run,
-					config->csv_sep,
-					ena ? 100.0 * run / ena : 100.0);
-	} else if (run != ena) {
+
+	double enabled_percent = 100;
+
+	if (run != ena)
+		enabled_percent = 100 * run / ena;
+	if (config->json_output)
+		fprintf(config->output,
+						"\"event-runtime\" : %lu, \"pcnt-running\" : %.2f, ",
+						 run, enabled_percent);
+	else if (config->csv_output)
+		fprintf(config->output,
+						"%s%" PRIu64 "%s%.2f", config->csv_sep,
+						 run, config->csv_sep, enabled_percent);
+	else if (run != ena)
 		fprintf(config->output, "  (%.2f%%)", 100.0 * run / ena);
-	}
 }
 
 static void print_noise_pct(struct perf_stat_config *config,
@@ -43,7 +49,9 @@ static void print_noise_pct(struct perf_stat_config *config,
 {
 	double pct = rel_stddev_stats(total, avg);
 
-	if (config->csv_output)
+	if (config->json_output)
+		fprintf(config->output, "\"variance\" : %.2f, ", pct);
+	else if (config->csv_output)
 		fprintf(config->output, "%s%.2f%%", config->csv_sep, pct);
 	else if (pct)
 		fprintf(config->output, "  ( +-%6.2f%% )", pct);
@@ -63,9 +71,16 @@ static void print_noise(struct perf_stat_config *config,
 
 static void print_cgroup(struct perf_stat_config *config, struct evsel *evsel)
 {
-	if (nr_cgroups) {
+	if (config->json_output && nr_cgroups) {
 		const char *cgrp_name = evsel->cgrp ? evsel->cgrp->name  : "";
+
+		fprintf(config->output, "\"cgroup\" : \"%s\", ", cgrp_name);
+	} else {
+		if (nr_cgroups) {
+			const char *cgrp_name = evsel->cgrp ? evsel->cgrp->name  : "";
+
 		fprintf(config->output, "%s%s", config->csv_sep, cgrp_name);
+		}
 	}
 }
 
@@ -73,8 +88,20 @@ static void print_cgroup(struct perf_stat_config *config, struct evsel *evsel)
 static void aggr_printout(struct perf_stat_config *config,
 			  struct evsel *evsel, struct aggr_cpu_id id, int nr)
 {
+
+
+	if (config->json_output && !config->interval)
+		fprintf(config->output, "{");
+
 	switch (config->aggr_mode) {
 	case AGGR_CORE:
+		if (config->json_output) {
+			fprintf(config->output, "\"core\" : \"S%d-D%d-C%d\", \"cpu\" : %d, ",
+						 id.socket,
+						 id.die,
+						 id.core,
+						 nr);
+	} else {
 		fprintf(config->output, "S%d-D%d-C%*d%s%*d%s",
 			id.socket,
 			id.die,
@@ -84,56 +111,95 @@ static void aggr_printout(struct perf_stat_config *config,
 			config->csv_output ? 0 : 4,
 			nr,
 			config->csv_sep);
+	}
 		break;
 	case AGGR_DIE:
-		fprintf(config->output, "S%d-D%*d%s%*d%s",
-			id.socket,
-			config->csv_output ? 0 : -8,
-			id.die,
-			config->csv_sep,
-			config->csv_output ? 0 : 4,
-			nr,
-			config->csv_sep);
+			if (config->json_output) {
+				fprintf(config->output, "\"die\" : \"S%d-D%d\", \"cpu\" : %d, ",
+					 id.socket,
+					 id.die,
+					 nr);
+		} else {
+			fprintf(config->output, "S%d-D%*d%s%*d%s",
+				id.socket,
+				config->csv_output ? 0 : -8,
+				id.die,
+				config->csv_sep,
+				config->csv_output ? 0 : 4,
+				nr,
+				config->csv_sep);
+		}
 		break;
 	case AGGR_SOCKET:
-		fprintf(config->output, "S%*d%s%*d%s",
-			config->csv_output ? 0 : -5,
-			id.socket,
-			config->csv_sep,
-			config->csv_output ? 0 : 4,
-			nr,
-			config->csv_sep);
+			if (config->json_output) {
+				fprintf(config->output, "\"socket\" : \"S%d\", \"number\" : %d, ",
+					 id.socket,
+					 nr);
+		} else {
+			fprintf(config->output, "S%*d%s%*d%s",
+				config->csv_output ? 0 : -5,
+				id.socket,
+				config->csv_sep,
+				config->csv_output ? 0 : 4,
+				nr,
+				config->csv_sep);
+		}
 			break;
 	case AGGR_NODE:
-		fprintf(config->output, "N%*d%s%*d%s",
-			config->csv_output ? 0 : -5,
-			id.node,
-			config->csv_sep,
-			config->csv_output ? 0 : 4,
-			nr,
-			config->csv_sep);
+			if (config->json_output) {
+				fprintf(config->output, "\"node\" : \"N%d\", \"cpu\" : %d, ",
+					 id.node,
+					 nr);
+		} else {
+			fprintf(config->output, "N%*d%s%*d%s",
+				config->csv_output ? 0 : -5,
+				id.node,
+				config->csv_sep,
+				config->csv_output ? 0 : 4,
+				nr,
+				config->csv_sep);
+		}
 			break;
 	case AGGR_NONE:
-		if (evsel->percore && !config->percore_show_thread) {
-			fprintf(config->output, "S%d-D%d-C%*d%s",
-				id.socket,
-				id.die,
-				config->csv_output ? 0 : -3,
-				id.core, config->csv_sep);
+		if (config->json_output) {
+			if (evsel->percore && !config->percore_show_thread) {
+				fprintf(config->output, "\"core\" : \"S%d-D%d-C%d\"",
+					id.socket,
+					id.die,
+					id.core);
+			} else if (id.core > -1) {
+				fprintf(config->output, "\"cpu\" : \"%d\", ",
+					evsel__cpus(evsel)->map[id.core]);
+			}
+		} else {
+			if (evsel->percore && !config->percore_show_thread) {
+				fprintf(config->output, "S%d-D%d-C%*d%s",
+					id.socket,
+					id.die,
+					config->csv_output ? 0 : -3,
+					id.core, config->csv_sep);
 		} else if (id.core > -1) {
 			fprintf(config->output, "CPU%*d%s",
 				config->csv_output ? 0 : -7,
 				evsel__cpus(evsel)->map[id.core],
 				config->csv_sep);
-		}
+			}
+	}
+
 		break;
 	case AGGR_THREAD:
-		fprintf(config->output, "%*s-%*d%s",
-			config->csv_output ? 0 : 16,
-			perf_thread_map__comm(evsel->core.threads, id.thread),
-			config->csv_output ? 0 : -8,
-			perf_thread_map__pid(evsel->core.threads, id.thread),
-			config->csv_sep);
+			if (config->json_output) {
+				fprintf(config->output, "\"thread\" : \"%s-%d\", ",
+					 perf_thread_map__comm(evsel->core.threads, id.thread),
+					 perf_thread_map__pid(evsel->core.threads, id.thread));
+		} else {
+			fprintf(config->output, "%*s-%*d%s",
+				config->csv_output ? 0 : 16,
+				perf_thread_map__comm(evsel->core.threads, id.thread),
+				config->csv_output ? 0 : -8,
+				perf_thread_map__pid(evsel->core.threads, id.thread),
+				config->csv_sep);
+		}
 		break;
 	case AGGR_GLOBAL:
 	case AGGR_UNSET:
@@ -184,20 +250,56 @@ static void print_metric_std(struct perf_stat_config *config,
 
 	os->newline = false;
 
+		if (unit == NULL || fmt == NULL) {
+			fprintf(out, "%-*s", METRIC_LEN, "");
+			return;
+		}
+
+		if (newline)
+			do_new_line_std(config, os);
+
+		n = fprintf(out, " # ");
+		if (color)
+			n += color_fprintf(out, color, fmt, val);
+		else
+			n += fprintf(out, fmt, val);
+		fprintf(out, " %-*s", METRIC_LEN - n - 1, unit);
+}
+
+static void print_metric_csv(struct perf_stat_config *config __maybe_unused,
+			     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;
+
 	if (unit == NULL || fmt == NULL) {
-		fprintf(out, "%-*s", METRIC_LEN, "");
+		fprintf(out, "%s%s", config->csv_sep, config->csv_sep);
 		return;
 	}
+	snprintf(buf, sizeof(buf), fmt, val);
+	ends = vals = skip_spaces(buf);
+	while (isdigit(*ends) || *ends == '.')
+		ends++;
+	*ends = 0;
+	fprintf(out, "%s%s%s%s", config->csv_sep, vals,
+					 config->csv_sep, skip_spaces(unit));
+}
 
-	if (newline)
-		do_new_line_std(config, os);
+static void print_metric_json(struct perf_stat_config *config __maybe_unused,
+			     void *ctx,
+			     const char *color __maybe_unused,
+			     const char *fmt, const char *unit, double val)
+{
+	struct outstate *os = ctx;
+	FILE *out = os->fh;
 
-	n = fprintf(out, " # ");
-	if (color)
-		n += color_fprintf(out, color, fmt, val);
-	else
-		n += fprintf(out, fmt, val);
-	fprintf(out, " %-*s", METRIC_LEN - n - 1, unit);
+	fprintf(out, "\"metric-value\" : %f, ", val);
+	fprintf(out, "\"metric-unit\" : \"%s\"", unit);
+	if (!config->metric_only)
+		fprintf(out, "}");
 }
 
 static void new_line_csv(struct perf_stat_config *config, void *ctx)
@@ -213,25 +315,14 @@ static void new_line_csv(struct perf_stat_config *config, void *ctx)
 		fputs(config->csv_sep, os->fh);
 }
 
-static void print_metric_csv(struct perf_stat_config *config __maybe_unused,
-			     void *ctx,
-			     const char *color __maybe_unused,
-			     const char *fmt, const char *unit, double val)
+static void new_line_json(struct perf_stat_config *config, void *ctx)
 {
 	struct outstate *os = ctx;
-	FILE *out = os->fh;
-	char buf[64], *vals, *ends;
 
-	if (unit == NULL || fmt == NULL) {
-		fprintf(out, "%s%s", config->csv_sep, config->csv_sep);
-		return;
-	}
-	snprintf(buf, sizeof(buf), fmt, val);
-	ends = vals = skip_spaces(buf);
-	while (isdigit(*ends) || *ends == '.')
-		ends++;
-	*ends = 0;
-	fprintf(out, "%s%s%s%s", config->csv_sep, vals, config->csv_sep, skip_spaces(unit));
+	fputc('\n', os->fh);
+	if (os->prefix)
+		fprintf(os->fh, "%s", os->prefix);
+	aggr_printout(config, os->evsel, os->id, os->nr);
 }
 
 /* Filter out some columns that don't work well in metrics only mode */
@@ -292,7 +383,7 @@ static void print_metric_only_csv(struct perf_stat_config *config __maybe_unused
 	if (!valid_only_metric(unit))
 		return;
 	unit = fixunit(tbuf, os->evsel, unit);
-	snprintf(buf, sizeof buf, fmt, val);
+	snprintf(buf, sizeof(buf), fmt, val);
 	ends = vals = skip_spaces(buf);
 	while (isdigit(*ends) || *ends == '.')
 		ends++;
@@ -300,6 +391,27 @@ static void print_metric_only_csv(struct perf_stat_config *config __maybe_unused
 	fprintf(out, "%s%s", vals, config->csv_sep);
 }
 
+static void print_metric_only_json(struct perf_stat_config *config __maybe_unused,
+				  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);
+	ends = vals = skip_spaces(buf);
+	while (isdigit(*ends) || *ends == '.')
+		ends++;
+	*ends = 0;
+	fprintf(out, "{\"metric-value\" : \"%s\"}", vals);
+}
+
 static void new_line_metric(struct perf_stat_config *config __maybe_unused,
 			    void *ctx __maybe_unused)
 {
@@ -318,10 +430,13 @@ static void print_metric_header(struct perf_stat_config *config,
 	    os->evsel->priv != os->evsel->evlist->selected->priv)
 		return;
 
-	if (!valid_only_metric(unit))
+	if (!valid_only_metric(unit) && !config->json_output)
 		return;
 	unit = fixunit(tbuf, os->evsel, unit);
-	if (config->csv_output)
+
+	if (config->json_output)
+		fprintf(os->fh, "\"unit\" : \"%s\"", unit);
+	else if (config->csv_output)
 		fprintf(os->fh, "%s%s", unit, config->csv_sep);
 	else
 		fprintf(os->fh, "%*s ", config->metric_only_len, unit);
@@ -369,14 +484,28 @@ static void abs_printout(struct perf_stat_config *config,
 
 	aggr_printout(config, evsel, id, nr);
 
-	fprintf(output, fmt, avg, config->csv_sep);
+	if (config->json_output)
+		fprintf(output, "\"counter-value\" : \"%f\", ", avg);
+	else
+		fprintf(output, fmt, avg, config->csv_sep);
 
-	if (evsel->unit)
-		fprintf(output, "%-*s%s",
-			config->csv_output ? 0 : config->unit_width,
-			evsel->unit, config->csv_sep);
+	if (config->json_output) {
+		if (evsel->unit) {
+			fprintf(output, "\"unit\" : \"%s\", ",
+				evsel->unit);
+		}
+	} else {
+		if (evsel->unit)
+			fprintf(output, "%-*s%s",
+				config->csv_output ? 0 : config->unit_width,
+				evsel->unit, config->csv_sep);
+	}
 
-	fprintf(output, "%-*s", config->csv_output ? 0 : 25, evsel__name(evsel));
+
+	if (config->json_output)
+		fprintf(output, "\"event\" : \"%s\", ", evsel__name(evsel));
+	else
+		fprintf(output, "%-*s", config->csv_output ? 0 : 25, evsel__name(evsel));
 
 	print_cgroup(config, evsel);
 }
@@ -425,6 +554,8 @@ static void printout(struct perf_stat_config *config, struct aggr_cpu_id id, int
 		nl = new_line_metric;
 		if (config->csv_output)
 			pm = print_metric_only_csv;
+		else if (config->json_output)
+			pm = print_metric_only_json;
 		else
 			pm = print_metric_only;
 	} else
@@ -448,6 +579,24 @@ static void printout(struct perf_stat_config *config, struct aggr_cpu_id id, int
 			os.nfields++;
 	}
 
+	if (config->json_output && !config->metric_only) {
+		static int aggr_fields[] = {
+			[AGGR_GLOBAL] = 0,
+			[AGGR_THREAD] = 1,
+			[AGGR_NONE] = 1,
+			[AGGR_SOCKET] = 2,
+			[AGGR_DIE] = 2,
+			[AGGR_CORE] = 2,
+		};
+
+		pm = print_metric_json;
+		nl = new_line_json;
+		os.nfields = 3;
+		os.nfields += aggr_fields[config->aggr_mode];
+		if (counter->cgrp)
+			os.nfields++;
+	}
+
 	if (!config->no_csv_summary && config->csv_output &&
 	    config->summary && !config->interval) {
 		fprintf(config->output, "%16s%s", "summary", config->csv_sep);
@@ -460,10 +609,15 @@ static void printout(struct perf_stat_config *config, struct aggr_cpu_id id, int
 		}
 		aggr_printout(config, counter, id, nr);
 
-		fprintf(config->output, "%*s%s",
-			config->csv_output ? 0 : 18,
-			counter->supported ? CNTR_NOT_COUNTED : CNTR_NOT_SUPPORTED,
-			config->csv_sep);
+		if (config->json_output) {
+			fprintf(config->output, "\"counter-value\" : \"%s\", ",
+					counter->supported ? CNTR_NOT_COUNTED : CNTR_NOT_SUPPORTED);
+		} else {
+			fprintf(config->output, "%*s%s",
+				config->csv_output ? 0 : 18,
+				counter->supported ? CNTR_NOT_COUNTED : CNTR_NOT_SUPPORTED,
+				config->csv_sep);
+		}
 
 		if (counter->supported) {
 			if (!evlist__has_hybrid(counter->evlist)) {
@@ -473,21 +627,32 @@ static void printout(struct perf_stat_config *config, struct aggr_cpu_id id, int
 			}
 		}
 
-		fprintf(config->output, "%-*s%s",
-			config->csv_output ? 0 : config->unit_width,
-			counter->unit, config->csv_sep);
+		if (config->json_output) {
+			fprintf(config->output, "\"unit\" : \"%s\", ", counter->unit);
+		} else {
+			fprintf(config->output, "%-*s%s",
+				config->csv_output ? 0 : config->unit_width,
+				counter->unit, config->csv_sep);
+		}
 
-		fprintf(config->output, "%*s",
-			config->csv_output ? 0 : -25, evsel__name(counter));
+		if (config->json_output) {
+			fprintf(config->output, "\"event\" : \"%s\", ",
+				evsel__name(counter));
+		} else {
+			fprintf(config->output, "%*s",
+				 config->csv_output ? 0 : -25, evsel__name(counter));
+		}
 
 		print_cgroup(config, counter);
 
-		if (!config->csv_output)
+		if (!config->csv_output && !config->json_output)
 			pm(config, &os, NULL, NULL, "", 0);
 		print_noise(config, counter, noise);
 		print_running(config, run, ena);
 		if (config->csv_output)
 			pm(config, &os, NULL, NULL, "", 0);
+		else if (config->json_output)
+			pm(config, &os, NULL, NULL, "", 0);
 		return;
 	}
 
@@ -502,12 +667,15 @@ static void printout(struct perf_stat_config *config, struct aggr_cpu_id id, int
 	if (config->csv_output && !config->metric_only) {
 		print_noise(config, counter, noise);
 		print_running(config, run, ena);
+	} else if (config->json_output && !config->metric_only) {
+		print_noise(config, counter, noise);
+		print_running(config, run, ena);
 	}
 
 	perf_stat__print_shadow_stats(config, counter, uval,
 				first_shadow_cpu(config, counter, id),
 				&out, &config->metric_events, st);
-	if (!config->csv_output && !config->metric_only) {
+	if (!config->csv_output && !config->metric_only && !config->json_output) {
 		print_noise(config, counter, noise);
 		print_running(config, run, ena);
 	}
@@ -866,7 +1034,8 @@ static void print_counter_aggr(struct perf_stat_config *config,
 		fprintf(output, "%s", prefix);
 
 	uval = cd.avg * counter->scale;
-	printout(config, cpu_map__empty_aggr_cpu_id(), 0, counter, uval, prefix, cd.avg_running,
+	printout(config, cpu_map__empty_aggr_cpu_id(), 0, counter,
+		 uval, prefix, cd.avg_running,
 		 cd.avg_enabled, cd.avg, &rt_stat);
 	if (!metric_only)
 		fprintf(output, "\n");
@@ -981,8 +1150,12 @@ static void print_metric_headers(struct perf_stat_config *config,
 	struct outstate os = {
 		.fh = config->output
 	};
+	bool first = true;
 
-	if (prefix)
+		if (config->json_output && !config->interval)
+			fprintf(config->output, "{");
+
+	if (prefix && !config->json_output)
 		fprintf(config->output, "%s", prefix);
 
 	if (!config->csv_output && !no_indent)
@@ -1002,6 +1175,9 @@ static void print_metric_headers(struct perf_stat_config *config,
 		os.evsel = counter;
 		out.ctx = &os;
 		out.print_metric = print_metric_header;
+		if (!first && config->json_output)
+			fprintf(config->output, ", ");
+		first = false;
 		out.new_line = new_line_metric;
 		out.force_header = true;
 		perf_stat__print_shadow_stats(config, counter, 0,
@@ -1010,6 +1186,8 @@ static void print_metric_headers(struct perf_stat_config *config,
 					      &config->metric_events,
 					      &rt_stat);
 	}
+	if (config->json_output)
+		fprintf(config->output, "}");
 	fputc('\n', config->output);
 }
 
@@ -1025,10 +1203,18 @@ static void print_interval(struct perf_stat_config *config,
 	if (config->interval_clear)
 		puts(CONSOLE_CLEAR);
 
-	if (!config->iostat_run)
-		sprintf(prefix, "%6lu.%09lu%s", (unsigned long) ts->tv_sec, ts->tv_nsec, config->csv_sep);
-
-	if ((num_print_interval == 0 && !config->csv_output) || config->interval_clear) {
+	if (!config->iostat_run && !config->json_output)
+		sprintf(prefix, "%6lu.%09lu%s", (unsigned long) ts->tv_sec,
+				 ts->tv_nsec, config->csv_sep);
+	if (!config->iostat_run && config->json_output && !config->metric_only)
+		sprintf(prefix, "{\"interval\" : %lu.%09lu, ", (unsigned long)
+				 ts->tv_sec, ts->tv_nsec);
+	if (!config->iostat_run && config->json_output && config->metric_only)
+		sprintf(prefix, "{\"interval\" : %lu.%09lu}", (unsigned long)
+				 ts->tv_sec, ts->tv_nsec);
+
+	if ((num_print_interval == 0 && !config->csv_output && !config->json_output)
+			 || config->interval_clear) {
 		switch (config->aggr_mode) {
 		case AGGR_NODE:
 			fprintf(output, "#           time node   cpus");
@@ -1072,8 +1258,14 @@ static void print_interval(struct perf_stat_config *config,
 		}
 	}
 
-	if ((num_print_interval == 0 || config->interval_clear) && metric_only)
+	if ((num_print_interval == 0 || config->interval_clear)
+			 && metric_only && !config->json_output)
+		print_metric_headers(config, evlist, " ", true);
+	if ((num_print_interval == 0 || config->interval_clear)
+			 && metric_only && config->json_output) {
+		fprintf(output, "{");
 		print_metric_headers(config, evlist, " ", true);
+	}
 	if (++num_print_interval == 25)
 		num_print_interval = 0;
 }
@@ -1087,7 +1279,7 @@ static void print_header(struct perf_stat_config *config,
 
 	fflush(stdout);
 
-	if (!config->csv_output) {
+	if (!config->csv_output && !config->json_output) {
 		fprintf(output, "\n");
 		fprintf(output, " Performance counter stats for ");
 		if (_target->bpf_str)
@@ -1276,6 +1468,9 @@ void evlist__print_counters(struct evlist *evlist, struct perf_stat_config *conf
 			num_print_iv = 0;
 		if (config->aggr_mode == AGGR_GLOBAL && prefix && !config->iostat_run)
 			fprintf(config->output, "%s", prefix);
+
+		if (config->json_output && !config->metric_only)
+			fprintf(config->output, "}");
 	}
 
 	switch (config->aggr_mode) {
@@ -1319,7 +1514,7 @@ void evlist__print_counters(struct evlist *evlist, struct perf_stat_config *conf
 		break;
 	}
 
-	if (!interval && !config->csv_output)
+	if (!interval && !config->csv_output && !config->json_output)
 		print_footer(config);
 
 	fflush(config->output);
diff --git a/tools/perf/util/stat-shadow.c b/tools/perf/util/stat-shadow.c
index 34a7f5c1fff7..8a04b6433591 100644
--- a/tools/perf/util/stat-shadow.c
+++ b/tools/perf/util/stat-shadow.c
@@ -981,8 +981,9 @@ void perf_stat__print_shadow_stats(struct perf_stat_config *config,
 		total = max(total, runtime_stat_avg(st,
 						    STAT_STALLED_CYCLES_BACK,
 						    cpu, &rsd));
-
-		if (total && avg) {
+// TODO: Refactor code to avoid errors in JSON output b/194436171
+// !config->json_output is temporary workaround for JSON linter.
+		if (total && avg && !config->json_output) {
 			out->new_line(config, ctxp);
 			ratio = total / avg;
 			print_metric(config, ctxp, NULL, "%7.2f ",
diff --git a/tools/perf/util/stat.h b/tools/perf/util/stat.h
index 32c8527de347..2bde525e7630 100644
--- a/tools/perf/util/stat.h
+++ b/tools/perf/util/stat.h
@@ -117,6 +117,7 @@ struct perf_stat_config {
 	bool			 no_inherit;
 	bool			 identifier;
 	bool			 csv_output;
+	bool			 json_output;
 	bool			 interval_clear;
 	bool			 metric_only;
 	bool			 null_run;
-- 
2.32.0.605.g8dce9f2422-goog


^ permalink raw reply related	[flat|nested] 2+ messages in thread

* Re: [PATCH 1/1] perf stat: Add JSON output option and testing.
  2021-08-11 22:43 [PATCH 1/1] perf stat: Add JSON output option and testing Claire Jensen
@ 2021-08-12 18:23 ` Arnaldo Carvalho de Melo
  0 siblings, 0 replies; 2+ messages in thread
From: Arnaldo Carvalho de Melo @ 2021-08-12 18:23 UTC (permalink / raw)
  To: Claire Jensen
  Cc: peterz, mingo, mark.rutland, alexander.shishkin, jolsa, namhyung,
	yao.jin, song, andi, adrian.hunter, kan.liang, james.clark,
	alexander.antonov, changbin.du, liuqi115, irogers, eranian,
	linux-kernel, linux-perf-users

Em Wed, Aug 11, 2021 at 10:43:18PM +0000, Claire Jensen escreveu:
<SNIP>
> +++ b/tools/perf/util/stat-display.c
> @@ -1,25 +1,25 @@
>  #include <stdlib.h>
>  #include <stdio.h>
>  #include <inttypes.h>
> -#include <linux/string.h>
> -#include <linux/time64.h>
> +#include "third_party/linux_tools/src/tools/include/linux/string.h"
> +#include "third_party/linux_tools/src/tools/include/linux/time64.h"
>  #include <math.h>
> -#include "color.h"
> -#include "counts.h"
> -#include "evlist.h"
> -#include "evsel.h"
> -#include "stat.h"
> -#include "top.h"
> -#include "thread_map.h"
> -#include "cpumap.h"
> -#include "string2.h"
> -#include <linux/ctype.h>
> -#include "cgroup.h"
> -#include <api/fs/fs.h>
> -#include "util.h"
> -#include "iostat.h"
> -#include "pmu-hybrid.h"
> -#include "evlist-hybrid.h"
> +#include "third_party/linux_tools/src/tools/perf/util/color.h"
> +#include "third_party/linux_tools/src/tools/perf/util/counts.h"
> +#include "third_party/linux_tools/src/tools/perf/util/evlist.h"
> +#include "third_party/linux_tools/src/tools/perf/util/evsel.h"
> +#include "third_party/linux_tools/src/tools/perf/util/stat.h"
> +#include "third_party/linux_tools/src/tools/perf/util/top.h"
> +#include "third_party/linux_tools/src/tools/perf/util/thread_map.h"
> +#include "third_party/linux_tools/src/tools/perf/util/cpumap.h"
> +#include "third_party/linux_tools/src/tools/perf/util/string2.h"
> +#include "third_party/linux_tools/src/tools/include/linux/ctype.h"
> +#include "third_party/linux_tools/src/tools/perf/util/cgroup.h"
> +#include "third_party/linux_tools/src/tools/lib/api/fs/fs.h"
> +#include "third_party/linux_tools/src/tools/perf/util/util.h"
> +#include "third_party/linux_tools/src/tools/perf/util/iostat.h"
> +#include "third_party/linux_tools/src/tools/perf/util/pmu-hybrid.h"
> +#include "third_party/linux_tools/src/tools/perf/util/evlist-hybrid.h"

Oops, these shouldn'be here, right? :-)

Some comments: Please separate the 'perf test' entry from the feature
being introduced, also align 'TODO' entries in the if block and remove
that b/somenumber (what is that for?)

Pay attention to indentation, you removed from several if blocks.

More below

Thanks for working on this, I hope the 'stat' guys can test this and
provide feedback.

- Arnaldo
  
>  #define CNTR_NOT_SUPPORTED	"<not supported>"
>  #define CNTR_NOT_COUNTED	"<not counted>"
> @@ -27,15 +27,21 @@
>  static void print_running(struct perf_stat_config *config,
>  			  u64 run, u64 ena)
>  {
> -	if (config->csv_output) {
> -		fprintf(config->output, "%s%" PRIu64 "%s%.2f",
> -					config->csv_sep,
> -					run,
> -					config->csv_sep,
> -					ena ? 100.0 * run / ena : 100.0);
> -	} else if (run != ena) {
> +

Please no blank line after { and the variables

> +	double enabled_percent = 100;
> +
> +	if (run != ena)
> +		enabled_percent = 100 * run / ena;
> +	if (config->json_output)
> +		fprintf(config->output,
> +						"\"event-runtime\" : %lu, \"pcnt-running\" : %.2f, ",

Why this newline before the format string?

> +						 run, enabled_percent);
> +	else if (config->csv_output)
> +		fprintf(config->output,
> +						"%s%" PRIu64 "%s%.2f", config->csv_sep,
> +						 run, config->csv_sep, enabled_percent);
> +	else if (run != ena)
>  		fprintf(config->output, "  (%.2f%%)", 100.0 * run / ena);
> -	}
>  }
>  
>  static void print_noise_pct(struct perf_stat_config *config,
> @@ -43,7 +49,9 @@ static void print_noise_pct(struct perf_stat_config *config,
>  {
>  	double pct = rel_stddev_stats(total, avg);
>  
> -	if (config->csv_output)
> +	if (config->json_output)
> +		fprintf(config->output, "\"variance\" : %.2f, ", pct);
> +	else if (config->csv_output)
>  		fprintf(config->output, "%s%.2f%%", config->csv_sep, pct);
>  	else if (pct)
>  		fprintf(config->output, "  ( +-%6.2f%% )", pct);
> @@ -63,9 +71,16 @@ static void print_noise(struct perf_stat_config *config,
>  
>  static void print_cgroup(struct perf_stat_config *config, struct evsel *evsel)
>  {
> -	if (nr_cgroups) {
> +	if (config->json_output && nr_cgroups) {
>  		const char *cgrp_name = evsel->cgrp ? evsel->cgrp->name  : "";
> +
> +		fprintf(config->output, "\"cgroup\" : \"%s\", ", cgrp_name);
> +	} else {
> +		if (nr_cgroups) {
> +			const char *cgrp_name = evsel->cgrp ? evsel->cgrp->name  : "";
> +
>  		fprintf(config->output, "%s%s", config->csv_sep, cgrp_name);
> +		}
>  	}
>  }
>  
> @@ -73,8 +88,20 @@ static void print_cgroup(struct perf_stat_config *config, struct evsel *evsel)
>  static void aggr_printout(struct perf_stat_config *config,
>  			  struct evsel *evsel, struct aggr_cpu_id id, int nr)
>  {
> +
> +
> +	if (config->json_output && !config->interval)
> +		fprintf(config->output, "{");
> +
>  	switch (config->aggr_mode) {
>  	case AGGR_CORE:
> +		if (config->json_output) {
> +			fprintf(config->output, "\"core\" : \"S%d-D%d-C%d\", \"cpu\" : %d, ",
> +						 id.socket,
> +						 id.die,
> +						 id.core,
> +						 nr);
> +	} else {

Indentation, align if/else

>  		fprintf(config->output, "S%d-D%d-C%*d%s%*d%s",
>  			id.socket,
>  			id.die,
> @@ -84,56 +111,95 @@ static void aggr_printout(struct perf_stat_config *config,
>  			config->csv_output ? 0 : 4,
>  			nr,
>  			config->csv_sep);
> +	}
>  		break;
>  	case AGGR_DIE:
> -		fprintf(config->output, "S%d-D%*d%s%*d%s",
> -			id.socket,
> -			config->csv_output ? 0 : -8,
> -			id.die,
> -			config->csv_sep,
> -			config->csv_output ? 0 : 4,
> -			nr,
> -			config->csv_sep);
> +			if (config->json_output) {
> +				fprintf(config->output, "\"die\" : \"S%d-D%d\", \"cpu\" : %d, ",
> +					 id.socket,
> +					 id.die,
> +					 nr);
> +		} else {
> +			fprintf(config->output, "S%d-D%*d%s%*d%s",
> +				id.socket,
> +				config->csv_output ? 0 : -8,
> +				id.die,
> +				config->csv_sep,
> +				config->csv_output ? 0 : 4,
> +				nr,
> +				config->csv_sep);
> +		}
>  		break;
>  	case AGGR_SOCKET:
> -		fprintf(config->output, "S%*d%s%*d%s",
> -			config->csv_output ? 0 : -5,
> -			id.socket,
> -			config->csv_sep,
> -			config->csv_output ? 0 : 4,
> -			nr,
> -			config->csv_sep);
> +			if (config->json_output) {
> +				fprintf(config->output, "\"socket\" : \"S%d\", \"number\" : %d, ",
> +					 id.socket,
> +					 nr);
> +		} else {
> +			fprintf(config->output, "S%*d%s%*d%s",
> +				config->csv_output ? 0 : -5,
> +				id.socket,
> +				config->csv_sep,
> +				config->csv_output ? 0 : 4,
> +				nr,
> +				config->csv_sep);
> +		}
>  			break;
>  	case AGGR_NODE:
> -		fprintf(config->output, "N%*d%s%*d%s",
> -			config->csv_output ? 0 : -5,
> -			id.node,
> -			config->csv_sep,
> -			config->csv_output ? 0 : 4,
> -			nr,
> -			config->csv_sep);
> +			if (config->json_output) {
> +				fprintf(config->output, "\"node\" : \"N%d\", \"cpu\" : %d, ",
> +					 id.node,
> +					 nr);
> +		} else {
> +			fprintf(config->output, "N%*d%s%*d%s",
> +				config->csv_output ? 0 : -5,
> +				id.node,
> +				config->csv_sep,
> +				config->csv_output ? 0 : 4,
> +				nr,
> +				config->csv_sep);
> +		}
>  			break;
>  	case AGGR_NONE:
> -		if (evsel->percore && !config->percore_show_thread) {
> -			fprintf(config->output, "S%d-D%d-C%*d%s",
> -				id.socket,
> -				id.die,
> -				config->csv_output ? 0 : -3,
> -				id.core, config->csv_sep);
> +		if (config->json_output) {
> +			if (evsel->percore && !config->percore_show_thread) {
> +				fprintf(config->output, "\"core\" : \"S%d-D%d-C%d\"",
> +					id.socket,
> +					id.die,
> +					id.core);
> +			} else if (id.core > -1) {
> +				fprintf(config->output, "\"cpu\" : \"%d\", ",
> +					evsel__cpus(evsel)->map[id.core]);
> +			}
> +		} else {
> +			if (evsel->percore && !config->percore_show_thread) {
> +				fprintf(config->output, "S%d-D%d-C%*d%s",
> +					id.socket,
> +					id.die,
> +					config->csv_output ? 0 : -3,
> +					id.core, config->csv_sep);
>  		} else if (id.core > -1) {
>  			fprintf(config->output, "CPU%*d%s",
>  				config->csv_output ? 0 : -7,
>  				evsel__cpus(evsel)->map[id.core],
>  				config->csv_sep);
> -		}
> +			}
> +	}
> +
>  		break;
>  	case AGGR_THREAD:
> -		fprintf(config->output, "%*s-%*d%s",
> -			config->csv_output ? 0 : 16,
> -			perf_thread_map__comm(evsel->core.threads, id.thread),
> -			config->csv_output ? 0 : -8,
> -			perf_thread_map__pid(evsel->core.threads, id.thread),
> -			config->csv_sep);
> +			if (config->json_output) {
> +				fprintf(config->output, "\"thread\" : \"%s-%d\", ",
> +					 perf_thread_map__comm(evsel->core.threads, id.thread),
> +					 perf_thread_map__pid(evsel->core.threads, id.thread));
> +		} else {
> +			fprintf(config->output, "%*s-%*d%s",
> +				config->csv_output ? 0 : 16,
> +				perf_thread_map__comm(evsel->core.threads, id.thread),
> +				config->csv_output ? 0 : -8,
> +				perf_thread_map__pid(evsel->core.threads, id.thread),
> +				config->csv_sep);
> +		}
>  		break;
>  	case AGGR_GLOBAL:
>  	case AGGR_UNSET:
> @@ -184,20 +250,56 @@ static void print_metric_std(struct perf_stat_config *config,
>  
>  	os->newline = false;
>  
> +		if (unit == NULL || fmt == NULL) {
> +			fprintf(out, "%-*s", METRIC_LEN, "");
> +			return;
> +		}
> +
> +		if (newline)
> +			do_new_line_std(config, os);
> +
> +		n = fprintf(out, " # ");
> +		if (color)
> +			n += color_fprintf(out, color, fmt, val);
> +		else
> +			n += fprintf(out, fmt, val);
> +		fprintf(out, " %-*s", METRIC_LEN - n - 1, unit);
> +}
> +
> +static void print_metric_csv(struct perf_stat_config *config __maybe_unused,
> +			     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;
> +
>  	if (unit == NULL || fmt == NULL) {
> -		fprintf(out, "%-*s", METRIC_LEN, "");
> +		fprintf(out, "%s%s", config->csv_sep, config->csv_sep);
>  		return;
>  	}
> +	snprintf(buf, sizeof(buf), fmt, val);
> +	ends = vals = skip_spaces(buf);
> +	while (isdigit(*ends) || *ends == '.')
> +		ends++;
> +	*ends = 0;
> +	fprintf(out, "%s%s%s%s", config->csv_sep, vals,
> +					 config->csv_sep, skip_spaces(unit));
> +}
>  
> -	if (newline)
> -		do_new_line_std(config, os);
> +static void print_metric_json(struct perf_stat_config *config __maybe_unused,
> +			     void *ctx,
> +			     const char *color __maybe_unused,
> +			     const char *fmt, const char *unit, double val)
> +{
> +	struct outstate *os = ctx;
> +	FILE *out = os->fh;
>  
> -	n = fprintf(out, " # ");
> -	if (color)
> -		n += color_fprintf(out, color, fmt, val);
> -	else
> -		n += fprintf(out, fmt, val);
> -	fprintf(out, " %-*s", METRIC_LEN - n - 1, unit);
> +	fprintf(out, "\"metric-value\" : %f, ", val);
> +	fprintf(out, "\"metric-unit\" : \"%s\"", unit);
> +	if (!config->metric_only)
> +		fprintf(out, "}");
>  }
>  
>  static void new_line_csv(struct perf_stat_config *config, void *ctx)
> @@ -213,25 +315,14 @@ static void new_line_csv(struct perf_stat_config *config, void *ctx)
>  		fputs(config->csv_sep, os->fh);
>  }
>  
> -static void print_metric_csv(struct perf_stat_config *config __maybe_unused,
> -			     void *ctx,
> -			     const char *color __maybe_unused,
> -			     const char *fmt, const char *unit, double val)
> +static void new_line_json(struct perf_stat_config *config, void *ctx)
>  {
>  	struct outstate *os = ctx;
> -	FILE *out = os->fh;
> -	char buf[64], *vals, *ends;
>  
> -	if (unit == NULL || fmt == NULL) {
> -		fprintf(out, "%s%s", config->csv_sep, config->csv_sep);
> -		return;
> -	}
> -	snprintf(buf, sizeof(buf), fmt, val);
> -	ends = vals = skip_spaces(buf);
> -	while (isdigit(*ends) || *ends == '.')
> -		ends++;
> -	*ends = 0;
> -	fprintf(out, "%s%s%s%s", config->csv_sep, vals, config->csv_sep, skip_spaces(unit));
> +	fputc('\n', os->fh);
> +	if (os->prefix)
> +		fprintf(os->fh, "%s", os->prefix);
> +	aggr_printout(config, os->evsel, os->id, os->nr);
>  }
>  
>  /* Filter out some columns that don't work well in metrics only mode */
> @@ -292,7 +383,7 @@ static void print_metric_only_csv(struct perf_stat_config *config __maybe_unused
>  	if (!valid_only_metric(unit))
>  		return;
>  	unit = fixunit(tbuf, os->evsel, unit);
> -	snprintf(buf, sizeof buf, fmt, val);
> +	snprintf(buf, sizeof(buf), fmt, val);
>  	ends = vals = skip_spaces(buf);
>  	while (isdigit(*ends) || *ends == '.')
>  		ends++;
> @@ -300,6 +391,27 @@ static void print_metric_only_csv(struct perf_stat_config *config __maybe_unused
>  	fprintf(out, "%s%s", vals, config->csv_sep);
>  }
>  
> +static void print_metric_only_json(struct perf_stat_config *config __maybe_unused,
> +				  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);
> +	ends = vals = skip_spaces(buf);
> +	while (isdigit(*ends) || *ends == '.')
> +		ends++;
> +	*ends = 0;
> +	fprintf(out, "{\"metric-value\" : \"%s\"}", vals);
> +}
> +
>  static void new_line_metric(struct perf_stat_config *config __maybe_unused,
>  			    void *ctx __maybe_unused)
>  {
> @@ -318,10 +430,13 @@ static void print_metric_header(struct perf_stat_config *config,
>  	    os->evsel->priv != os->evsel->evlist->selected->priv)
>  		return;
>  
> -	if (!valid_only_metric(unit))
> +	if (!valid_only_metric(unit) && !config->json_output)
>  		return;
>  	unit = fixunit(tbuf, os->evsel, unit);
> -	if (config->csv_output)
> +
> +	if (config->json_output)
> +		fprintf(os->fh, "\"unit\" : \"%s\"", unit);
> +	else if (config->csv_output)
>  		fprintf(os->fh, "%s%s", unit, config->csv_sep);
>  	else
>  		fprintf(os->fh, "%*s ", config->metric_only_len, unit);
> @@ -369,14 +484,28 @@ static void abs_printout(struct perf_stat_config *config,
>  
>  	aggr_printout(config, evsel, id, nr);
>  
> -	fprintf(output, fmt, avg, config->csv_sep);
> +	if (config->json_output)
> +		fprintf(output, "\"counter-value\" : \"%f\", ", avg);
> +	else
> +		fprintf(output, fmt, avg, config->csv_sep);
>  
> -	if (evsel->unit)
> -		fprintf(output, "%-*s%s",
> -			config->csv_output ? 0 : config->unit_width,
> -			evsel->unit, config->csv_sep);
> +	if (config->json_output) {
> +		if (evsel->unit) {
> +			fprintf(output, "\"unit\" : \"%s\", ",
> +				evsel->unit);
> +		}
> +	} else {
> +		if (evsel->unit)
> +			fprintf(output, "%-*s%s",
> +				config->csv_output ? 0 : config->unit_width,
> +				evsel->unit, config->csv_sep);
> +	}
>  
> -	fprintf(output, "%-*s", config->csv_output ? 0 : 25, evsel__name(evsel));
> +
> +	if (config->json_output)
> +		fprintf(output, "\"event\" : \"%s\", ", evsel__name(evsel));
> +	else
> +		fprintf(output, "%-*s", config->csv_output ? 0 : 25, evsel__name(evsel));
>  
>  	print_cgroup(config, evsel);
>  }
> @@ -425,6 +554,8 @@ static void printout(struct perf_stat_config *config, struct aggr_cpu_id id, int
>  		nl = new_line_metric;
>  		if (config->csv_output)
>  			pm = print_metric_only_csv;
> +		else if (config->json_output)
> +			pm = print_metric_only_json;
>  		else
>  			pm = print_metric_only;
>  	} else
> @@ -448,6 +579,24 @@ static void printout(struct perf_stat_config *config, struct aggr_cpu_id id, int
>  			os.nfields++;
>  	}
>  
> +	if (config->json_output && !config->metric_only) {
> +		static int aggr_fields[] = {
> +			[AGGR_GLOBAL] = 0,
> +			[AGGR_THREAD] = 1,
> +			[AGGR_NONE] = 1,
> +			[AGGR_SOCKET] = 2,
> +			[AGGR_DIE] = 2,
> +			[AGGR_CORE] = 2,
> +		};
> +
> +		pm = print_metric_json;
> +		nl = new_line_json;
> +		os.nfields = 3;
> +		os.nfields += aggr_fields[config->aggr_mode];
> +		if (counter->cgrp)
> +			os.nfields++;
> +	}
> +
>  	if (!config->no_csv_summary && config->csv_output &&
>  	    config->summary && !config->interval) {
>  		fprintf(config->output, "%16s%s", "summary", config->csv_sep);
> @@ -460,10 +609,15 @@ static void printout(struct perf_stat_config *config, struct aggr_cpu_id id, int
>  		}
>  		aggr_printout(config, counter, id, nr);
>  
> -		fprintf(config->output, "%*s%s",
> -			config->csv_output ? 0 : 18,
> -			counter->supported ? CNTR_NOT_COUNTED : CNTR_NOT_SUPPORTED,
> -			config->csv_sep);
> +		if (config->json_output) {
> +			fprintf(config->output, "\"counter-value\" : \"%s\", ",
> +					counter->supported ? CNTR_NOT_COUNTED : CNTR_NOT_SUPPORTED);
> +		} else {
> +			fprintf(config->output, "%*s%s",
> +				config->csv_output ? 0 : 18,
> +				counter->supported ? CNTR_NOT_COUNTED : CNTR_NOT_SUPPORTED,
> +				config->csv_sep);
> +		}
>  
>  		if (counter->supported) {
>  			if (!evlist__has_hybrid(counter->evlist)) {
> @@ -473,21 +627,32 @@ static void printout(struct perf_stat_config *config, struct aggr_cpu_id id, int
>  			}
>  		}
>  
> -		fprintf(config->output, "%-*s%s",
> -			config->csv_output ? 0 : config->unit_width,
> -			counter->unit, config->csv_sep);
> +		if (config->json_output) {
> +			fprintf(config->output, "\"unit\" : \"%s\", ", counter->unit);
> +		} else {
> +			fprintf(config->output, "%-*s%s",
> +				config->csv_output ? 0 : config->unit_width,
> +				counter->unit, config->csv_sep);
> +		}
>  
> -		fprintf(config->output, "%*s",
> -			config->csv_output ? 0 : -25, evsel__name(counter));
> +		if (config->json_output) {
> +			fprintf(config->output, "\"event\" : \"%s\", ",
> +				evsel__name(counter));
> +		} else {
> +			fprintf(config->output, "%*s",
> +				 config->csv_output ? 0 : -25, evsel__name(counter));
> +		}
>  
>  		print_cgroup(config, counter);
>  
> -		if (!config->csv_output)
> +		if (!config->csv_output && !config->json_output)
>  			pm(config, &os, NULL, NULL, "", 0);
>  		print_noise(config, counter, noise);
>  		print_running(config, run, ena);
>  		if (config->csv_output)
>  			pm(config, &os, NULL, NULL, "", 0);
> +		else if (config->json_output)
> +			pm(config, &os, NULL, NULL, "", 0);
>  		return;
>  	}
>  
> @@ -502,12 +667,15 @@ static void printout(struct perf_stat_config *config, struct aggr_cpu_id id, int
>  	if (config->csv_output && !config->metric_only) {
>  		print_noise(config, counter, noise);
>  		print_running(config, run, ena);
> +	} else if (config->json_output && !config->metric_only) {
> +		print_noise(config, counter, noise);
> +		print_running(config, run, ena);
>  	}
>  
>  	perf_stat__print_shadow_stats(config, counter, uval,
>  				first_shadow_cpu(config, counter, id),
>  				&out, &config->metric_events, st);
> -	if (!config->csv_output && !config->metric_only) {
> +	if (!config->csv_output && !config->metric_only && !config->json_output) {
>  		print_noise(config, counter, noise);
>  		print_running(config, run, ena);
>  	}
> @@ -866,7 +1034,8 @@ static void print_counter_aggr(struct perf_stat_config *config,
>  		fprintf(output, "%s", prefix);
>  
>  	uval = cd.avg * counter->scale;
> -	printout(config, cpu_map__empty_aggr_cpu_id(), 0, counter, uval, prefix, cd.avg_running,
> +	printout(config, cpu_map__empty_aggr_cpu_id(), 0, counter,
> +		 uval, prefix, cd.avg_running,
>  		 cd.avg_enabled, cd.avg, &rt_stat);
>  	if (!metric_only)
>  		fprintf(output, "\n");
> @@ -981,8 +1150,12 @@ static void print_metric_headers(struct perf_stat_config *config,
>  	struct outstate os = {
>  		.fh = config->output
>  	};
> +	bool first = true;
>  
> -	if (prefix)
> +		if (config->json_output && !config->interval)
> +			fprintf(config->output, "{");
> +
> +	if (prefix && !config->json_output)
>  		fprintf(config->output, "%s", prefix);
>  
>  	if (!config->csv_output && !no_indent)
> @@ -1002,6 +1175,9 @@ static void print_metric_headers(struct perf_stat_config *config,
>  		os.evsel = counter;
>  		out.ctx = &os;
>  		out.print_metric = print_metric_header;
> +		if (!first && config->json_output)
> +			fprintf(config->output, ", ");
> +		first = false;
>  		out.new_line = new_line_metric;
>  		out.force_header = true;
>  		perf_stat__print_shadow_stats(config, counter, 0,
> @@ -1010,6 +1186,8 @@ static void print_metric_headers(struct perf_stat_config *config,
>  					      &config->metric_events,
>  					      &rt_stat);
>  	}
> +	if (config->json_output)
> +		fprintf(config->output, "}");
>  	fputc('\n', config->output);
>  }
>  
> @@ -1025,10 +1203,18 @@ static void print_interval(struct perf_stat_config *config,
>  	if (config->interval_clear)
>  		puts(CONSOLE_CLEAR);
>  
> -	if (!config->iostat_run)
> -		sprintf(prefix, "%6lu.%09lu%s", (unsigned long) ts->tv_sec, ts->tv_nsec, config->csv_sep);
> -
> -	if ((num_print_interval == 0 && !config->csv_output) || config->interval_clear) {
> +	if (!config->iostat_run && !config->json_output)
> +		sprintf(prefix, "%6lu.%09lu%s", (unsigned long) ts->tv_sec,
> +				 ts->tv_nsec, config->csv_sep);
> +	if (!config->iostat_run && config->json_output && !config->metric_only)
> +		sprintf(prefix, "{\"interval\" : %lu.%09lu, ", (unsigned long)
> +				 ts->tv_sec, ts->tv_nsec);
> +	if (!config->iostat_run && config->json_output && config->metric_only)
> +		sprintf(prefix, "{\"interval\" : %lu.%09lu}", (unsigned long)
> +				 ts->tv_sec, ts->tv_nsec);
> +
> +	if ((num_print_interval == 0 && !config->csv_output && !config->json_output)
> +			 || config->interval_clear) {
>  		switch (config->aggr_mode) {
>  		case AGGR_NODE:
>  			fprintf(output, "#           time node   cpus");
> @@ -1072,8 +1258,14 @@ static void print_interval(struct perf_stat_config *config,
>  		}
>  	}
>  
> -	if ((num_print_interval == 0 || config->interval_clear) && metric_only)
> +	if ((num_print_interval == 0 || config->interval_clear)
> +			 && metric_only && !config->json_output)
> +		print_metric_headers(config, evlist, " ", true);
> +	if ((num_print_interval == 0 || config->interval_clear)
> +			 && metric_only && config->json_output) {
> +		fprintf(output, "{");
>  		print_metric_headers(config, evlist, " ", true);
> +	}
>  	if (++num_print_interval == 25)
>  		num_print_interval = 0;
>  }
> @@ -1087,7 +1279,7 @@ static void print_header(struct perf_stat_config *config,
>  
>  	fflush(stdout);
>  
> -	if (!config->csv_output) {
> +	if (!config->csv_output && !config->json_output) {
>  		fprintf(output, "\n");
>  		fprintf(output, " Performance counter stats for ");
>  		if (_target->bpf_str)
> @@ -1276,6 +1468,9 @@ void evlist__print_counters(struct evlist *evlist, struct perf_stat_config *conf
>  			num_print_iv = 0;
>  		if (config->aggr_mode == AGGR_GLOBAL && prefix && !config->iostat_run)
>  			fprintf(config->output, "%s", prefix);
> +
> +		if (config->json_output && !config->metric_only)
> +			fprintf(config->output, "}");
>  	}
>  
>  	switch (config->aggr_mode) {
> @@ -1319,7 +1514,7 @@ void evlist__print_counters(struct evlist *evlist, struct perf_stat_config *conf
>  		break;
>  	}
>  
> -	if (!interval && !config->csv_output)
> +	if (!interval && !config->csv_output && !config->json_output)
>  		print_footer(config);
>  
>  	fflush(config->output);
> diff --git a/tools/perf/util/stat-shadow.c b/tools/perf/util/stat-shadow.c
> index 34a7f5c1fff7..8a04b6433591 100644
> --- a/tools/perf/util/stat-shadow.c
> +++ b/tools/perf/util/stat-shadow.c
> @@ -981,8 +981,9 @@ void perf_stat__print_shadow_stats(struct perf_stat_config *config,
>  		total = max(total, runtime_stat_avg(st,
>  						    STAT_STALLED_CYCLES_BACK,
>  						    cpu, &rsd));
> -
> -		if (total && avg) {
> +// TODO: Refactor code to avoid errors in JSON output b/194436171
> +// !config->json_output is temporary workaround for JSON linter.
> +		if (total && avg && !config->json_output) {
>  			out->new_line(config, ctxp);
>  			ratio = total / avg;
>  			print_metric(config, ctxp, NULL, "%7.2f ",
> diff --git a/tools/perf/util/stat.h b/tools/perf/util/stat.h
> index 32c8527de347..2bde525e7630 100644
> --- a/tools/perf/util/stat.h
> +++ b/tools/perf/util/stat.h
> @@ -117,6 +117,7 @@ struct perf_stat_config {
>  	bool			 no_inherit;
>  	bool			 identifier;
>  	bool			 csv_output;
> +	bool			 json_output;
>  	bool			 interval_clear;
>  	bool			 metric_only;
>  	bool			 null_run;
> -- 
> 2.32.0.605.g8dce9f2422-goog
> 

-- 

- Arnaldo

^ permalink raw reply	[flat|nested] 2+ messages in thread

end of thread, other threads:[~2021-08-12 18:23 UTC | newest]

Thread overview: 2+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2021-08-11 22:43 [PATCH 1/1] perf stat: Add JSON output option and testing Claire Jensen
2021-08-12 18:23 ` Arnaldo Carvalho de Melo

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).