Linux Perf Users
 help / color / mirror / Atom feed
From: Ian Rogers <irogers@google.com>
To: Peter Zijlstra <peterz@infradead.org>,
	Ingo Molnar <mingo@redhat.com>,
	 Arnaldo Carvalho de Melo <acme@kernel.org>,
	Namhyung Kim <namhyung@kernel.org>, Jiri Olsa <jolsa@kernel.org>,
	 Ian Rogers <irogers@google.com>,
	Adrian Hunter <adrian.hunter@intel.com>,
	 James Clark <james.clark@linaro.org>,
	linux-kernel@vger.kernel.org,  linux-perf-users@vger.kernel.org
Subject: [RFC PATCH v1 02/14] perf stat: Implement standard console (STD) formatting callbacks
Date: Fri, 22 May 2026 15:33:30 -0700	[thread overview]
Message-ID: <20260522223342.2393553-3-irogers@google.com> (raw)
In-Reply-To: <20260522223342.2393553-1-irogers@google.com>

This patch implements standard console formatting callbacks inside
util/stat-print-std.c, replacing the empty stubs introduced in Commit 1.

Introduces the format-private `struct queued_event` and `struct queued_metric`
DOM nodes to buffer traversal streams, and fully encapsulates DOM state
initialization and queue cleanups inside std_print_start() and std_print_end().

Utilizes the newly centralized unified aggregation helpers to resolve CPU and
thread prefixes cleanly, and incorporates full interval-mode timestamp
printing support across all rows.

Signed-off-by: Ian Rogers <irogers@google.com>

TAG=agy
CONV=7c33d6a0-70a2-454a-82f7-4de2101a5b9c
---
 tools/perf/util/stat-print-std.c | 740 ++++++++++++++++++++++++++++++-
 1 file changed, 733 insertions(+), 7 deletions(-)

