All of lore.kernel.org
 help / color / mirror / Atom feed
From: Ian Rogers <irogers@google.com>
To: irogers@google.com, acme@kernel.org, namhyung@kernel.org,
	 ctshao@google.com
Cc: adrian.hunter@intel.com, james.clark@linaro.org,
	jolsa@kernel.org,  linux-kernel@vger.kernel.org,
	linux-perf-users@vger.kernel.org,  mingo@redhat.com,
	peterz@infradead.org
Subject: [PATCH v3 01/14] perf stat: Introduce core generic print traversal engine and header stubs
Date: Wed, 15 Jul 2026 21:32:10 -0700	[thread overview]
Message-ID: <20260716043223.388233-2-irogers@google.com> (raw)
In-Reply-To: <20260716043223.388233-1-irogers@google.com>

This patch introduces the initial infrastructure for decoupling the perf
stat printing API. It declares the struct perf_stat_print_callbacks
interface and the core traversal driver perf_stat__print_cb() inside the
newly created util/stat-print.h and util/stat-print.c files.

The generic traversal driver perf_stat__print_cb() drive traversing the
event lists across all supported cpu aggregation modes (global, die,
socket, cache, cluster, core, thread, none). It implements the clean
display filtering checks (perf_stat__skip_metric_event(), hybrid
wildcard merges) and the basic metrics allowlist filter
(is_basic_shadow_metric()) to keep formatting callbacks decoupled.

This also introduces two format-agnostic shared helpers to centralize
aggregation prefix formatting:
- perf_stat__get_aggr_key(): resolves JSON key names.
- perf_stat__get_aggr_id_char(): formats unified aggregation identifiers.

Adds empty format-specific stubs (perf_stat__print_std, _csv, _json) to
ensure that the new print files link and compile cleanly under
util/Build, without affecting the legacy print path.

Signed-off-by: Ian Rogers <irogers@google.com>
Assisted-by: Antigravity:gemini-3.5-flash

Acked-by: Chun-Tse Shao <ctshao@google.com>
---
 tools/perf/builtin-stat.c         |  96 +++---
 tools/perf/util/Build             |   4 +
 tools/perf/util/stat-display.c    |   6 +-
 tools/perf/util/stat-print-csv.c  |  13 +
 tools/perf/util/stat-print-json.c |  13 +
 tools/perf/util/stat-print-std.c  |  13 +
 tools/perf/util/stat-print.c      | 489 ++++++++++++++++++++++++++++++
 tools/perf/util/stat-print.h      | 133 ++++++++
 tools/perf/util/stat.h            |   2 +
 9 files changed, 722 insertions(+), 47 deletions(-)
 create mode 100644 tools/perf/util/stat-print-csv.c
 create mode 100644 tools/perf/util/stat-print-json.c
 create mode 100644 tools/perf/util/stat-print-std.c
 create mode 100644 tools/perf/util/stat-print.c
 create mode 100644 tools/perf/util/stat-print.h

diff --git a/tools/perf/builtin-stat.c b/tools/perf/builtin-stat.c
index 3f685beba384..532856f8be90 100644
--- a/tools/perf/builtin-stat.c
+++ b/tools/perf/builtin-stat.c
@@ -40,62 +40,64 @@
  *   Jaswinder Singh Rajput <jaswinder@kernel.org>
  */
 
+#include <errno.h>
+#include <inttypes.h>
+#include <locale.h>
+#include <math.h>
+#include <signal.h>
+#include <stdlib.h>
+
+#include <linux/ctype.h>
+#include <linux/err.h>
+#include <linux/list_sort.h>
+#include <linux/time64.h>
+#include <linux/zalloc.h>
+#include <sys/prctl.h>
+#include <sys/resource.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+#include <api/fs/fs.h>
+#include <internal/threadmap.h>
+#include <perf/evlist.h>
+#include <subcmd/parse-options.h>
+
+#include "asm/bug.h"
 #include "builtin.h"
+#include "util/affinity.h"
+#include "util/bpf_counter.h"
 #include "util/cgroup.h"
-#include <subcmd/parse-options.h>
-#include "util/parse-events.h"
-#include "util/pmus.h"
-#include "util/pmu.h"
-#include "util/tool_pmu.h"
+#include "util/color.h"
+#include "util/counts.h"
+#include "util/cpumap.h"
+#include "util/debug.h"
 #include "util/event.h"
 #include "util/evlist.h"
 #include "util/evsel.h"
-#include "util/debug.h"
-#include "util/color.h"
-#include "util/stat.h"
 #include "util/header.h"
-#include "util/cpumap.h"
-#include "util/thread_map.h"
-#include "util/counts.h"
-#include "util/topdown.h"
+#include "util/intel-tpebs.h"
+#include "util/iostat.h"
+#include "util/metricgroup.h"
+#include "util/parse-events.h"
+#include "util/pfm.h"
+#include "util/pmu.h"
+#include "util/pmus.h"
 #include "util/session.h"
-#include "util/tool.h"
+#include "util/stat-print.h"
+#include "util/stat.h"
 #include "util/string2.h"
-#include "util/metricgroup.h"
 #include "util/synthetic-events.h"
 #include "util/target.h"
+#include "util/thread_map.h"
 #include "util/time-utils.h"
+#include "util/tool.h"
+#include "util/tool_pmu.h"
 #include "util/top.h"
-#include "util/affinity.h"
-#include "util/pfm.h"
-#include "util/bpf_counter.h"
-#include "util/iostat.h"
+#include "util/topdown.h"
 #include "util/util.h"
-#include "util/intel-tpebs.h"
-#include "asm/bug.h"
-
-#include <linux/list_sort.h>
-#include <linux/time64.h>
-#include <linux/zalloc.h>
-#include <api/fs/fs.h>
-#include <errno.h>
-#include <signal.h>
-#include <stdlib.h>
-#include <sys/prctl.h>
-#include <inttypes.h>
-#include <locale.h>
-#include <math.h>
-#include <sys/types.h>
-#include <sys/stat.h>
-#include <sys/wait.h>
-#include <unistd.h>
-#include <sys/time.h>
-#include <sys/resource.h>
-#include <linux/err.h>
-
-#include <linux/ctype.h>
-#include <perf/evlist.h>
-#include <internal/threadmap.h>
 
 #ifdef HAVE_BPF_SKEL
 #include "util/bpf_skel/bperf_cgroup.h"
@@ -123,6 +125,7 @@ static struct target target;
 static volatile sig_atomic_t	child_pid			= -1;
 static int			detailed_run			=  0;
 static bool			transaction_run;
+static bool use_perf_stat_print;
 static bool			topdown_run			= false;
 static bool			smi_cost			= false;
 static bool			smi_reset			= false;
@@ -1094,7 +1097,10 @@ static void print_counters(struct timespec *ts, int argc, const char **argv)
 	if (quiet)
 		return;
 
-	evlist__print_counters(evsel_list, &stat_config, &target, ts, argc, argv);
+	if (use_perf_stat_print)
+		perf_stat__print(evsel_list, &stat_config, &target, ts, argc, argv);
+	else
+		evlist__print_counters(evsel_list, &stat_config, &target, ts, argc, argv);
 }
 
 static volatile sig_atomic_t signr = -1;
@@ -2593,6 +2599,8 @@ int cmd_stat(int argc, const char **argv)
 			"Use with 'percore' event qualifier to show the event "
 			"counts of one hardware thread by sum up total hardware "
 			"threads of same physical core"),
+		OPT_BOOLEAN(0, "new", &use_perf_stat_print,
+			"use new clean API code for display output"),
 		OPT_BOOLEAN(0, "summary", &stat_config.summary,
 			"print summary for interval mode"),
 		OPT_BOOLEAN(0, "no-csv-summary", &stat_config.no_csv_summary,
diff --git a/tools/perf/util/Build b/tools/perf/util/Build
index 330311cac550..0cf5bf625664 100644
--- a/tools/perf/util/Build
+++ b/tools/perf/util/Build
@@ -113,6 +113,10 @@ perf-util-y += counts.o
 perf-util-y += stat.o
 perf-util-y += stat-shadow.o
 perf-util-y += stat-display.o
+perf-util-y += stat-print.o
+perf-util-y += stat-print-std.o
+perf-util-y += stat-print-csv.o
+perf-util-y += stat-print-json.o
 perf-util-y += perf_api_probe.o
 perf-util-y += record.o
 perf-util-y += srcline.o
diff --git a/tools/perf/util/stat-display.c b/tools/perf/util/stat-display.c
index f94f1324d24a..d71d413e7f31 100644
--- a/tools/perf/util/stat-display.c
+++ b/tools/perf/util/stat-display.c
@@ -37,7 +37,7 @@
 #define PID_LEN       7
 #define CPUS_LEN      4
 
-static int aggr_header_lens[] = {
+const int aggr_header_lens[] = {
 	[AGGR_CORE] 	= 18,
 	[AGGR_CACHE]	= 22,
 	[AGGR_CLUSTER]	= 20,
@@ -49,7 +49,7 @@ static int aggr_header_lens[] = {
 	[AGGR_GLOBAL] 	= 0,
 };
 
-static const char *aggr_header_csv[] = {
+const char *aggr_header_csv[] = {
 	[AGGR_CORE]	=	"core,ctrs,",
 	[AGGR_CACHE]	=	"cache,ctrs,",
 	[AGGR_CLUSTER]	=	"cluster,ctrs,",
@@ -61,7 +61,7 @@ static const char *aggr_header_csv[] = {
 	[AGGR_GLOBAL]	=	""
 };
 
-static const char *aggr_header_std[] = {
+const char *aggr_header_std[] = {
 	[AGGR_CORE] 	= 	"core",
 	[AGGR_CACHE] 	= 	"cache",
 	[AGGR_CLUSTER]	= 	"cluster",
diff --git a/tools/perf/util/stat-print-csv.c b/tools/perf/util/stat-print-csv.c
new file mode 100644
index 000000000000..ee86d07636ea
--- /dev/null
+++ b/tools/perf/util/stat-print-csv.c
@@ -0,0 +1,13 @@
+// SPDX-License-Identifier: GPL-2.0
+#include "stat-print.h"
+#include <linux/compiler.h>
+
+int perf_stat__print_csv(struct evlist *evlist __maybe_unused,
+			 const struct perf_stat_config *config __maybe_unused,
+			 const struct target *target __maybe_unused,
+			 const struct timespec *ts __maybe_unused,
+			 int argc __maybe_unused,
+			 const char **argv __maybe_unused)
+{
+	return 0;
+}
diff --git a/tools/perf/util/stat-print-json.c b/tools/perf/util/stat-print-json.c
new file mode 100644
index 000000000000..4690c4286fd2
--- /dev/null
+++ b/tools/perf/util/stat-print-json.c
@@ -0,0 +1,13 @@
+// SPDX-License-Identifier: GPL-2.0
+#include "stat-print.h"
+#include <linux/compiler.h>
+
+int perf_stat__print_json(struct evlist *evlist __maybe_unused,
+			  const struct perf_stat_config *config __maybe_unused,
+			  const struct target *target __maybe_unused,
+			  const struct timespec *ts __maybe_unused,
+			  int argc __maybe_unused,
+			  const char **argv __maybe_unused)
+{
+	return 0;
+}
diff --git a/tools/perf/util/stat-print-std.c b/tools/perf/util/stat-print-std.c
new file mode 100644
index 000000000000..a1311f950559
--- /dev/null
+++ b/tools/perf/util/stat-print-std.c
@@ -0,0 +1,13 @@
+// SPDX-License-Identifier: GPL-2.0
+#include "stat-print.h"
+#include <linux/compiler.h>
+
+int perf_stat__print_std(struct evlist *evlist __maybe_unused,
+			 const struct perf_stat_config *config __maybe_unused,
+			 const struct target *target __maybe_unused,
+			 const struct timespec *ts __maybe_unused,
+			 int argc __maybe_unused,
+			 const char **argv __maybe_unused)
+{
+	return 0;
+}
diff --git a/tools/perf/util/stat-print.c b/tools/perf/util/stat-print.c
new file mode 100644
index 000000000000..0fff50dce80b
--- /dev/null
+++ b/tools/perf/util/stat-print.c
@@ -0,0 +1,489 @@
+// SPDX-License-Identifier: GPL-2.0
+#include "stat-print.h"
+
+#include <errno.h>
+#include <math.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <linux/compiler.h>
+
+#include "cpumap.h"
+#include "debug.h"
+#include "evlist.h"
+#include "evsel.h"
+#include "expr.h"
+#include "metricgroup.h"
+#include "stat.h"
+#include "thread_map.h"
+#include "tool_pmu.h"
+
+/*
+ * Unified Aggregation Helpers (Shared by STD, CSV, JSON Formats)
+ */
+
+const char *perf_stat__get_aggr_key(const struct perf_stat_config *config,
+				    const struct evsel *evsel)
+{
+	switch (config->aggr_mode) {
+	case AGGR_CORE:
+		return "core";
+	case AGGR_CACHE:
+		return "cache";
+	case AGGR_CLUSTER:
+		return "cluster";
+	case AGGR_DIE:
+		return "die";
+	case AGGR_SOCKET:
+		return "socket";
+	case AGGR_NODE:
+		return "node";
+	case AGGR_NONE:
+		if (evsel->percore && !config->percore_show_thread)
+			return "core";
+		return "cpu";
+	case AGGR_THREAD:
+		return "thread";
+	case AGGR_GLOBAL:
+	case AGGR_UNSET:
+	case AGGR_MAX:
+	default:
+		return "";
+	}
+}
+
+int perf_stat__get_aggr_id_char(const struct perf_stat_config *config, struct evsel *evsel,
+				struct aggr_cpu_id id, char *buf, size_t buf_size)
+{
+	switch (config->aggr_mode) {
+	case AGGR_CORE:
+		return scnprintf(buf, buf_size, "S%d-D%d-C%d", id.socket, id.die, id.core);
+	case AGGR_CACHE:
+		return scnprintf(buf, buf_size, "S%d-D%d-L%d-ID%d", id.socket, id.die, id.cache_lvl,
+				 id.cache);
+	case AGGR_CLUSTER:
+		return scnprintf(buf, buf_size, "S%d-D%d-CLS%d", id.socket, id.die, id.cluster);
+	case AGGR_DIE:
+		return scnprintf(buf, buf_size, "S%d-D%d", id.socket, id.die);
+	case AGGR_SOCKET:
+		return scnprintf(buf, buf_size, "S%d", id.socket);
+	case AGGR_NODE:
+		return scnprintf(buf, buf_size, "N%d", id.node);
+	case AGGR_NONE:
+		if (evsel->percore && !config->percore_show_thread)
+			return scnprintf(buf, buf_size, "S%d-D%d-C%d", id.socket, id.die, id.core);
+		else if (id.cpu.cpu > -1)
+			return scnprintf(buf, buf_size, "%d", id.cpu.cpu);
+		break;
+	case AGGR_THREAD:
+		return scnprintf(buf, buf_size, "%s-%d",
+				 perf_thread_map__comm(evsel->core.threads, id.thread_idx),
+				 perf_thread_map__pid(evsel->core.threads, id.thread_idx));
+	case AGGR_GLOBAL:
+	case AGGR_UNSET:
+	case AGGR_MAX:
+	default:
+		break;
+	}
+	buf[0] = '\0';
+	return -1;
+}
+
+/*
+ * Traversal Driver and Calculation Code
+ */
+
+/**
+ * tool_pmu__is_time_event - Check if event is a tool PMU time event.
+ *
+ * Copied from stat-shadow.c to make stat-print.c self-contained.
+ */
+static bool tool_pmu__is_time_event(const struct perf_stat_config *config,
+				    const struct evsel *evsel, int *tool_aggr_idx)
+{
+	enum tool_pmu_event event = evsel__tool_event(evsel);
+	int aggr_idx;
+
+	if (event != TOOL_PMU__EVENT_DURATION_TIME && event != TOOL_PMU__EVENT_USER_TIME &&
+	    event != TOOL_PMU__EVENT_SYSTEM_TIME)
+		return false;
+
+	if (config) {
+		cpu_aggr_map__for_each_idx(aggr_idx, config->aggr_map) {
+			if (config->aggr_map->map[aggr_idx].cpu.cpu == 0) {
+				*tool_aggr_idx = aggr_idx;
+				return true;
+			}
+		}
+		pr_debug("Unexpected CPU0 missing in aggregation for tool event.\n");
+	}
+	*tool_aggr_idx = 0; /* Assume the first aggregation index works. */
+	return true;
+}
+
+/**
+ * prepare_metric - Collect event values required for a metric.
+ * @config: Perf stat configuration.
+ * @mexp: The metric expression.
+ * @evsel: The associated event selector.
+ * @pctx: Expr parse context to add ID/values to.
+ * @aggr_idx: Aggregation index to read values from.
+ *
+ * Iterates over the events required for the metric expression, reads their
+ * counts for the given aggregation index, and adds them to the expression
+ * parser context.
+ *
+ * Copied and refactored from stat-shadow.c.
+ */
+static int prepare_metric(const struct perf_stat_config *config, const struct metric_expr *mexp,
+			  struct evsel *evsel, struct expr_parse_ctx *pctx, int aggr_idx)
+{
+	struct evsel *const *metric_events = mexp->metric_events;
+	struct metric_ref *metric_refs = mexp->metric_refs;
+	int i;
+
+	for (i = 0; metric_events[i]; i++) {
+		int source_count = 0, tool_aggr_idx;
+		bool is_tool_time =
+			tool_pmu__is_time_event(config, metric_events[i], &tool_aggr_idx);
+		struct perf_stat_evsel *ps = metric_events[i]->stats;
+		char *n;
+		double val;
+
+		/*
+		 * If there are multiple uncore PMUs and we're not reading the
+		 * leader's stats, determine the stats for the appropriate
+		 * uncore PMU.
+		 */
+		if (evsel && evsel->metric_leader && evsel->pmu != evsel->metric_leader->pmu &&
+		    mexp->metric_events[i]->pmu == evsel->metric_leader->pmu) {
+			struct evsel *pos;
+
+			evlist__for_each_entry(evsel->evlist, pos) {
+				if (pos->pmu != evsel->pmu)
+					continue;
+				if (pos->metric_leader != mexp->metric_events[i])
+					continue;
+				ps = pos->stats;
+				source_count = 1;
+				break;
+			}
+		}
+		/* Time events are always on CPU0, the first aggregation index. */
+		if (!ps || !metric_events[i]->supported) {
+			val = NAN;
+			source_count = 0;
+		} else {
+			struct perf_stat_aggr *aggr =
+				&ps->aggr[is_tool_time ? tool_aggr_idx : aggr_idx];
+
+			if (aggr->counts.run == 0) {
+				val = NAN;
+				source_count = 0;
+			} else {
+				val = aggr->counts.val;
+				if (is_tool_time) {
+					/* Convert time event nanoseconds to seconds. */
+					val *= 1e-9;
+				}
+				if (!source_count)
+					source_count = evsel__source_count(metric_events[i]);
+			}
+		}
+		n = strdup(evsel__metric_id(metric_events[i]));
+		if (!n)
+			return -ENOMEM;
+
+		expr__add_id_val_source_count(pctx, n, val, source_count);
+	}
+
+	for (int j = 0; metric_refs && metric_refs[j].metric_name; j++) {
+		int ret = expr__add_ref(pctx, &metric_refs[j]);
+
+		if (ret)
+			return ret;
+	}
+
+	return i;
+}
+
+/**
+ * calculate_and_print_metric - Compute and print a single metric.
+ *
+ * Parses the metric expression, computes the ratio, and calls the print_metric
+ * callback directly with clean parameters.
+ * Returns the return value of the print_metric callback (0 on success, or error).
+ */
+static int calculate_and_print_metric(const struct perf_stat_config *config,
+				      const struct perf_stat_print_callbacks *cb, void *outer_ctx,
+				      struct metric_expr *mexp, struct evsel *evsel, int aggr_idx)
+{
+	const char *metric_name = mexp->metric_name;
+	const char *metric_expr = mexp->metric_expr;
+	const char *metric_threshold = mexp->metric_threshold;
+	const char *metric_unit = mexp->metric_unit;
+	struct evsel *const *metric_events = mexp->metric_events;
+	int runtime = mexp->runtime;
+	struct expr_parse_ctx *pctx;
+	double ratio, scale, threshold;
+	int i;
+	enum metric_threshold_classify thresh = METRIC_THRESHOLD_UNKNOWN;
+	int ret = 0;
+
+	if (!cb->print_metric)
+		return 0;
+
+	pctx = expr__ctx_new();
+	if (!pctx)
+		return -ENOMEM;
+
+	if (config->user_requested_cpu_list)
+		pctx->sctx.user_requested_cpu_list = strdup(config->user_requested_cpu_list);
+	pctx->sctx.runtime = runtime;
+	pctx->sctx.system_wide = config->system_wide;
+	i = prepare_metric(config, mexp, evsel, pctx, aggr_idx);
+	if (i < 0) {
+		expr__ctx_free(pctx);
+		return i;
+	}
+	if (!metric_events[i]) {
+		if (expr__parse(&ratio, pctx, metric_expr) == 0) {
+			char *unit;
+
+			if (metric_threshold &&
+			    expr__parse(&threshold, pctx, metric_threshold) == 0 &&
+			    !isnan(threshold)) {
+				thresh = fpclassify(threshold) == FP_ZERO ? METRIC_THRESHOLD_GOOD :
+									    METRIC_THRESHOLD_BAD;
+			}
+
+			if (metric_unit && metric_name) {
+				if (perf_pmu__convert_scale(metric_unit, &unit, &scale) >= 0)
+					ratio *= scale;
+
+				ret = cb->print_metric(outer_ctx, config, evsel, aggr_idx,
+						       metric_name, unit, ratio, thresh);
+			} else {
+				ret = cb->print_metric(outer_ctx, config, evsel, aggr_idx,
+						       metric_name ?: (evsel->name ?: ""), NULL,
+						       ratio, thresh);
+			}
+		}
+	}
+
+	expr__ctx_free(pctx);
+	return ret;
+}
+
+/**
+ * perf_stat_print_metricgroup - Traverse metrics for an event.
+ *
+ * Returns 0 on success, or a negative error code on failure.
+ */
+static bool is_basic_shadow_metric(const char *name)
+{
+	static const char *const basic_metrics[] = {
+		"insn_per_cycle",   "branch_miss_rate",	      "branch_frequency",
+		"cycles_frequency", "page_faults_per_second", "migrations_per_second",
+		"cs_per_second",    "CPUs_utilized",
+	};
+	for (size_t i = 0; i < ARRAY_SIZE(basic_metrics); i++) {
+		if (!strcmp(basic_metrics[i], name))
+			return true;
+	}
+	return false;
+}
+
+static int perf_stat_print_metricgroup(const struct perf_stat_config *config,
+				       const struct perf_stat_print_callbacks *cb, void *outer_ctx,
+				       struct evsel *evsel, int aggr_idx)
+{
+	struct metric_event *me;
+	struct metric_expr *mexp;
+	struct rblist *metric_events = &evsel->evlist->metric_events;
+	int ret;
+
+	me = metricgroup__lookup(metric_events, evsel, false);
+	if (me == NULL)
+		return 0;
+
+	list_for_each_entry(mexp, &me->head, nd) {
+		if (!config->metric_only &&
+		    (!evsel->default_metricgroup || evsel->default_show_events)) {
+			if (!is_basic_shadow_metric(mexp->metric_name))
+				continue;
+		}
+
+		ret = calculate_and_print_metric(config, cb, outer_ctx, mexp, evsel, aggr_idx);
+		if (ret)
+			return ret;
+	}
+	return 0;
+}
+
+/**
+ * perf_stat_print_metrics - Entry point for metric calculation & printing.
+ *
+ * Returns 0 on success, or a negative error code on failure.
+ */
+static int perf_stat_print_metrics(const struct perf_stat_config *config,
+				   const struct perf_stat_print_callbacks *cb, void *outer_ctx,
+				   struct evsel *evsel, int aggr_idx)
+{
+	if (config->iostat_run) {
+		/* IOSTAT metrics not supported yet in new API */
+		return 0;
+	}
+
+	return perf_stat_print_metricgroup(config, cb, outer_ctx, evsel, aggr_idx);
+}
+
+int perf_stat__print_cb(struct evlist *evlist, const struct perf_stat_config *config,
+			const struct target *target __maybe_unused,
+			const struct timespec *ts __maybe_unused, int argc __maybe_unused,
+			const char **argv __maybe_unused,
+			const struct perf_stat_print_callbacks *cb, void *ctx)
+{
+	struct evsel *counter;
+	int aggr_idx;
+	int ret = 0;
+
+	evlist__uniquify_evsel_names(evlist, config);
+
+	if (cb->print_start) {
+		ret = cb->print_start(ctx, config);
+		if (ret)
+			return ret;
+	}
+
+	switch (config->aggr_mode) {
+	case AGGR_GLOBAL:
+	case AGGR_NONE:
+	case AGGR_SOCKET:
+	case AGGR_DIE:
+	case AGGR_CLUSTER:
+	case AGGR_CACHE:
+	case AGGR_CORE:
+	case AGGR_THREAD:
+	case AGGR_NODE:
+		if (config->aggr_map) {
+			cpu_aggr_map__for_each_idx(aggr_idx, config->aggr_map) {
+				evlist__for_each_entry(evlist, counter) {
+					struct perf_stat_evsel *ps = counter->stats;
+					u64 val = 0, ena = 0, run = 0;
+
+					if (ps && ps->aggr) {
+						val = ps->aggr[aggr_idx].counts.val;
+						ena = ps->aggr[aggr_idx].counts.ena;
+						run = ps->aggr[aggr_idx].counts.run;
+					}
+
+					/* Skip already merged uncore/hybrid events */
+					if (config->aggr_mode != AGGR_NONE) {
+						bool skip_hw = evsel__is_hybrid(counter) ?
+							(config->hybrid_merge &&
+							 counter->first_wildcard_match != NULL) :
+							(counter->first_wildcard_match != NULL);
+
+						if (skip_hw)
+							continue;
+					}
+
+					if (perf_stat__skip_metric_event(counter))
+						continue;
+
+					if (cb->print_event) {
+						double stdev_pct = 0.0;
+
+						if (ps && ps->res_stats.n > 1) {
+							stdev_pct = rel_stddev_stats(
+								stddev_stats(&ps->res_stats), val);
+						}
+						ret = cb->print_event(ctx, config, counter,
+								      aggr_idx, val, ena, run,
+								      stdev_pct);
+						if (ret)
+							goto out;
+					}
+
+					ret = perf_stat_print_metrics(config, cb, ctx, counter,
+								      aggr_idx);
+					if (ret)
+						goto out;
+				}
+			}
+		} else {
+			evlist__for_each_entry(evlist, counter) {
+				struct perf_stat_evsel *ps = counter->stats;
+				u64 val = 0, ena = 0, run = 0;
+
+				if (ps && ps->aggr) {
+					val = ps->aggr[0].counts.val;
+					ena = ps->aggr[0].counts.ena;
+					run = ps->aggr[0].counts.run;
+				}
+
+				/* Skip already merged uncore/hybrid events */
+				if (config->aggr_mode != AGGR_NONE) {
+					bool skip_hw = evsel__is_hybrid(counter) ?
+						(config->hybrid_merge &&
+						 counter->first_wildcard_match != NULL) :
+						(counter->first_wildcard_match != NULL);
+
+					if (skip_hw)
+						continue;
+				}
+
+				if (perf_stat__skip_metric_event(counter))
+					continue;
+
+				if (cb->print_event) {
+					double stdev_pct = 0.0;
+
+					if (ps && ps->res_stats.n > 1) {
+						stdev_pct = rel_stddev_stats(
+							stddev_stats(&ps->res_stats), val);
+					}
+					ret = cb->print_event(ctx, config, counter, 0, val, ena,
+							      run, stdev_pct);
+					if (ret)
+						goto out;
+				}
+
+				ret = perf_stat_print_metrics(config, cb, ctx, counter, 0);
+				if (ret)
+					goto out;
+			}
+		}
+		break;
+	case AGGR_UNSET:
+	case AGGR_MAX:
+	default:
+		fprintf(config->output, "Aggregation mode %d not supported in new API yet\n",
+			config->aggr_mode);
+		break;
+	}
+
+out:
+	if (cb->print_end) {
+		int err = cb->print_end(ctx, config);
+
+		if (!ret)
+			ret = err;
+	}
+
+	return ret;
+}
+
+int perf_stat__print(struct evlist *evlist, const struct perf_stat_config *config,
+		     const struct target *target, const struct timespec *ts, int argc,
+		     const char **argv)
+{
+	if (config->csv_output)
+		return perf_stat__print_csv(evlist, config, target, ts, argc, argv);
+	else if (config->json_output)
+		return perf_stat__print_json(evlist, config, target, ts, argc, argv);
+	else
+		return perf_stat__print_std(evlist, config, target, ts, argc, argv);
+}
diff --git a/tools/perf/util/stat-print.h b/tools/perf/util/stat-print.h
new file mode 100644
index 000000000000..a86414f32584
--- /dev/null
+++ b/tools/perf/util/stat-print.h
@@ -0,0 +1,133 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef __PERF_STAT_PRINT_H
+#define __PERF_STAT_PRINT_H
+
+#include <linux/types.h>
+
+#include "stat.h"
+
+#define CNTR_NOT_SUPPORTED "<not supported>"
+#define CNTR_NOT_COUNTED "<not counted>"
+
+struct evlist;
+struct perf_stat_config;
+struct target;
+struct timespec;
+struct evsel;
+struct aggr_cpu_id;
+
+extern const int aggr_header_lens[];
+extern const char *aggr_header_csv[];
+extern const char *aggr_header_std[];
+
+/**
+ * struct perf_stat_print_callbacks - Callbacks for rendering perf stat output.
+ *
+ * This structure defines the interface for different output formats (e.g.,
+ * Standard, CSV, JSON) to render the collected performance counter statistics.
+ * The core display logic traverses the events and metrics and calls these
+ * callbacks in a streaming fashion, which build an in-memory DOM tree. The
+ * final rendering and output formatting is executed entirely in print_end.
+ */
+struct perf_stat_print_callbacks {
+	/**
+	 * print_start - Called before any event or metric is printed.
+	 * @ctx: Opaque context pointer passed to the print function.
+	 * @config: Perf stat configuration.
+	 */
+	int (*print_start)(void *ctx, const struct perf_stat_config *config);
+
+	/**
+	 * print_end - Called after all events and metrics have been traversed.
+	 * Executes the actual formatting and printing of the buffered tree.
+	 * @ctx: Opaque context pointer.
+	 * @config: Perf stat configuration.
+	 */
+	int (*print_end)(void *ctx, const struct perf_stat_config *config);
+
+	/**
+	 * print_event - Called to buffer an event (counter) value.
+	 * @ctx: Opaque context pointer.
+	 * @config: Perf stat configuration.
+	 * @evsel: The event selector being printed (mutable for lazy initialization).
+	 * @aggr_idx: Aggregation index in evsel->stats.
+	 * @val: Raw counter value.
+	 * @ena: Enabled time for the counter (for multiplexing).
+	 * @run: Running time for the counter (for multiplexing).
+	 * @stdev_pct: Standard deviation percentage across multiple repeated runs.
+	 *
+	 * Returns 0 on success, or a negative error code (e.g., -ENOMEM) on failure.
+	 */
+	int (*print_event)(void *ctx, const struct perf_stat_config *config, struct evsel *evsel,
+			   int aggr_idx, u64 val, u64 ena, u64 run, double stdev_pct);
+
+	/**
+	 * print_metric - Called to buffer a metric value associated with an event.
+	 * @ctx: Opaque context pointer.
+	 * @config: Perf stat configuration.
+	 * @evsel: The event selector associated with the metric (mutable).
+	 * @aggr_idx: Aggregation index.
+	 * @name: The display name of the metric.
+	 * @unit: The unit of the metric (e.g., "%", "GHz", or NULL).
+	 * @val: The calculated metric value.
+	 * @thresh: Threshold classification (e.g., good, bad) for color coding.
+	 *
+	 * Returns 0 on success, or a negative error code (e.g., -ENOMEM) on failure.
+	 */
+	int (*print_metric)(void *ctx, const struct perf_stat_config *config, struct evsel *evsel,
+			    int aggr_idx, const char *name, const char *unit, double val,
+			    enum metric_threshold_classify thresh);
+};
+
+/**
+ * perf_stat__get_aggr_key - Get the JSON key name for an aggregation mode.
+ */
+const char *perf_stat__get_aggr_key(const struct perf_stat_config *config,
+				    const struct evsel *evsel);
+
+/**
+ * perf_stat__get_aggr_id_char - Get the unified aggregation ID string.
+ *
+ * Returns the formatted string size, or a negative error code on failure.
+ */
+int perf_stat__get_aggr_id_char(const struct perf_stat_config *config, struct evsel *evsel,
+				struct aggr_cpu_id id, char *buf, size_t buf_size);
+
+/**
+ * perf_stat__print_cb - Drive the traversal and call callbacks.
+ *
+ * Defined in stat-print.c. Called by format-specific entry points.
+ * Returns 0 on success, or a negative error code on failure.
+ */
+int perf_stat__print_cb(struct evlist *evlist, const struct perf_stat_config *config,
+			const struct target *target, const struct timespec *ts, int argc,
+			const char **argv, const struct perf_stat_print_callbacks *cb, void *ctx);
+
+/**
+ * perf_stat__print - Entry point for the decoupled print API.
+ *
+ * Defined in stat-print.c. Dispatches to format-specific entry points.
+ * Returns 0 on success, or a negative error code on failure.
+ */
+int perf_stat__print(struct evlist *evlist, const struct perf_stat_config *config,
+		     const struct target *target, const struct timespec *ts, int argc,
+		     const char **argv);
+
+/*
+ * Format-specific entry points, implemented in their respective files.
+ * All return 0 on success, or a negative error code on failure.
+ */
+
+int perf_stat__print_std(struct evlist *evlist, const struct perf_stat_config *config,
+			 const struct target *target, const struct timespec *ts, int argc,
+			 const char **argv);
+
+int perf_stat__print_csv(struct evlist *evlist, const struct perf_stat_config *config,
+			 const struct target *target, const struct timespec *ts, int argc,
+			 const char **argv);
+
+int perf_stat__print_json(struct evlist *evlist, const struct perf_stat_config *config,
+			  const struct target *target, const struct timespec *ts, int argc,
+			  const char **argv);
+
+#endif /* __PERF_STAT_PRINT_H */
diff --git a/tools/perf/util/stat.h b/tools/perf/util/stat.h
index 4bced233d2fc..77873d51786c 100644
--- a/tools/perf/util/stat.h
+++ b/tools/perf/util/stat.h
@@ -106,6 +106,8 @@ struct perf_stat_config {
 	bool			 ctl_fd_close;
 	const char		*cgroup_list;
 	unsigned int		topdown_level;
+	bool			 headers_printed;
+	bool			 metric_only_headers_printed;
 };
 
 extern struct perf_stat_config stat_config;
-- 
2.55.0.141.g00534a21ce-goog


  reply	other threads:[~2026-07-16  4:32 UTC|newest]

Thread overview: 91+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-05-22 22:33 [RFC PATCH v1 00/14] perf stat: Decouple and modularize metrics/events output printing API Ian Rogers
2026-05-22 22:33 ` [RFC PATCH v1 01/14] perf stat: Introduce core generic print traversal engine and header stubs Ian Rogers
2026-05-22 23:47   ` sashiko-bot
2026-05-22 22:33 ` [RFC PATCH v1 02/14] perf stat: Implement standard console (STD) formatting callbacks Ian Rogers
2026-05-22 22:54   ` sashiko-bot
2026-05-22 22:33 ` [RFC PATCH v1 03/14] perf stat: Extend STD output linter to test basic New API checks Ian Rogers
2026-05-22 22:33 ` [RFC PATCH v1 04/14] perf stat: Extend STD output linter to test core aggregation checks Ian Rogers
2026-05-22 22:33 ` [RFC PATCH v1 05/14] perf stat: Extend STD output linter to test advanced PMU checks Ian Rogers
2026-05-22 22:33 ` [RFC PATCH v1 06/14] perf stat: Extend STD output linter to test metric-only checks Ian Rogers
2026-05-22 22:33 ` [RFC PATCH v1 07/14] perf stat: Implement CSV formatting callbacks Ian Rogers
2026-05-22 23:01   ` sashiko-bot
2026-05-22 22:33 ` [RFC PATCH v1 08/14] perf stat: Extend CSV output linter to test core aggregation checks Ian Rogers
2026-05-22 22:59   ` sashiko-bot
2026-05-22 22:33 ` [RFC PATCH v1 09/14] perf stat: Extend CSV output linter to test advanced PMU and metric-only checks Ian Rogers
2026-05-22 22:48   ` sashiko-bot
2026-05-22 22:33 ` [RFC PATCH v1 10/14] perf stat: Implement streaming JSON formatting callbacks Ian Rogers
2026-05-22 23:02   ` sashiko-bot
2026-05-22 22:33 ` [RFC PATCH v1 11/14] perf stat: Extend JSON output linter to test core aggregation checks Ian Rogers
2026-05-22 22:53   ` sashiko-bot
2026-05-22 22:33 ` [RFC PATCH v1 12/14] perf stat: Extend JSON output linter to test advanced PMU and metric-only checks Ian Rogers
2026-05-22 22:33 ` [RFC PATCH v1 13/14] perf stat: Add --new support to PMU metrics Python validator Ian Rogers
2026-05-22 22:33 ` [RFC PATCH v1 14/14] perf stat: Extend PMU metrics value linter to validate --new outputs Ian Rogers
2026-05-25 23:18 ` [RFC PATCH v2 00/14] perf stat: Decouple and modularize metrics/events output printing API Ian Rogers
2026-05-25 23:18   ` [RFC PATCH v2 01/14] perf stat: Introduce core generic print traversal engine and header stubs Ian Rogers
2026-05-25 23:38     ` Arnaldo Carvalho de Melo
2026-05-25 23:48       ` Ian Rogers
2026-05-26  0:20         ` Arnaldo Carvalho de Melo
2026-05-25 23:18   ` [RFC PATCH v2 02/14] perf stat: Implement standard console (STD) formatting callbacks Ian Rogers
2026-05-25 23:49     ` Arnaldo Carvalho de Melo
2026-05-26  0:09       ` Ian Rogers
2026-05-25 23:53     ` sashiko-bot
2026-05-25 23:18   ` [RFC PATCH v2 03/14] perf stat: Extend STD output linter to test basic New API checks Ian Rogers
2026-05-25 23:39     ` Arnaldo Carvalho de Melo
2026-05-25 23:18   ` [RFC PATCH v2 04/14] perf stat: Extend STD output linter to test core aggregation checks Ian Rogers
2026-05-25 23:18   ` [RFC PATCH v2 05/14] perf stat: Extend STD output linter to test advanced PMU checks Ian Rogers
2026-05-25 23:18   ` [RFC PATCH v2 06/14] perf stat: Extend STD output linter to test metric-only checks Ian Rogers
2026-05-25 23:18   ` [RFC PATCH v2 07/14] perf stat: Implement CSV formatting callbacks Ian Rogers
2026-05-25 23:18   ` [RFC PATCH v2 08/14] perf stat: Extend CSV output linter to test core aggregation checks Ian Rogers
2026-05-25 23:18   ` [RFC PATCH v2 09/14] perf stat: Extend CSV output linter to test advanced PMU and metric-only checks Ian Rogers
2026-05-25 23:18   ` [RFC PATCH v2 10/14] perf stat: Implement streaming JSON formatting callbacks Ian Rogers
2026-05-25 23:18   ` [RFC PATCH v2 11/14] perf stat: Extend JSON output linter to test core aggregation checks Ian Rogers
2026-05-25 23:18   ` [RFC PATCH v2 12/14] perf stat: Extend JSON output linter to test advanced PMU and metric-only checks Ian Rogers
2026-05-25 23:18   ` [RFC PATCH v2 13/14] perf stat: Add --new support to PMU metrics Python validator Ian Rogers
2026-05-25 23:19   ` [RFC PATCH v2 14/14] perf stat: Extend PMU metrics value linter to validate --new outputs Ian Rogers
2026-05-25 23:53     ` sashiko-bot
2026-06-05 18:02   ` [RFC PATCH v2 00/14] perf stat: Decouple and modularize metrics/events output printing API Chun-Tse Shao
2026-07-16  4:32   ` [PATCH v3 00/14] perf stat: Decouple printing API and introduce streaming zero-allocation printers Ian Rogers
2026-07-16  4:32     ` Ian Rogers [this message]
2026-07-16  4:47       ` [PATCH v3 01/14] perf stat: Introduce core generic print traversal engine and header stubs sashiko-bot
2026-07-16  4:32     ` [PATCH v3 02/14] perf stat: Implement standard console (STD) formatting callbacks Ian Rogers
2026-07-16  4:44       ` sashiko-bot
2026-07-16  4:32     ` [PATCH v3 03/14] perf stat: Extend STD output linter to test basic New API checks Ian Rogers
2026-07-16  4:42       ` sashiko-bot
2026-07-16  4:32     ` [PATCH v3 04/14] perf stat: Extend STD output linter to test core aggregation checks Ian Rogers
2026-07-16  4:38       ` sashiko-bot
2026-07-16  4:32     ` [PATCH v3 05/14] perf stat: Extend STD output linter to test advanced PMU checks Ian Rogers
2026-07-16  4:43       ` sashiko-bot
2026-07-16  4:32     ` [PATCH v3 06/14] perf stat: Extend STD output linter to test metric-only checks Ian Rogers
2026-07-16  4:32     ` [PATCH v3 07/14] perf stat: Implement CSV formatting callbacks Ian Rogers
2026-07-16  4:43       ` sashiko-bot
2026-07-16  4:32     ` [PATCH v3 08/14] perf stat: Extend CSV output linter to test core aggregation checks Ian Rogers
2026-07-16  4:32     ` [PATCH v3 09/14] perf stat: Extend CSV output linter to test advanced PMU and metric-only checks Ian Rogers
2026-07-16  4:32     ` [PATCH v3 10/14] perf stat: Implement streaming JSON formatting callbacks Ian Rogers
2026-07-16  4:46       ` sashiko-bot
2026-07-16  4:32     ` [PATCH v3 11/14] perf stat: Extend JSON output linter to test core aggregation checks Ian Rogers
2026-07-16  4:42       ` sashiko-bot
2026-07-16  4:32     ` [PATCH v3 12/14] perf stat: Extend JSON output linter to test advanced PMU and metric-only checks Ian Rogers
2026-07-16  4:32     ` [PATCH v3 13/14] perf stat: Add --new support to PMU metrics Python validator Ian Rogers
2026-07-16  4:52       ` sashiko-bot
2026-07-16  4:32     ` [PATCH v3 14/14] perf stat: Extend PMU metrics value linter to validate --new outputs Ian Rogers
2026-07-16  4:49       ` sashiko-bot
2026-07-16  7:02     ` [PATCH v4 00/14] perf stat: Decouple and modularize display formatting Ian Rogers
2026-07-16  7:02       ` [PATCH v4 01/14] perf stat: Introduce core generic print traversal engine and header stubs Ian Rogers
2026-07-16  7:16         ` sashiko-bot
2026-07-16  7:02       ` [PATCH v4 02/14] perf stat: Implement standard console (STD) formatting callbacks Ian Rogers
2026-07-16  7:24         ` sashiko-bot
2026-07-16  7:02       ` [PATCH v4 03/14] perf stat: Extend STD output linter to test basic New API checks Ian Rogers
2026-07-16  7:02       ` [PATCH v4 04/14] perf stat: Extend STD output linter to test core aggregation checks Ian Rogers
2026-07-16  7:02       ` [PATCH v4 05/14] perf stat: Extend STD output linter to test advanced PMU checks Ian Rogers
2026-07-16  7:02       ` [PATCH v4 06/14] perf stat: Extend STD output linter to test metric-only checks Ian Rogers
2026-07-16  7:02       ` [PATCH v4 07/14] perf stat: Implement CSV formatting callbacks Ian Rogers
2026-07-16  7:11         ` sashiko-bot
2026-07-16  7:02       ` [PATCH v4 08/14] perf stat: Extend CSV output linter to test core aggregation checks Ian Rogers
2026-07-16  7:02       ` [PATCH v4 09/14] perf stat: Extend CSV output linter to test advanced PMU and metric-only checks Ian Rogers
2026-07-16  7:02       ` [PATCH v4 10/14] perf stat: Implement streaming JSON formatting callbacks Ian Rogers
2026-07-16  7:19         ` sashiko-bot
2026-07-16  7:03       ` [PATCH v4 11/14] perf stat: Extend JSON output linter to test core aggregation checks Ian Rogers
2026-07-16  7:03       ` [PATCH v4 12/14] perf stat: Extend JSON output linter to test advanced PMU and metric-only checks Ian Rogers
2026-07-16  7:03       ` [PATCH v4 13/14] perf stat: Add --new support to PMU metrics Python validator Ian Rogers
2026-07-16  7:03       ` [PATCH v4 14/14] perf stat: Extend PMU metrics value linter to validate --new outputs Ian Rogers
2026-07-16  7:16         ` sashiko-bot

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20260716043223.388233-2-irogers@google.com \
    --to=irogers@google.com \
    --cc=acme@kernel.org \
    --cc=adrian.hunter@intel.com \
    --cc=ctshao@google.com \
    --cc=james.clark@linaro.org \
    --cc=jolsa@kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux-perf-users@vger.kernel.org \
    --cc=mingo@redhat.com \
    --cc=namhyung@kernel.org \
    --cc=peterz@infradead.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.