diff --git a/tools/perf/util/stat-print-std.c b/tools/perf/util/stat-print-std.c
index 83987e97c889..0aa4e2669bba 100644
--- a/tools/perf/util/stat-print-std.c
+++ b/tools/perf/util/stat-print-std.c
@@ -1,13 +1,739 @@
 /* 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 <linux/kernel.h>
+#include <linux/list.h>
+
+#include "color.h"
+#include "cpumap.h"
+#include "debug.h"
+#include "evlist.h"
+#include "evsel.h"
+#include "metricgroup.h"
+#include "stat-print.h"
+#include "stat.h"
+#include "target.h"
+#include "thread_map.h"
+#include "tool_pmu.h"
+
+#define COUNTS_LEN 18
+#define EVNAME_LEN 32
+#define COMM_LEN 16
+#define PID_LEN 7
+#define MGROUP_LEN 50
+#define METRIC_LEN 38
+
+static const int aggr_header_lens[] = {
+	[AGGR_CORE] = 18, [AGGR_CACHE] = 22,  [AGGR_CLUSTER] = 20,
+	[AGGR_DIE] = 12,  [AGGR_SOCKET] = 6,  [AGGR_NODE] = 6,
+	[AGGR_NONE] = 6,  [AGGR_THREAD] = 16, [AGGR_GLOBAL] = 0,
+};
+
+/**
+ * struct queued_metric - In-memory record of a buffered metric.
+ * @list: Linked list node for queueing.
+ * @name: The display name of the metric.
+ * @unit: The metric's unit (e.g., "%", "GHz", or NULL).
+ * @val: The calculated ratio/metric value.
+ * @thresh: Threshold classification for color coding.
+ * @aggr_idx: Aggregation index in evsel stats.
+ */
+struct queued_metric {
+	struct list_head list;
+	char *name;
+	char *unit;
+	double val;
+	enum metric_threshold_classify thresh;
+	int aggr_idx;
+};
+
+/**
+ * struct queued_event - In-memory record of a buffered counter event.
+ * @list: Linked list node for queueing.
+ * @evsel: The associated performance event selector.
+ * @name: The uniquely formatted/resolved event name.
+ * @val: Raw aggregated counter value.
+ * @ena: Enabled time for multiplexing percentage.
+ * @run: Running time for multiplexing percentage.
+ * @stdev_pct: Standard deviation percentage across repeated runs.
+ * @aggr_idx: Aggregation index.
+ * @is_metricgroup: Whether this represents a unified metricgroup header.
+ * @metrics_list: Linked list head containing nested queued_metric structures.
+ */
+struct queued_event {
+	struct list_head list;
+	struct evsel *evsel;
+	char *name;
+	u64 val, ena, run;
+	double stdev_pct;
+	int aggr_idx;
+	bool is_metricgroup;
+	struct list_head metrics_list;
+};
+
+/**
+ * struct std_print_state - Print state context for Standard console output.
+ * @fp: File descriptor to output to.
+ * @timestamp: Formatted interval timestamp (optional).
+ * @events_list: Linked list head containing queued_event nodes.
+ * @current_event: Pointer to the currently active event being printed.
+ *                 Serves as a temporary bridge to associate streaming metrics back to
+ *                 their parent event node during list buffering. This relies on a
+ *                 strict temporal coupling in the traversal driver: the driver always
+ *                 invokes print_metric() callbacks for a counter synchronously and
+ *                 immediately after its print_event() callback, prior to advancing
+ *                 to the next event or aggregation node. This pointer is completely
+ *                 private to standard printing, keeping the traversal driver decoupled
+ *                 and preserving strict encapsulation.
+ * @target: target query parameters for header printout.
+ * @argc: Command argument count.
+ * @argv: Command argument values.
+ */
+struct std_print_state {
+	FILE *fp;
+	char timestamp[64];
+	struct list_head events_list;
+	struct queued_event *current_event;
+	const struct target *target;
+	int argc;
+	const char **argv;
+};
+
+/**
+ * struct std_metric_only_print_state - Metric-only print state context for Standard console output.
+ * @fp: File descriptor to output to.
+ * @queued_metrics: Linked list head containing queued_metric nodes.
+ * @timestamp: Formatted interval timestamp (optional).
+ * @target: target query parameters.
+ * @argc: Command argument count.
+ * @argv: Command argument values.
+ */
+struct std_metric_only_print_state {
+	FILE *fp;
+	struct list_head queued_metrics;
+	char timestamp[64];
+	const struct target *target;
+	int argc;
+	const char **argv;
+};
+
+/**
+ * print_aggr_id_std - Print the aggregation prefix for STD format.
+ *
+ * Uses the unified perf_stat__get_aggr_id_char helper to format the base
+ * aggregation string, and pads it dynamically using aggr_header_lens.
+ */
+static void print_aggr_id_std(const struct perf_stat_config *config, FILE *output,
+			      struct evsel *evsel, struct aggr_cpu_id id, int aggr_nr)
+{
+	char buf[128];
+
+	if (perf_stat__get_aggr_id_char(config, evsel, id, buf, sizeof(buf)) < 0)
+		return;
+
+	if (config->aggr_mode == AGGR_NONE) {
+		if (evsel->percore && !config->percore_show_thread) {
+			fprintf(output, "%-*s ", aggr_header_lens[AGGR_CORE], buf);
+		} else if (id.cpu.cpu > -1) {
+			/* For CPU none mode, prepend "CPU" during console print */
+			char cpu_buf[160];
+			snprintf(cpu_buf, sizeof(cpu_buf), "CPU%s", buf);
+			fprintf(output, "%-*s ", aggr_header_lens[AGGR_NONE], cpu_buf);
+		}
+		return;
+	}
+
+	if (config->aggr_mode == AGGR_THREAD) {
+		fprintf(output, "%-*s ", aggr_header_lens[AGGR_THREAD], buf);
+		return;
+	}
+
+	/* Socket/Die/Node/Cache/Cluster modes print base ID and aggr count */
+	fprintf(output, "%-s %*d ", buf, 4, aggr_nr);
+}
+
+/**
+ * should_skip_zero_counter - Check if a zero-valued counter should be skipped.
+ *
+ * Implemented locally for standard console formatting.
+ */
+static bool should_skip_zero_counter(const struct perf_stat_config *config, struct evsel *counter,
+				     int aggr_idx)
+{
+	struct perf_cpu cpu;
+	unsigned int idx;
+	struct aggr_cpu_id id;
+
+	if (verbose == 0 && counter->skippable && !counter->supported)
+		return true;
+
+	if (config->metric_only)
+		return false;
+
+	if (config->aggr_mode == AGGR_THREAD && config->system_wide)
+		return true;
+
+	if (!config->aggr_map || !config->aggr_get_id)
+		return false;
+
+	id = config->aggr_map->map[aggr_idx];
+
+	if (evsel__is_tool(counter)) {
+		struct aggr_cpu_id own_id = config->aggr_get_id((struct perf_stat_config *)config,
+								(struct perf_cpu){ .cpu = 0 });
+
+		return !aggr_cpu_id__equal(&id, &own_id);
+	}
+
+	perf_cpu_map__for_each_cpu(cpu, idx, counter->core.cpus) {
+		struct aggr_cpu_id own_id =
+			config->aggr_get_id((struct perf_stat_config *)config, cpu);
+
+		if (aggr_cpu_id__equal(&id, &own_id))
+			return false;
+	}
+	return true;
+}
+
+/*
+ * Standard (STD) Output Callbacks - Normal Mode
+ */
+
+static int std_print_start(void *ctx, const struct perf_stat_config *config __maybe_unused)
+{
+	struct std_print_state *ps = ctx;
+
+	INIT_LIST_HEAD(&ps->events_list);
+	ps->current_event = NULL;
+	return 0;
+}
+
+static int std_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)
+{
+	struct std_print_state *ps = ctx;
+	struct queued_event *ev;
+
+	/* Skip zero counters locally in STD callbacks if they qualify */
+	if (val == 0 && should_skip_zero_counter(config, evsel, aggr_idx))
+		return 0;
+
+	ev = malloc(sizeof(*ev));
+	if (!ev)
+		return -ENOMEM;
+
+	ev->name = strdup(evsel__name(evsel));
+	if (!ev->name) {
+		free(ev);
+		return -ENOMEM;
+	}
+
+	ev->evsel = evsel;
+	ev->val = val;
+	ev->ena = ena;
+	ev->run = run;
+	ev->stdev_pct = stdev_pct;
+	ev->aggr_idx = aggr_idx;
+	INIT_LIST_HEAD(&ev->metrics_list);
+
+	list_add_tail(&ev->list, &ps->events_list);
+	ps->current_event = ev;
+
+	return 0;
+}
+
+static int std_print_metric(void *ctx, const struct perf_stat_config *config __maybe_unused,
+			    struct evsel *evsel __maybe_unused, int aggr_idx __maybe_unused,
+			    const char *name, const char *unit, double val,
+			    enum metric_threshold_classify thresh)
+{
+	struct std_print_state *ps = ctx;
+	struct queued_metric *b;
+
+	if (!ps->current_event)
+		return 0;
+
+	if (evsel != ps->current_event->evsel) {
+		pr_err("decoupled print engine: temporal coupling violation: evsel mismatch!\n");
+		return -EINVAL;
+	}
+
+	b = malloc(sizeof(*b));
+	if (!b)
+		return -ENOMEM;
+
+	b->name = strdup(name);
+	if (!b->name) {
+		free(b);
+		return -ENOMEM;
+	}
+
+	if (unit && unit[0]) {
+		b->unit = strdup(unit);
+		if (!b->unit) {
+			free(b->name);
+			free(b);
+			return -ENOMEM;
+		}
+	} else {
+		b->unit = NULL;
+	}
+
+	b->val = val;
+	b->thresh = thresh;
+	list_add_tail(&b->list, &ps->current_event->metrics_list);
+
+	return 0;
+}
+
+#define USEC_PER_SEC 1000000ULL
+#define NSEC_PER_SEC 1000000000ULL
+
+static double timeval2double(struct timeval *t)
+{
+	return t->tv_sec + (double)t->tv_usec / USEC_PER_SEC;
+}
+
+static void print_footer_std(const struct perf_stat_config *config)
+{
+	double avg = avg_stats(config->walltime_nsecs_stats) / NSEC_PER_SEC;
+	FILE *output = config->output;
 
-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)
+	if (config->interval)
+		return;
+
+	if (!config->null_run)
+		fprintf(output, "\n");
+
+	if (config->run_count == 1) {
+		fprintf(output, " %17.9f seconds time elapsed", avg);
+
+		if (config->ru_display) {
+			double ru_utime =
+				timeval2double((struct timeval *)&config->ru_data.ru_utime);
+			double ru_stime =
+				timeval2double((struct timeval *)&config->ru_data.ru_stime);
+
+			fprintf(output, "\n\n");
+			fprintf(output, " %17.9f seconds user\n", ru_utime);
+			fprintf(output, " %17.9f seconds sys\n", ru_stime);
+		}
+	} else {
+		double sd = stddev_stats(config->walltime_nsecs_stats) / NSEC_PER_SEC;
+		fprintf(output, " %17.9f +- %-17.9f seconds time elapsed", avg, sd);
+	}
+	fprintf(output, "\n");
+}
+
+/**
+ * print_header_std - Print the header prefix matching old API.
+ *
+ * Copied and adapted from stat-display.c.
+ */
+static void print_header_std(const struct perf_stat_config *config, const struct target *target,
+			     int argc, const char **argv)
+{
+	FILE *output = config->output;
+	int i;
+
+	fprintf(output, "\n");
+	fprintf(output, " Performance counter stats for ");
+	if (target->bpf_str)
+		fprintf(output, "\'BPF program(s) %s", target->bpf_str);
+	else if (target->system_wide)
+		fprintf(output, "\'system wide");
+	else if (target->cpu_list)
+		fprintf(output, "\'CPU(s) %s", target->cpu_list);
+	else if (!target__has_task(target)) {
+		fprintf(output, "\'%s", argv ? argv[0] : "pipe");
+		for (i = 1; argv && (i < argc); i++)
+			fprintf(output, " %s", argv[i]);
+	} else if (target->pid)
+		fprintf(output, "process id \'%s", target->pid);
+	else
+		fprintf(output, "thread id \'%s", target->tid);
+
+	fprintf(output, "\'");
+	if (config->run_count > 1)
+		fprintf(output, " (%d runs)", config->run_count);
+	fprintf(output, ":\n\n");
+}
+
+static int std_print_end(void *ctx, const struct perf_stat_config *config)
+{
+	struct std_print_state *ps = ctx;
+	struct queued_event *ev, *tmp_ev;
+	struct queued_metric *met, *tmp_met;
+	FILE *out = ps->fp;
+	bool first;
+	const char *last_mg_name = NULL;
+	const struct perf_pmu *last_pmu = NULL;
+	int last_aggr_idx = -1;
+
+	/* Print the formatted header prefix (only in non-interval mode) */
+	if (!config->interval)
+		print_header_std(config, ps->target, ps->argc, ps->argv);
+
+	list_for_each_entry_safe(ev, tmp_ev, &ps->events_list, list) {
+		struct evsel *evsel = ev->evsel;
+		double sc = evsel->scale;
+		const char *fmt;
+		const char *bad_count = evsel->supported ? CNTR_NOT_COUNTED : CNTR_NOT_SUPPORTED;
+		struct metric_event *me =
+			metricgroup__lookup(&evsel->evlist->metric_events, evsel, false);
+		bool is_metricgroup = false;
+		bool skip_header = false;
+		char full_name[128] = "";
+
+		if (me && me->is_default && !evsel->default_show_events) {
+			struct metric_expr *mexp =
+				list_first_entry(&me->head, struct metric_expr, nd);
+			const char *mg_name = mexp->default_metricgroup_name;
+			bool need_full_name = perf_pmus__num_core_pmus() > 1;
+
+			if (need_full_name && evsel->pmu)
+				scnprintf(full_name, sizeof(full_name), "%s (%s)", mg_name,
+					  evsel->pmu->name);
+			else
+				scnprintf(full_name, sizeof(full_name), "%s", mg_name);
+			is_metricgroup = true;
+
+			if (last_mg_name && !strcmp(last_mg_name, mg_name) &&
+			    last_pmu == evsel->pmu && last_aggr_idx == ev->aggr_idx) {
+				skip_header = true;
+			}
+			last_mg_name = mg_name;
+			last_pmu = evsel->pmu;
+			last_aggr_idx = ev->aggr_idx;
+		}
+
+		/* Print interval timestamp if configured */
+		if (config->interval && ps->timestamp[0] && !skip_header)
+			fprintf(out, "%s", ps->timestamp);
+
+		/* 1. Print aggregation prefix first (if we don't skip header) */
+		if (!skip_header && config->aggr_map && ev->aggr_idx >= 0) {
+			struct aggr_cpu_id id = config->aggr_map->map[ev->aggr_idx];
+			int aggr_nr = 0;
+			if (evsel->stats && evsel->stats->aggr) {
+				aggr_nr = evsel->stats->aggr[ev->aggr_idx].nr;
+			}
+			print_aggr_id_std(config, out, evsel, id, aggr_nr);
+		}
+
+		/* 2. Print event value (scaled) or spaces if metricgroup */
+		if (is_metricgroup) {
+			if (!skip_header) {
+				int n = fprintf(out, " %*s", EVNAME_LEN, full_name);
+				fprintf(out, "%*s", MGROUP_LEN + config->unit_width + 2 - n, "");
+			}
+		} else {
+			if (config->big_num)
+				fmt = floor(sc) != sc ? "%'*.2f " : "%'*.0f ";
+			else
+				fmt = floor(sc) != sc ? "%*.2f " : "%*.0f ";
+
+			if (ev->run == 0 || ev->ena == 0) {
+				fprintf(out, "%*s ", COUNTS_LEN, bad_count);
+			} else {
+				double scaled = (double)ev->val;
+				double avg;
+				if (ev->ena < ev->run) {
+					scaled = (double)ev->val * ev->run / ev->ena;
+				}
+				avg = scaled * sc;
+				fprintf(out, fmt, COUNTS_LEN, avg);
+			}
+
+			/* 3. Print unit */
+			if (evsel->unit) {
+				fprintf(out, "%-*s ", config->unit_width, evsel->unit);
+			} else {
+				if (config->unit_width > 0)
+					fprintf(out, "%-*s ", config->unit_width, "");
+			}
+
+			/* 4. Print event name */
+			fprintf(out, "%-*s", EVNAME_LEN, evsel__name(evsel));
+
+			/* If there are no metrics, print noise and multiplexing percentage */
+			if (list_empty(&ev->metrics_list)) {
+				if (ev->stdev_pct)
+					fprintf(out, "  ( +-%6.2f%% )", ev->stdev_pct);
+				if (ev->run != ev->ena)
+					fprintf(out, "  (%.2f%%)", 100.0 * ev->run / ev->ena);
+			}
+		}
+
+		first = true;
+		list_for_each_entry_safe(met, tmp_met, &ev->metrics_list, list) {
+			const char *color = metric_threshold_classify__color(met->thresh);
+			char unit_name[128];
+			const char *m_fmt = (met->unit && met->unit[0]) ? "%8.1f" : "%8.2f";
+
+			if (met->unit && met->unit[0]) {
+				snprintf(unit_name, sizeof(unit_name), "%s  %s", met->unit,
+					 met->name);
+			} else {
+				snprintf(unit_name, sizeof(unit_name), "%s", met->name);
+			}
+
+			if (first) {
+				if (skip_header) {
+					if (config->interval && ps->timestamp[0])
+						fprintf(out, "%s", ps->timestamp);
+					if (config->aggr_map && ev->aggr_idx >= 0) {
+						struct aggr_cpu_id id =
+							config->aggr_map->map[ev->aggr_idx];
+						int aggr_nr = 0;
+						if (evsel->stats && evsel->stats->aggr) {
+							aggr_nr =
+								evsel->stats->aggr[ev->aggr_idx].nr;
+						}
+						print_aggr_id_std(config, out, evsel, id, aggr_nr);
+					}
+					fprintf(out, "%*s# ",
+						COUNTS_LEN + EVNAME_LEN + config->unit_width + 3,
+						"");
+				} else {
+					fprintf(out, " # ");
+				}
+				first = false;
+			} else {
+				/* Align subsequent metric lines */
+				fprintf(out, "\n");
+				if (config->interval && ps->timestamp[0])
+					fprintf(out, "%s", ps->timestamp);
+				if (config->aggr_map && ev->aggr_idx >= 0) {
+					struct aggr_cpu_id id = config->aggr_map->map[ev->aggr_idx];
+					int aggr_nr = 0;
+					if (evsel->stats && evsel->stats->aggr) {
+						aggr_nr = evsel->stats->aggr[ev->aggr_idx].nr;
+					}
+					print_aggr_id_std(config, out, evsel, id, aggr_nr);
+				}
+				fprintf(out, "%*s# ",
+					COUNTS_LEN + EVNAME_LEN + config->unit_width + 3, "");
+			}
+
+			if (color && color[0]) {
+				color_fprintf(out, color, m_fmt, met->val);
+			} else {
+				fprintf(out, m_fmt, met->val);
+			}
+			/* Print the metric unit and name left-aligned padded to METRIC_LEN - n - 1 = 26 */
+			fprintf(out, " %-26s", unit_name);
+
+			/* If this is the last metric in the list, print noise and multiplexing percentage */
+			if (list_is_last(&met->list, &ev->metrics_list)) {
+				if (ev->stdev_pct)
+					fprintf(out, "  ( +-%6.2f%% )", ev->stdev_pct);
+				if (ev->run != ev->ena)
+					fprintf(out, "  (%.2f%%)", 100.0 * ev->run / ev->ena);
+			}
+
+			list_del(&met->list);
+			free(met->name);
+			free(met->unit);
+			free(met);
+		}
+		fprintf(out, "\n");
+
+		list_del(&ev->list);
+		free(ev->name);
+		free(ev);
+	}
+	print_footer_std(config);
+	return 0;
+}
+
+static const struct perf_stat_print_callbacks std_print_callbacks = {
+	.print_start = std_print_start,
+	.print_end = std_print_end,
+	.print_event = std_print_event,
+	.print_metric = std_print_metric,
+};
+
+/*
+ * Standard (STD) Output Callbacks - Metric-Only Mode
+ */
+
+static int std_metric_only_print_start(void *ctx,
+				       const struct perf_stat_config *config __maybe_unused)
+{
+	struct std_metric_only_print_state *ps = ctx;
+	INIT_LIST_HEAD(&ps->queued_metrics);
+	return 0;
+}
+
+static int std_metric_only_print_metric(void *ctx,
+					const struct perf_stat_config *config __maybe_unused,
+					struct evsel *evsel __maybe_unused, int aggr_idx,
+					const char *name, const char *unit, double val,
+					enum metric_threshold_classify thresh)
 {
+	struct std_metric_only_print_state *ps = ctx;
+	struct queued_metric *b = malloc(sizeof(*b));
+
+	if (!b)
+		return -ENOMEM;
+
+	b->name = strdup(name);
+	if (!b->name) {
+		free(b);
+		return -ENOMEM;
+	}
+
+	if (unit && unit[0]) {
+		b->unit = strdup(unit);
+		if (!b->unit) {
+			free(b->name);
+			free(b);
+			return -ENOMEM;
+		}
+	} else {
+		b->unit = NULL;
+	}
+
+	b->val = val;
+	b->thresh = thresh;
+	b->aggr_idx = aggr_idx;
+	list_add_tail(&b->list, &ps->queued_metrics);
+
 	return 0;
 }
+
+static int std_metric_only_print_end(void *ctx, const struct perf_stat_config *config)
+{
+	struct std_metric_only_print_state *ps = ctx;
+	struct queued_metric *b, *tmp;
+	FILE *out = ps->fp;
+	int first_aggr = -1;
+	int current_aggr = -1;
+	const char *color;
+	char *str;
+	int mlen;
+	int ret = 0;
+	int err;
+
+	if (list_empty(&ps->queued_metrics))
+		return 0;
+
+	/* Print the formatted header prefix */
+	if (!config->interval)
+		print_header_std(config, ps->target, ps->argc, ps->argv);
+
+	first_aggr = list_first_entry(&ps->queued_metrics, struct queued_metric, list)->aggr_idx;
+
+	/* Print headers */
+	list_for_each_entry(b, &ps->queued_metrics, list) {
+		if (b->aggr_idx == first_aggr) {
+			char *header_name;
+			if (b->unit && b->unit[0]) {
+				err = asprintf(&header_name, "%s  %s", b->unit, b->name);
+			} else {
+				header_name = strdup(b->name);
+				err = header_name ? 0 : -1;
+			}
+			if (err < 0) {
+				ret = -ENOMEM;
+				goto cleanup;
+			}
+			fprintf(out, "%*s ", config->metric_only_len, header_name);
+			free(header_name);
+		}
+	}
+	fprintf(out, "\n\n");
+
+	/* Print values */
+	list_for_each_entry_safe(b, tmp, &ps->queued_metrics, list) {
+		if (b->aggr_idx != current_aggr) {
+			if (current_aggr != -1)
+				fprintf(out, "\n");
+			current_aggr = b->aggr_idx;
+			if (config->interval && ps->timestamp[0])
+				fprintf(out, "%s", ps->timestamp);
+		}
+		color = metric_threshold_classify__color(b->thresh);
+		mlen = config->metric_only_len;
+
+		if (color && color[0]) {
+			err = asprintf(&str, "%s%.1f%s", color, b->val, PERF_COLOR_RESET);
+			mlen += strlen(color) + sizeof(PERF_COLOR_RESET) - 1;
+		} else {
+			err = asprintf(&str, "%.1f", b->val);
+		}
+		if (err < 0) {
+			ret = -ENOMEM;
+			goto cleanup;
+		}
+		fprintf(out, "%*s ", mlen, str);
+		free(str);
+
+		list_del(&b->list);
+		free(b->name);
+		free(b->unit);
+		free(b);
+	}
+	print_footer_std(config);
+	return 0;
+
+cleanup:
+	list_for_each_entry_safe(b, tmp, &ps->queued_metrics, list) {
+		list_del(&b->list);
+		free(b->name);
+		free(b->unit);
+		free(b);
+	}
+	return ret;
+}
+
+static const struct perf_stat_print_callbacks std_metric_only_print_callbacks = {
+	.print_start = std_metric_only_print_start,
+	.print_end = std_metric_only_print_end,
+	.print_event = NULL,
+	.print_metric = std_metric_only_print_metric,
+};
+
+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)
+{
+	struct std_print_state ps = {
+		.fp = config->output,
+		.target = target,
+		.argc = argc,
+		.argv = argv,
+	};
+
+	if (config->metric_only) {
+		struct std_metric_only_print_state mops = {
+			.fp = config->output,
+			.target = target,
+			.argc = argc,
+			.argv = argv,
+		};
+		if (config->interval && ts) {
+			scnprintf(mops.timestamp, sizeof(mops.timestamp), "%6lu.%09lu ",
+				  (unsigned long)ts->tv_sec, ts->tv_nsec);
+		} else {
+			mops.timestamp[0] = '\0';
+		}
+		return perf_stat__print_cb(evlist, config, target, ts, argc, argv,
+					   &std_metric_only_print_callbacks, &mops);
+	} else {
+		if (config->interval && ts) {
+			scnprintf(ps.timestamp, sizeof(ps.timestamp), "%6lu.%09lu ",
+				  (unsigned long)ts->tv_sec, ts->tv_nsec);
+		} else {
+			ps.timestamp[0] = '\0';
+		}
+		return perf_stat__print_cb(evlist, config, target, ts, argc, argv,
+					   &std_print_callbacks, &ps);
+	}
+}
-- 
2.54.0.794.g4f17f83d09-goog


  parent reply	other threads:[~2026-05-22 22:34 UTC|newest]

Thread overview: 45+ 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 ` Ian Rogers [this message]
2026-05-22 22:54   ` [RFC PATCH v1 02/14] perf stat: Implement standard console (STD) formatting callbacks 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

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=20260522223342.2393553-3-irogers@google.com \
    --to=irogers@google.com \
    --cc=acme@kernel.org \
    --cc=adrian.hunter@intel.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 a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox