From nobody Sat Jul 25 16:18:33 2026 Received: from mail-pg1-f202.google.com (mail-pg1-f202.google.com [209.85.215.202]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 3C381382F1F for ; Thu, 16 Jul 2026 07:03:15 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.215.202 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1784185397; cv=none; b=WhMClTQ6Cn560H0p1rHZa9OAPq7RkZ/vEU+qDN9YpIu72qM0BO+NzElnwkwsIpoHp72Z09Pf268GiEJJwI9wExALGr6EAbJGs8nxHOwKFHLUpZkouDOFFcjx7NyBof+Ctmn+JkoLST10AU6GO8/E2OvHK38F4IJjS8ggZIQvfTU= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1784185397; c=relaxed/simple; bh=gX8FH3xl5Ce/HbHRdrPrPqHhqmei7NAZHebeN92xnNM=; h=Date:In-Reply-To:Mime-Version:References:Message-ID:Subject:From: To:Cc:Content-Type; b=phMHVaxfCUgD7tg4A/YaKaCT859EBHPrqsciNINEJ7e8W9W9eLPZt7iig6i1HzcZTlimsk/gKKorMpyYaUadFD7Yf3LhqUp+d8HXIOc+S2V5a+lxWZmYHj5Ae3jtv6RK7x6GeIT2evzwXG6vDZyDIFDO5nAUuo3HvrUMqqAcaWY= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=google.com; spf=pass smtp.mailfrom=flex--irogers.bounces.google.com; dkim=pass (2048-bit key) header.d=google.com header.i=@google.com header.b=r60NTxmK; arc=none smtp.client-ip=209.85.215.202 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=google.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=flex--irogers.bounces.google.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=google.com header.i=@google.com header.b="r60NTxmK" Received: by mail-pg1-f202.google.com with SMTP id 41be03b00d2f7-c9fe4c5eb39so4565816a12.1 for ; Thu, 16 Jul 2026 00:03:15 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=20251104; t=1784185394; x=1784790194; darn=vger.kernel.org; h=content-type:cc:to:from:subject:message-id:references:mime-version :in-reply-to:date:from:to:cc:subject:date:message-id:reply-to :content-type; bh=iB1uN90j5N0d/SOwbL4gMa3WOJnzO1ag8EpJtfMr9fg=; b=r60NTxmKxA9wEN7MuN2Xd4OEfxvtnM3nekdbtcd5grHDGCIgkwk+DCSCv9xeWJOiCg JZgFWNFkn0wHprhMt2hvNamGX4i6SHx2TLQOjIbAZIHCZU4oL/yvVe2SJJUaE+/bFidY Jr8QDZdwM9tipkwFolV9jXMb1UCgIJ2qb8pu7jBpkqfx5G4RVH3WNmz+JMqeqU3gxNP0 R77hpyV3Ou0CLjuIlcsbxzhzc3rTmakKBJrlpdTjwTu3AWDqfOOdcgcX6s5SmwSWCIax RDmuJbpgVjHUmW9tvBk5DOXcnP7Oz48C3h4OhKrTRz5ChUJeijEmLcqIaDz1mSqh8lec 5nFw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1784185394; x=1784790194; h=content-type:cc:to:from:subject:message-id:references:mime-version :in-reply-to:date:x-gm-message-state:from:to:cc:subject:date :message-id:reply-to:content-type; bh=iB1uN90j5N0d/SOwbL4gMa3WOJnzO1ag8EpJtfMr9fg=; b=eQhCWGlDz4Tki9/hcYBRrY9LrW+WZ4QeeTEJSx4vDOe+Q3KfsAQZRcB3SjqfE3PExV jNf6OLLLGSZAVg4T+3QA/HdxROUjNVSr/t12cZcGV5uiElw2G/kjBgtL5aEYuIoatTNK XXJVlIk4xOnWXzjuYD/k+yNtj7KheIDoXIqi75/Y1J8KDXvrs06Ug2dHhBKzQqNlV45K RgK9PyANAdSqE/Hgk1oJsBj2viFxVCOJRWMaCRweannV8egR+BblkXkYwLpXM2P0wruG uSY4juypDJoJupro/mca3wsuZ5nTf6fksqQdQDaA4pbMGWQUCyi5jgKBiTkK1zn1wvLM TYPw== X-Forwarded-Encrypted: i=1; AHgh+RpnyAOyOK1tQxJ07Thu3tY1OvYZu/l4gPhTpcWFZSFeTqdvJGouQgoYOvHBw6iWg72I89kxj5cAKmk9k7Y=@vger.kernel.org X-Gm-Message-State: AOJu0YxF88tQE51hXhUHJLJSXMoe3UPDcdVtEsTmI9IlY5CqPXhVowQj zkgdef5OyrgWtdm6ZWZ1fXUH2qI9ft55P8vPSTY5LgZ2UH5HlGhnT7erL0cx0BDTSHcx7tHcXE3 dhm5dPCLQzg== X-Received: from dycnr7-n1.prod.google.com ([2002:a05:7300:e9c7:10b0:2f9:af7:504e]) (user=irogers job=prod-delivery.src-stubby-dispatcher) by 2002:a05:6a20:6a25:b0:3bf:49c8:f69 with SMTP id adf61e73a8af0-3c36c2ed6a5mr6505235637.39.1784185394273; Thu, 16 Jul 2026 00:03:14 -0700 (PDT) Date: Thu, 16 Jul 2026 00:02:50 -0700 In-Reply-To: <20260716070303.507066-1-irogers@google.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: Mime-Version: 1.0 References: <20260716043223.388233-1-irogers@google.com> <20260716070303.507066-1-irogers@google.com> X-Mailer: git-send-email 2.55.0.141.g00534a21ce-goog Message-ID: <20260716070303.507066-2-irogers@google.com> Subject: [PATCH v4 01/14] perf stat: Introduce core generic print traversal engine and header stubs From: Ian Rogers To: irogers@google.com, acme@kernel.org, ctshao@google.com, namhyung@kernel.org 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 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" 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 Assisted-by: Antigravity:gemini-3.5-flash Acked-by: Chun-Tse Shao --- tools/perf/builtin-stat.c | 96 +++--- tools/perf/util/Build | 4 + tools/perf/util/stat-display.c | 12 +- 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 | 500 ++++++++++++++++++++++++++++++ tools/perf/util/stat-print.h | 137 ++++++++ tools/perf/util/stat.h | 6 + 9 files changed, 744 insertions(+), 50 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 */ =20 +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "asm/bug.h" #include "builtin.h" +#include "util/affinity.h" +#include "util/bpf_counter.h" #include "util/cgroup.h" -#include -#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 -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include =20 #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 =3D -1; static int detailed_run =3D 0; static bool transaction_run; +static bool use_perf_stat_print; static bool topdown_run =3D false; static bool smi_cost =3D false; static bool smi_reset =3D false; @@ -1094,7 +1097,10 @@ static void print_counters(struct timespec *ts, int = argc, const char **argv) if (quiet) return; =20 - 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= ); } =20 static volatile sig_atomic_t signr =3D -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 +=3D counts.o perf-util-y +=3D stat.o perf-util-y +=3D stat-shadow.o perf-util-y +=3D stat-display.o +perf-util-y +=3D stat-print.o +perf-util-y +=3D stat-print-std.o +perf-util-y +=3D stat-print-csv.o +perf-util-y +=3D stat-print-json.o perf-util-y +=3D perf_api_probe.o perf-util-y +=3D record.o perf-util-y +=3D srcline.o diff --git a/tools/perf/util/stat-display.c b/tools/perf/util/stat-display.c index f94f1324d24a..ab67c80a895e 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 =20 -static int aggr_header_lens[] =3D { +const int aggr_header_lens[] =3D { [AGGR_CORE] =3D 18, [AGGR_CACHE] =3D 22, [AGGR_CLUSTER] =3D 20, @@ -49,7 +49,7 @@ static int aggr_header_lens[] =3D { [AGGR_GLOBAL] =3D 0, }; =20 -static const char *aggr_header_csv[] =3D { +const char *aggr_header_csv[] =3D { [AGGR_CORE] =3D "core,ctrs,", [AGGR_CACHE] =3D "cache,ctrs,", [AGGR_CLUSTER] =3D "cluster,ctrs,", @@ -61,7 +61,7 @@ static const char *aggr_header_csv[] =3D { [AGGR_GLOBAL] =3D "" }; =20 -static const char *aggr_header_std[] =3D { +const char *aggr_header_std[] =3D { [AGGR_CORE] =3D "core", [AGGR_CACHE] =3D "cache", [AGGR_CLUSTER] =3D "cluster", @@ -890,9 +890,9 @@ static void printout(struct perf_stat_config *config, s= truct outstate *os, * Return: %true if the value should NOT be printed, %false if the value * needs to be printed like "" or "". */ -static bool should_skip_zero_counter(struct perf_stat_config *config, - struct evsel *counter, - const struct aggr_cpu_id *id) +bool should_skip_zero_counter(struct perf_stat_config *config, + struct evsel *counter, + const struct aggr_cpu_id *id) { struct perf_cpu cpu; unsigned int idx; 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 + +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 + +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 + +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..419fecfd9198 --- /dev/null +++ b/tools/perf/util/stat-print.c @@ -0,0 +1,500 @@ +// SPDX-License-Identifier: GPL-2.0 +#include "stat-print.h" +#include "cgroup.h" + +#include +#include +#include +#include +#include + +#include + +#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, str= uct 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.cor= e); + case AGGR_CACHE: + return scnprintf(buf, buf_size, "S%d-D%d-L%d-ID%d", id.socket, id.die, i= d.cache_lvl, + id.cache); + case AGGR_CLUSTER: + return scnprintf(buf, buf_size, "S%d-D%d-CLS%d", id.socket, id.die, id.c= luster); + 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.co= re); + 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] =3D '\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 =3D evsel__tool_event(evsel); + int aggr_idx; + + if (event !=3D TOOL_PMU__EVENT_DURATION_TIME && event !=3D TOOL_PMU__EVEN= T_USER_TIME && + event !=3D TOOL_PMU__EVENT_SYSTEM_TIME) + return false; + + if (config && config->aggr_map) { + for (aggr_idx =3D 0; aggr_idx < config->aggr_map->nr; aggr_idx++) { + if (config->aggr_map->map[aggr_idx].cpu.cpu =3D=3D 0) { + *tool_aggr_idx =3D aggr_idx; + return true; + } + } + pr_debug("Unexpected CPU0 missing in aggregation for tool event.\n"); + } + *tool_aggr_idx =3D 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 str= uct metric_expr *mexp, + struct evsel *evsel, struct expr_parse_ctx *pctx, int aggr_idx) +{ + struct evsel *const *metric_events =3D mexp->metric_events; + struct metric_ref *metric_refs =3D mexp->metric_refs; + int i; + + for (i =3D 0; metric_events[i]; i++) { + int source_count =3D 0, tool_aggr_idx; + bool is_tool_time =3D + tool_pmu__is_time_event(config, metric_events[i], &tool_aggr_idx); + struct perf_stat_evsel *ps =3D 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 !=3D evsel->metric_leade= r->pmu && + mexp->metric_events[i]->pmu =3D=3D evsel->metric_leader->pmu) { + struct evsel *pos; + + evlist__for_each_entry(evsel->evlist, pos) { + if (pos->pmu !=3D evsel->pmu) + continue; + if (pos->metric_leader !=3D mexp->metric_events[i]) + continue; + ps =3D pos->stats; + source_count =3D 1; + break; + } + } + /* Time events are always on CPU0, the first aggregation index. */ + if (!ps || !metric_events[i]->supported || !ps->aggr || + aggr_idx < 0 || (is_tool_time && tool_aggr_idx < 0)) { + val =3D NAN; + source_count =3D 0; + } else { + struct perf_stat_aggr *aggr =3D + &ps->aggr[is_tool_time ? tool_aggr_idx : + aggr_idx]; + + if (aggr->counts.run =3D=3D 0) { + val =3D NAN; + source_count =3D 0; + } else { + val =3D aggr->counts.val; + if (is_tool_time) { + /* Convert time event nanoseconds to seconds. */ + val *=3D 1e-9; + } + if (!source_count) + source_count =3D evsel__source_count(metric_events[i]); + } + } + n =3D 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 =3D 0; metric_refs && metric_refs[j].metric_name; j++) { + int ret =3D 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_m= etric + * 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(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 =3D mexp->metric_name; + const char *metric_expr =3D mexp->metric_expr; + const char *metric_threshold =3D mexp->metric_threshold; + const char *metric_unit =3D mexp->metric_unit; + struct evsel *const *metric_events =3D mexp->metric_events; + int runtime =3D mexp->runtime; + struct expr_parse_ctx *pctx; + double ratio, scale, threshold; + int i; + enum metric_threshold_classify thresh =3D METRIC_THRESHOLD_UNKNOWN; + int ret =3D 0; + + if (!cb->print_metric) + return 0; + + pctx =3D expr__ctx_new(); + if (!pctx) + return -ENOMEM; + + if (config->user_requested_cpu_list) + pctx->sctx.user_requested_cpu_list =3D strdup(config->user_requested_cpu= _list); + pctx->sctx.runtime =3D runtime; + pctx->sctx.system_wide =3D config->system_wide; + i =3D 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) =3D=3D 0) { + char *unit =3D NULL; + + if (metric_threshold && + expr__parse(&threshold, pctx, metric_threshold) =3D=3D 0 && + !isnan(threshold)) { + thresh =3D fpclassify(threshold) =3D=3D FP_ZERO ? METRIC_THRESHOLD_GOO= D : + METRIC_THRESHOLD_BAD; + } + + if (metric_unit && metric_name) { + if (perf_pmu__convert_scale(metric_unit, &unit, + &scale) < 0) + unit =3D NULL; + else + ratio *=3D scale; + + ret =3D cb->print_metric(outer_ctx, config, evsel, + aggr_idx, metric_name, + unit, ratio, thresh); + } else { + ret =3D 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 __maybe_unused is_basic_shadow_metric(const char *name) +{ + static const char *const basic_metrics[] =3D { + "insn_per_cycle", "branch_miss_rate", "branch_frequency", + "cycles_frequency", "page_faults_per_second", "migrations_per_second", + "cs_per_second", "CPUs_utilized", + }; + size_t i; + + if (!name) + return false; + + for (i =3D 0; i < ARRAY_SIZE(basic_metrics); i++) { + if (!strcmp(basic_metrics[i], name)) + return true; + } + return false; +} + +static int perf_stat_print_metricgroup(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 =3D &evsel->evlist->metric_events; + int ret; + + me =3D metricgroup__lookup(metric_events, evsel, false); + if (me =3D=3D NULL) + return 0; + + list_for_each_entry(mexp, &me->head, nd) { + ret =3D 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(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, struct perf_stat_config *co= nfig, + 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 =3D 0; + + evlist__uniquify_evsel_names(evlist, config); + + if (cb->print_start) { + ret =3D 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 =3D counter->stats; + u64 val =3D 0, ena =3D 0, run =3D 0; + + if (ps && ps->aggr) { + val =3D ps->aggr[aggr_idx].counts.val; + ena =3D ps->aggr[aggr_idx].counts.ena; + run =3D ps->aggr[aggr_idx].counts.run; + } + + /* Skip already merged uncore/hybrid events */ + if (config->aggr_mode !=3D AGGR_NONE) { + bool skip_hw =3D evsel__is_hybrid(counter) ? + (config->hybrid_merge && + counter->first_wildcard_match !=3D NULL) : + (counter->first_wildcard_match !=3D NULL); + + if (skip_hw) + continue; + } + + if (perf_stat__skip_metric_event(counter)) + continue; + + if (cb->print_event) { + double stdev_pct =3D 0.0; + + if (ps && ps->res_stats.n > 1) { + stdev_pct =3D rel_stddev_stats( + stddev_stats(&ps->res_stats), val); + } + ret =3D cb->print_event( + ctx, config, counter, aggr_idx, val, + ena, run, stdev_pct, + counter->cgrp ? counter->cgrp->name : NULL); + if (ret) + goto out; + } + + ret =3D 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 =3D counter->stats; + u64 val =3D 0, ena =3D 0, run =3D 0; + + if (ps && ps->aggr) { + val =3D ps->aggr[0].counts.val; + ena =3D ps->aggr[0].counts.ena; + run =3D ps->aggr[0].counts.run; + } + + /* Skip already merged uncore/hybrid events */ + if (config->aggr_mode !=3D AGGR_NONE) { + bool skip_hw =3D evsel__is_hybrid(counter) ? + (config->hybrid_merge && + counter->first_wildcard_match !=3D NULL) : + (counter->first_wildcard_match !=3D NULL); + + if (skip_hw) + continue; + } + + if (perf_stat__skip_metric_event(counter)) + continue; + + if (cb->print_event) { + double stdev_pct =3D 0.0; + + if (ps && ps->res_stats.n > 1) { + stdev_pct =3D rel_stddev_stats( + stddev_stats(&ps->res_stats), val); + } + ret =3D cb->print_event( + ctx, config, counter, 0, val, ena, run, + stdev_pct, + counter->cgrp ? counter->cgrp->name : NULL); + if (ret) + goto out; + } + + ret =3D 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 ye= t\n", + config->aggr_mode); + break; + } + +out: + if (cb->print_end) { + int err =3D cb->print_end(ctx, config); + + if (!ret) + ret =3D err; + } + + return ret; +} + +int perf_stat__print(struct evlist *evlist, struct perf_stat_config *confi= g, + 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..7c0af4541ab8 --- /dev/null +++ b/tools/perf/util/stat-print.h @@ -0,0 +1,137 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef __PERF_STAT_PRINT_H +#define __PERF_STAT_PRINT_H + +#include + +#include "stat.h" + +#define CNTR_NOT_SUPPORTED "" +#define CNTR_NOT_COUNTED "" + +#include +#include + +struct evlist; +struct perf_stat_config; +struct target; +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 ou= tput. + * + * This structure defines the interface for different output formats (e.g., + * Standard, CSV, JSON) to render the collected performance counter statis= tics. + * 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, 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, 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 initializat= ion). + * @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 run= s. + * + * Returns 0 on success, or a negative error code (e.g., -ENOMEM) on fail= ure. + */ + int (*print_event)(void *ctx, struct perf_stat_config *config, + struct evsel *evsel, int aggr_idx, u64 val, u64 ena, + u64 run, double stdev_pct, const char *cgrp); + + /** + * print_metric - Called to buffer a metric value associated with an even= t. + * @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 fail= ure. + */ + int (*print_metric)(void *ctx, 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, str= uct 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, struct perf_stat_config *co= nfig, + const struct target *target, const struct timespec *ts, int argc, + const char **argv, const struct perf_stat_print_callbacks *cb, void *ct= x); + +/** + * 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, struct perf_stat_config *confi= g, + 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, struct perf_stat_config *c= onfig, + const struct target *target, const struct timespec *ts, int argc, + const char **argv); + +int perf_stat__print_csv(struct evlist *evlist, struct perf_stat_config *c= onfig, + const struct target *target, const struct timespec *ts, int argc, + const char **argv); + +int perf_stat__print_json(struct evlist *evlist, 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..ddc895bc93ca 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; }; =20 extern struct perf_stat_config stat_config; @@ -188,6 +190,10 @@ int perf_stat_process_counter(struct perf_stat_config = *config, void perf_stat_merge_counters(struct perf_stat_config *config, struct evli= st *evlist); void perf_stat_process_percore(struct perf_stat_config *config, struct evl= ist *evlist); =20 +bool should_skip_zero_counter(struct perf_stat_config *config, + struct evsel *counter, + const struct aggr_cpu_id *id); + struct perf_tool; union perf_event; struct perf_session; --=20 2.55.0.141.g00534a21ce-goog From nobody Sat Jul 25 16:18:33 2026 Received: from mail-pg1-f201.google.com (mail-pg1-f201.google.com [209.85.215.201]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 3328937AA78 for ; Thu, 16 Jul 2026 07:03:17 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.215.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1784185399; cv=none; b=n8BbDQMemGyNtbIjr9fXQzkyZT7Ev9tX/3oc3AcqI+a7RMvYZbbOJNXaLCkuV10yZIoMEzejGVLhQhdNCSKROzQLWVaK5hefnBYnQZNiEwuApxIu3cHq2id1yr9vnpOgopz34viG9w2oV0N//xipCAoV9Po9HZDqoATKDyuJosA= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1784185399; c=relaxed/simple; bh=UoPNWYvuB4yu48ETNHMwSdzBXN1jJk9KDaMrCYs66s8=; h=Date:In-Reply-To:Mime-Version:References:Message-ID:Subject:From: To:Cc:Content-Type; b=Z4S49OnODZnu71RZBtuNS50r9Tx/KUycWgWq2E5zbP7AAJy0TyZod3vVXI59mYofruBeJ5WghICbB61ynQHUxzK7GNlL1Irvkfj59vbG+bpXKKIWOpzKLeydg6LUyDVOSKQbALvWaWNtFaIhbv1WDjWPqs4S83mnD4TC6goyDvg= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=google.com; spf=pass smtp.mailfrom=flex--irogers.bounces.google.com; dkim=pass (2048-bit key) header.d=google.com header.i=@google.com header.b=qIBHFaDo; arc=none smtp.client-ip=209.85.215.201 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=google.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=flex--irogers.bounces.google.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=google.com header.i=@google.com header.b="qIBHFaDo" Received: by mail-pg1-f201.google.com with SMTP id 41be03b00d2f7-c85798977dcso6589203a12.0 for ; Thu, 16 Jul 2026 00:03:17 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=20251104; t=1784185396; x=1784790196; darn=vger.kernel.org; h=content-type:cc:to:from:subject:message-id:references:mime-version :in-reply-to:date:from:to:cc:subject:date:message-id:reply-to :content-type; bh=y/tOZ52WKKuEZEQB5fKzp7tEjvOQNbz5RWAL985b26U=; b=qIBHFaDoh5fLu1ijJI1VuRMKLcjBA7N0ZTwl2/+WghQqUeZrICCkunNT0gJsYgT2B1 2HjXr9C/EW8PN0N984L9pBUdkZ0MqCQSMtDuGJ2JFvVop8VRzQ4Z7w9lM4TNguwGrN3e gJwwFXBPIpUeo6zM4WxEEk74ez0PXSnIk3+00ZA3d6D75x6epYo8JJCcEE9xgKnW4BbM V6tjaB7Ser1SW8Q7ioWdqyWY/8GK+DRo/v73bTQs6zds4IUt+JANrt0RYTjzVZf/NXtw nfTn/RvvFwSEuswddLiBxy+iMbm2OPc5128g63Tya4SwPkTQCkhFfu40tQj7roGaQTGE e8aA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1784185396; x=1784790196; h=content-type:cc:to:from:subject:message-id:references:mime-version :in-reply-to:date:x-gm-message-state:from:to:cc:subject:date :message-id:reply-to:content-type; bh=y/tOZ52WKKuEZEQB5fKzp7tEjvOQNbz5RWAL985b26U=; b=saJWP2rudSRXzCpqDX2gIjgSDq1p/Npf2lOLswtHwMEdB5BEyySJ5fQrmxmLu+ByuH qxqP2sJzlrnZb8bpWmkzwIp3L3Z7kkMFbDgLGqb4ZazmmMhVU2vag6pBnlIJ1joqLTZ4 LnhOyJAtZ8RG97gkQbcrqC7AgL77R/mLK9HbH4txMREy64nGCFMcn2YQD1mNLfctYc2u OskJu26TmUJC2IRynatnsT4e4JY48L2SZu9FgOc1oA4GKSZ+uH8SQCRlb/W1XX+ymCsO CrFdjlI8sEY+oTk6t8djI4CHS94+K8CpPP7Q12FYgodyX5Ii23J0rGZ1nrmXHuf9pZUG lt1g== X-Forwarded-Encrypted: i=1; AHgh+Rqhtd601CGDIjOc+PajG3ongmYFB0qbpwoMLhEnT944HbHkP4MKgBIOUZdoM3ki/E/Wj2s5WdSPwNS3p7s=@vger.kernel.org X-Gm-Message-State: AOJu0YzvuED9CDieRMmx5AH+5F/Qs8BV6SbX43EzI3KiOtN3T5bl6uuq 3UfN1shDoDJjrDvvVL/+3LaMxK1pZBqTT2nHLKIyQQoLCUD7qOOoJN5lm43j1yY6r2SwVwXUyLz JL8kR/wJv+Q== X-Received: from pfam8.prod.google.com ([2002:aa7:8a08:0:b0:845:48ff:3362]) (user=irogers job=prod-delivery.src-stubby-dispatcher) by 2002:a05:6a00:805:b0:848:2f7a:2e5d with SMTP id d2e1a72fcca58-84a6733d84cmr5539901b3a.76.1784185396225; Thu, 16 Jul 2026 00:03:16 -0700 (PDT) Date: Thu, 16 Jul 2026 00:02:51 -0700 In-Reply-To: <20260716070303.507066-1-irogers@google.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: Mime-Version: 1.0 References: <20260716043223.388233-1-irogers@google.com> <20260716070303.507066-1-irogers@google.com> X-Mailer: git-send-email 2.55.0.141.g00534a21ce-goog Message-ID: <20260716070303.507066-3-irogers@google.com> Subject: [PATCH v4 02/14] perf stat: Implement standard console (STD) formatting callbacks From: Ian Rogers To: irogers@google.com, acme@kernel.org, ctshao@google.com, namhyung@kernel.org 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 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" 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 Assisted-by: Antigravity:gemini-3.5-flash Acked-by: Chun-Tse Shao --- tools/perf/util/stat-print-std.c | 855 ++++++++++++++++++++++++++++++- 1 file changed, 848 insertions(+), 7 deletions(-) diff --git a/tools/perf/util/stat-print-std.c b/tools/perf/util/stat-print-= std.c index a1311f950559..d83410ae3385 100644 --- a/tools/perf/util/stat-print-std.c +++ b/tools/perf/util/stat-print-std.c @@ -1,13 +1,854 @@ // SPDX-License-Identifier: GPL-2.0 -#include "stat-print.h" +#include +#include +#include +#include +#include + +#include +#include +#include +#include + #include +#include +#include + +#include "color.h" +#include "cpumap.h" +#include "debug.h" +#include "evlist.h" +#include "evsel.h" +#include "metricgroup.h" +#include "pmus.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 CGROUP_LEN 16 +#define METRIC_LEN 38 + + + +/** + * 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; + struct evsel *evsel; + 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. + * @cgrp: Cgroup name string (optional). + * @aggr_idx: Aggregation index. + * @is_metricgroup: Whether this represents a unified metricgroup header. + * @metrics_list: Linked list head containing nested queued_metric structu= res. + */ +struct queued_event { + struct list_head list; + struct evsel *evsel; + char *name; + char *cgrp; + 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 outpu= t. + * @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 met= rics back to + * their parent event node during list buffering. This rel= ies on a + * strict temporal coupling in the traversal driver: the d= river always + * invokes print_metric() callbacks for a counter synchron= ously 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 dri= ver 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 fo= r 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; + struct evlist *evlist; +}; + +/** + * 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 =3D=3D 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 =3D=3D 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 ", aggr_header_lens[config->aggr_mode], buf, 4,= aggr_nr); +} + +/** + * should_skip_zero_counter - Check if a zero-valued counter should be ski= pped. + * + * Implemented locally for standard console formatting. + */ +static bool std_should_skip_zero_counter(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 =3D=3D 0 && counter->skippable && !counter->supported) + return true; + + if (config->metric_only) + return false; + + if (config->aggr_mode =3D=3D AGGR_THREAD && config->system_wide) + return true; + + if (aggr_idx < 0 || !config->aggr_map || !config->aggr_get_id) + return false; + + id =3D config->aggr_map->map[aggr_idx]; + + if (evsel__is_tool(counter)) { + struct aggr_cpu_id own_id =3D config->aggr_get_id(config, + (struct perf_cpu){ .cpu =3D 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 =3D config->aggr_get_id(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, struct perf_stat_config *config __ma= ybe_unused) +{ + struct std_print_state *ps =3D ctx; + + INIT_LIST_HEAD(&ps->events_list); + ps->current_event =3D NULL; + return 0; +} + +static int std_print_event(void *ctx, struct perf_stat_config *config, + struct evsel *evsel, int aggr_idx, u64 val, u64 ena, + u64 run, double stdev_pct, const char *cgrp) +{ + struct std_print_state *ps =3D ctx; + struct queued_event *ev; + /* Skip zero counters locally in STD callbacks if they qualify */ + if (val =3D=3D 0 && std_should_skip_zero_counter(config, evsel, aggr_idx)= ) { + ps->current_event =3D NULL; + return 0; + } + + ev =3D malloc(sizeof(*ev)); + if (!ev) + return -ENOMEM; + + ev->name =3D strdup(evsel__name(evsel)); + if (!ev->name) { + free(ev); + return -ENOMEM; + } + + if (cgrp && cgrp[0]) { + ev->cgrp =3D strdup(cgrp); + if (!ev->cgrp) { + free(ev->name); + free(ev); + return -ENOMEM; + } + } else { + ev->cgrp =3D NULL; + } + + ev->evsel =3D evsel; + ev->val =3D val; + ev->ena =3D ena; + ev->run =3D run; + ev->stdev_pct =3D stdev_pct; + ev->aggr_idx =3D aggr_idx; + INIT_LIST_HEAD(&ev->metrics_list); + + list_add_tail(&ev->list, &ps->events_list); + ps->current_event =3D ev; + + return 0; +} + +static int std_print_metric(void *ctx, struct perf_stat_config *config __m= aybe_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 =3D ctx; + struct queued_metric *b; + + if (!ps->current_event) + return 0; + + if (evsel !=3D ps->current_event->evsel) { + pr_err("decoupled print engine: temporal coupling violation: evsel misma= tch!\n"); + return -EINVAL; + } + + b =3D malloc(sizeof(*b)); + if (!b) + return -ENOMEM; + + b->name =3D strdup(name); + if (!b->name) { + free(b); + return -ENOMEM; + } + + if (unit && unit[0]) { + b->unit =3D strdup(unit); + if (!b->unit) { + free(b->name); + free(b); + return -ENOMEM; + } + } else { + b->unit =3D NULL; + } + + b->val =3D val; + b->thresh =3D 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 =3D avg_stats(config->walltime_nsecs_stats) / NSEC_PER_SEC; + FILE *output =3D config->output; + + if (config->interval) + return; + + if (!config->null_run) + fprintf(output, "\n"); + + if (config->run_count =3D=3D 1) { + fprintf(output, " %17.9f seconds time elapsed", avg); + + if (config->ru_display) { + double ru_utime =3D + timeval2double((struct timeval *)&config->ru_data.ru_utime); + double ru_stime =3D + 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 =3D 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 =3D 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 =3D 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); =20 -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) + 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, struct perf_stat_config *config) { + struct std_print_state *ps =3D ctx; + struct queued_event *ev, *tmp_ev; + struct queued_metric *met, *tmp_met; + FILE *out =3D ps->fp; + bool first; + const char *last_mg_name =3D NULL; + const struct perf_pmu *last_pmu =3D NULL; + int last_aggr_idx =3D -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 =3D ev->evsel; + double sc =3D evsel->scale; + const char *fmt; + const char *bad_count =3D evsel->supported ? CNTR_NOT_COUNTED : CNTR_NOT= _SUPPORTED; + struct metric_event *me =3D + metricgroup__lookup(&evsel->evlist->metric_events, evsel, false); + bool is_metricgroup =3D false; + bool skip_header =3D false; + char full_name[128] =3D ""; + + if (me && me->is_default && !evsel->default_show_events) { + struct metric_expr *mexp =3D + list_first_entry(&me->head, struct metric_expr, nd); + const char *mg_name =3D mexp->default_metricgroup_name; + bool need_full_name =3D 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 =3D true; + + if (last_mg_name && !strcmp(last_mg_name, mg_name) && + last_pmu =3D=3D evsel->pmu && last_aggr_idx =3D=3D ev->aggr_idx) { + skip_header =3D true; + } + last_mg_name =3D mg_name; + last_pmu =3D evsel->pmu; + last_aggr_idx =3D 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 >=3D 0) { + struct aggr_cpu_id id =3D config->aggr_map->map[ev->aggr_idx]; + int aggr_nr =3D 0; + + if (evsel->stats && evsel->stats->aggr) + aggr_nr =3D 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 =3D fprintf(out, " %*s", EVNAME_LEN, full_name); + int pad =3D MGROUP_LEN + config->unit_width + 2 - n; + + fprintf(out, "%*s", pad > 0 ? pad : 0, ""); + } + } else { + if (config->big_num) + fmt =3D floor(sc) !=3D sc ? "%'*.2f " : "%'*.0f "; + else + fmt =3D floor(sc) !=3D sc ? "%*.2f " : "%*.0f "; + + if (ev->run =3D=3D 0 || ev->ena =3D=3D 0) { + fprintf(out, "%*s ", COUNTS_LEN, bad_count); + } else { + double scaled =3D (double)ev->val; + double avg; + + if (ev->ena < ev->run) + scaled =3D (double)ev->val * ev->run / ev->ena; + + avg =3D 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 and cgroup */ + fprintf(out, "%-*s", EVNAME_LEN, evsel__name(evsel)); + if (ev->cgrp && ev->cgrp[0]) + fprintf(out, " %-*s", CGROUP_LEN, ev->cgrp); + + /* 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 !=3D ev->ena) + fprintf(out, " (%.2f%%)", 100.0 * ev->run / ev->ena); + } + } + + first =3D true; + list_for_each_entry_safe(met, tmp_met, &ev->metrics_list, list) { + const char *color =3D metric_threshold_classify__color(met->thresh); + char unit_name[128]; + const char *m_fmt =3D (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 >=3D 0) { + struct aggr_cpu_id id =3D + config->aggr_map->map[ev->aggr_idx]; + struct perf_stat_evsel *se =3D evsel->stats; + int nr =3D (se && se->aggr) ? + se->aggr[ev->aggr_idx].nr : 0; + + print_aggr_id_std(config, out, evsel, id, nr); + } + fprintf(out, "%*s# ", + COUNTS_LEN + EVNAME_LEN + config->unit_width + 3, + ""); + } else { + fprintf(out, " # "); + } + first =3D 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 >=3D 0) { + struct aggr_cpu_id id =3D config->aggr_map->map[ev->aggr_idx]; + int aggr_nr =3D 0; + + if (evsel->stats && evsel->stats->aggr) + aggr_nr =3D 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 =3D 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 !=3D 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->cgrp); + free(ev); + } + print_footer_std(config); return 0; } + +static const struct perf_stat_print_callbacks std_print_callbacks =3D { + .print_start =3D std_print_start, + .print_end =3D std_print_end, + .print_event =3D std_print_event, + .print_metric =3D std_print_metric, +}; + +/* + * Standard (STD) Output Callbacks - Metric-Only Mode + */ + +static int std_metric_only_print_start(void *ctx, + struct perf_stat_config *config __maybe_unused) +{ + struct std_metric_only_print_state *ps =3D ctx; + + INIT_LIST_HEAD(&ps->queued_metrics); + return 0; +} + +static int std_metric_only_print_metric(void *ctx, + struct perf_stat_config *config __maybe_unused, + struct evsel *evsel, int aggr_idx, + const char *name, const char *unit, double val, + enum metric_threshold_classify thresh) +{ + struct std_metric_only_print_state *ps =3D ctx; + struct queued_metric *b =3D malloc(sizeof(*b)); + + if (!b) + return -ENOMEM; + + b->evsel =3D evsel; + b->name =3D strdup(name); + if (!b->name) { + free(b); + return -ENOMEM; + } + + if (unit && unit[0]) { + b->unit =3D strdup(unit); + if (!b->unit) { + free(b->name); + free(b); + return -ENOMEM; + } + } else { + b->unit =3D NULL; + } + + b->val =3D val; + b->thresh =3D thresh; + b->aggr_idx =3D aggr_idx; + list_add_tail(&b->list, &ps->queued_metrics); + + return 0; +} + +static int std_metric_only_print_end(void *ctx, struct perf_stat_config *c= onfig) +{ + struct std_metric_only_print_state *ps =3D ctx; + struct queued_metric *b, *tmp; + FILE *out =3D ps->fp; + int first_aggr =3D -1; + /* Initialize to -2 to distinguish from -1 (a valid index in AGGR_GLOBAL = mode) */ + int current_aggr =3D -2; + const char *color; + char *str; + size_t mlen; + size_t header_len; + int ret =3D 0; + int err; + + if (list_empty(&ps->queued_metrics)) + return 0; + + if (!config->metric_only_headers_printed) { + first_aggr =3D list_first_entry(&ps->queued_metrics, + struct queued_metric, list)->aggr_idx; + + /* Print the formatted header prefix */ + if (config->interval) { + /* Timestamp header: 17 chars wide ("# time ") */ + fprintf(out, "#%15s ", "time"); + } else { + print_header_std(config, ps->target, ps->argc, ps->argv); + } + + if (config->aggr_map && first_aggr >=3D 0 && + config->aggr_mode !=3D AGGR_GLOBAL) { + int len =3D aggr_header_lens[config->aggr_mode]; + + if (config->aggr_mode =3D=3D AGGR_CORE || + config->aggr_mode =3D=3D AGGR_NONE || + config->aggr_mode =3D=3D AGGR_THREAD) { + fprintf(out, "%-*s ", len, + aggr_header_std[config->aggr_mode]); + } else { + /* + * Socket/Die/Node/Cache/Cluster modes print + * label and "ctrs" + */ + fprintf(out, "%-*s %4s ", len, + aggr_header_std[config->aggr_mode], + "ctrs"); + } + } + + /* Print headers */ + list_for_each_entry(b, &ps->queued_metrics, list) { + if (b->aggr_idx =3D=3D first_aggr) { + char *header_name; + + + if (b->unit && b->unit[0]) { + err =3D asprintf(&header_name, "%s %s", b->unit, b->name); + header_len =3D strlen(b->unit) + 2 + strlen(b->name); + } else { + header_name =3D strdup(b->name); + header_len =3D strlen(b->name); + err =3D header_name ? 0 : -1; + } + if (err < 0) { + ret =3D -ENOMEM; + goto cleanup; + } + mlen =3D config->metric_only_len; + if (mlen < header_len) + mlen =3D header_len; + fprintf(out, "%*s ", (int)mlen, header_name); + free(header_name); + } + } + fprintf(out, "\n"); + config->metric_only_headers_printed =3D true; + } + + /* Print values */ + list_for_each_entry_safe(b, tmp, &ps->queued_metrics, list) { + if (b->aggr_idx !=3D current_aggr) { + if (current_aggr !=3D -2) + fprintf(out, "\n"); + current_aggr =3D b->aggr_idx; + if (config->interval && ps->timestamp[0]) + fprintf(out, "%s", ps->timestamp); + if (config->aggr_map && current_aggr >=3D 0 && + current_aggr < config->aggr_map->nr) { + struct aggr_cpu_id id =3D config->aggr_map->map[current_aggr]; + struct evsel *metric_evsel =3D b->evsel; + int aggr_nr =3D 0; + + if (metric_evsel && metric_evsel->stats && + metric_evsel->stats->aggr && + current_aggr < metric_evsel->stats->nr_aggr) + aggr_nr =3D metric_evsel->stats->aggr[current_aggr].nr; + + print_aggr_id_std(config, out, metric_evsel, id, + aggr_nr); + } + } + color =3D metric_threshold_classify__color(b->thresh); + if (b->unit && b->unit[0]) + header_len =3D strlen(b->unit) + 2 + strlen(b->name); + else + header_len =3D strlen(b->name); + + mlen =3D config->metric_only_len; + if (mlen < header_len) + mlen =3D header_len; + + if (color && color[0]) { + err =3D asprintf(&str, "%s%.1f%s", color, b->val, PERF_COLOR_RESET); + mlen +=3D strlen(color) + sizeof(PERF_COLOR_RESET) - 1; + } else { + err =3D asprintf(&str, "%.1f", b->val); + } + if (err < 0) { + ret =3D -ENOMEM; + goto cleanup; + } + fprintf(out, "%*s ", (int)mlen, str); + free(str); + + list_del(&b->list); + free(b->name); + free(b->unit); + free(b); + } + if (config->interval && current_aggr !=3D -2) + fprintf(out, "\n"); + 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_callba= cks =3D { + .print_start =3D std_metric_only_print_start, + .print_end =3D std_metric_only_print_end, + .print_event =3D NULL, + .print_metric =3D std_metric_only_print_metric, +}; + +int perf_stat__print_std(struct evlist *evlist, struct perf_stat_config *c= onfig, + const struct target *target, const struct timespec *ts, + int argc, const char **argv) +{ + struct std_print_state ps =3D { + .fp =3D config->output, + .target =3D target, + .argc =3D argc, + .argv =3D argv, + }; + + if (config->metric_only) { + struct std_metric_only_print_state mops =3D { + .fp =3D config->output, + .target =3D target, + .argc =3D argc, + .argv =3D argv, + .evlist =3D evlist, + }; + if (config->interval && ts) { + scnprintf(mops.timestamp, sizeof(mops.timestamp), "%6lu.%09lu ", + (unsigned long)ts->tv_sec, ts->tv_nsec); + } else { + mops.timestamp[0] =3D '\0'; + } + return perf_stat__print_cb(evlist, config, target, ts, argc, argv, + &std_metric_only_print_callbacks, &mops); + } else { + if (config->interval && !config->headers_printed) { + FILE *output =3D config->output; + + if (config->aggr_mode =3D=3D AGGR_GLOBAL) { + fprintf(output, "#%*s %*s %*s events\n", + 15 - 2, "time", 18, "counts", + config->unit_width, "unit"); + } else { + fprintf(output, + "#%*s %-*s ctrs %*s %*s events\n", + 15 - 2, "time", + aggr_header_lens[config->aggr_mode], + aggr_header_std[config->aggr_mode], + 18, "counts", config->unit_width, + "unit"); + } + config->headers_printed =3D true; + } + if (config->interval && ts) { + scnprintf(ps.timestamp, sizeof(ps.timestamp), "%6lu.%09lu ", + (unsigned long)ts->tv_sec, ts->tv_nsec); + } else { + ps.timestamp[0] =3D '\0'; + } + return perf_stat__print_cb(evlist, config, target, ts, argc, argv, + &std_print_callbacks, &ps); + } +} --=20 2.55.0.141.g00534a21ce-goog From nobody Sat Jul 25 16:18:33 2026 Received: from mail-pj1-f74.google.com (mail-pj1-f74.google.com [209.85.216.74]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 3F90C376490 for ; Thu, 16 Jul 2026 07:03:19 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.216.74 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1784185400; cv=none; b=VLBYrZmDyEnmIGt97S6lrliCHOIe67qIpNxupKT2zVB1djEtzrRTxcaT5Q7KS/tqiiXI347Wt9ERrX+O8mypA7i3Xyz2fk4vnTklj0pZfQlqJLx1Y4mCp/oKQDBqvmRtTFxVcqxY9R8mfCbK05d7I06PjOLMxPhS1MfATxn6ztk= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1784185400; c=relaxed/simple; bh=d5JB2fN4cHgH+ISFRjlPKGGrO51fpH0LIbT4lwQYVcg=; h=Date:In-Reply-To:Mime-Version:References:Message-ID:Subject:From: To:Cc:Content-Type; b=bzZQe0ErW/NNfmsggmBUVIV4MUpkX0yiagaT1IkznjCF//06pDDJAIxPM0fkj/ry5vG3cbfqrHVneL2TU2n1EUs3ftWrTV3xZoqkz30rnfTz38z5WZHLMkUj2MmaBzcaNBGw7UrU0EtmXXXKo7bv6UVB1Z8/F8skcWetLRqvuvg= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=google.com; spf=pass smtp.mailfrom=flex--irogers.bounces.google.com; dkim=pass (2048-bit key) header.d=google.com header.i=@google.com header.b=jFTkXET/; arc=none smtp.client-ip=209.85.216.74 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=google.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=flex--irogers.bounces.google.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=google.com header.i=@google.com header.b="jFTkXET/" Received: by mail-pj1-f74.google.com with SMTP id 98e67ed59e1d1-38d7fcd1f66so9296153a91.2 for ; Thu, 16 Jul 2026 00:03:19 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=20251104; t=1784185398; x=1784790198; darn=vger.kernel.org; h=content-type:cc:to:from:subject:message-id:references:mime-version :in-reply-to:date:from:to:cc:subject:date:message-id:reply-to :content-type; bh=pnCDelU6k7UkVlqKMeKUfoU14mnGppN9P1rHffhr4aQ=; b=jFTkXET/fRO5QdtmhFln8VurjsfW64hoBOek+tsX8qIQ07rgaxEyJFz83KIqY9D7cJ fXDs2QAKB1pjJIM3EZDkSxykJvFr1Lm9QWUp2a1fj1Z3jg1b2wF+x1tQY0c7QNhJoR6F iC7t+yXijjPRzVmtJdLNzilNqEvViewyb7S92Iw+7eYtHavmV/qw2Hfbv+vgtAPWNTqk EZTyIsWvtTvpuDwFeA5NEaokWBEk2G/xcawoXAPtZz4js3XEU50l6b/UvKTpF8pKJXS8 dnfqThXPRu+w/ouoRE1geNIUmktQKju/bkczE5gAkoXkHXeu5q9N0XugLwtESe2YDZTt U/Rg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1784185398; x=1784790198; h=content-type:cc:to:from:subject:message-id:references:mime-version :in-reply-to:date:x-gm-message-state:from:to:cc:subject:date :message-id:reply-to:content-type; bh=pnCDelU6k7UkVlqKMeKUfoU14mnGppN9P1rHffhr4aQ=; b=N2kUdud3Iy3D8xz8DlOP+xskWDu8vgc+2DJs2/t297maNscCh0+lbUuxyVBFMBnDDz P9SX3FcOG8Rj87B682bo1Y6K7gjL0J+uWQor0WOuagGSEJZHWqxoEdj3WB76IS8n73Ii 3TGI5b72Bi6SW9q2le7oj2HRxt6ppf6FxrbGhGfGgJzUN/lQF7OKsEv+mGp53h4rsWt0 oXNtQoVPGg5JvYAbBVnPRT9077shyZrxtLwMmEgSev/+qa/QtYjwrV6MT0iQVFTVPNDQ xsbXGnGWVgGzwfWOnMRR3v4LiENaiEep+iNUsmpsc+JLlvaiyLVQ2whqQEFjAH6M+SR+ czJg== X-Forwarded-Encrypted: i=1; AHgh+RrqolecjZ25uYoRNSa95MUKYen/Cw2Enh4e76YF5pgmGFKQKWZN8iOuKHj/8ium/Y0DlKt4NpXLrC/0Ud4=@vger.kernel.org X-Gm-Message-State: AOJu0YzExPexJq3qRLA/dCiUwMkRyBwUB3NfoPZ+pGMI7NywqSW4rtsp ss5Lecke8E0ha1+k6UR04ixEHJ8+DZ7F4OUTstp9sC8Clo1Ao48FaRsJfuxBabGQUsp99FFCpO1 2nWkX3vD4kQ== X-Received: from dychz19.prod.google.com ([2002:a05:7301:8613:b0:30f:2b52:8]) (user=irogers job=prod-delivery.src-stubby-dispatcher) by 2002:a17:90b:4c46:b0:37f:bfd6:8b40 with SMTP id 98e67ed59e1d1-38e1add8ad5mr8801189a91.5.1784185398199; Thu, 16 Jul 2026 00:03:18 -0700 (PDT) Date: Thu, 16 Jul 2026 00:02:52 -0700 In-Reply-To: <20260716070303.507066-1-irogers@google.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: Mime-Version: 1.0 References: <20260716043223.388233-1-irogers@google.com> <20260716070303.507066-1-irogers@google.com> X-Mailer: git-send-email 2.55.0.141.g00534a21ce-goog Message-ID: <20260716070303.507066-4-irogers@google.com> Subject: [PATCH v4 03/14] perf stat: Extend STD output linter to test basic New API checks From: Ian Rogers To: irogers@google.com, acme@kernel.org, ctshao@google.com, namhyung@kernel.org 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, Arnaldo Carvalho de Melo Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" This patch extends the standard console (STD) output linter script tools/perf/tests/shell/stat+std_output.sh to run the basic no-argument check a second time using the --new print flag: perf_cmd=3D"--new -o ${stat_output}" check_no_args "STD (New API)" "$perf_cmd" This ensures that standard console outputs produced by the decoupled printing callbacks are formally validated by the test suite. Signed-off-by: Ian Rogers Assisted-by: Antigravity:gemini-3.5-flash Acked-by: Arnaldo Carvalho de Melo Acked-by: Chun-Tse Shao --- tools/perf/tests/shell/stat+std_output.sh | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tools/perf/tests/shell/stat+std_output.sh b/tools/perf/tests/s= hell/stat+std_output.sh index 9c4b92ecf448..233e0a50eb33 100755 --- a/tools/perf/tests/shell/stat+std_output.sh +++ b/tools/perf/tests/shell/stat+std_output.sh @@ -118,5 +118,9 @@ then else echo "[Skip] Skipping tests for system_wide_no_aggr, per_core, per_die an= d per_socket since socket id exposed via topology is invalid" fi +# New API basic checks +perf_cmd=3D"--new -o ${stat_output}" +check_no_args "STD (New API)" "$perf_cmd" + cleanup exit 0 --=20 2.55.0.141.g00534a21ce-goog From nobody Sat Jul 25 16:18:33 2026 Received: from mail-pg1-f201.google.com (mail-pg1-f201.google.com [209.85.215.201]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 1A8CC386C2C for ; Thu, 16 Jul 2026 07:03:21 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.215.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1784185402; cv=none; b=cRSYduj30bATyfkI9n6fg49b+Tz4wZBdhtTUUlG4BfMLB5zSVDf3N+esI6nBF87ZgjfdOOw3OSIGUGKYz/1CqipGeenyswmFHGS4AEKOuKEuv8kKW+TUV1rxu0c5oBWFXiSFwuSF95BMTtnr8MjQ5gP2OPzvvLNzDzSaqi4RId0= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1784185402; c=relaxed/simple; bh=xnmKULBKjWn9ZNLB4yxCMsCYrpFYptWPY1nrp9Rer6w=; h=Date:In-Reply-To:Mime-Version:References:Message-ID:Subject:From: To:Cc:Content-Type; b=I/PPmq1QmcxRG6iJcDpiGeF0buiCM0qBtCuDJQlERR74irt5F1GnC8I1wEZhSkC0ntEu/oL5mXVDGxJYFpp/ISk7J90m2D2JlPSIUTIeW1Vp2wST711IRk5jFTXNRFaP4ToSMbq/dB2roLhMWywMSFttmrng0LN8J+MJNOtruuo= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=google.com; spf=pass smtp.mailfrom=flex--irogers.bounces.google.com; dkim=pass (2048-bit key) header.d=google.com header.i=@google.com header.b=EPX0d16a; arc=none smtp.client-ip=209.85.215.201 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=google.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=flex--irogers.bounces.google.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=google.com header.i=@google.com header.b="EPX0d16a" Received: by mail-pg1-f201.google.com with SMTP id 41be03b00d2f7-c894c1c4aa9so9232700a12.0 for ; Thu, 16 Jul 2026 00:03:21 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=20251104; t=1784185400; x=1784790200; darn=vger.kernel.org; h=content-type:cc:to:from:subject:message-id:references:mime-version :in-reply-to:date:from:to:cc:subject:date:message-id:reply-to :content-type; bh=rspboKk0H9Ay4WrfHqJFg1sxsAtnF7cVYDfdtCVodfw=; b=EPX0d16a3K/OcGcer/7ZV6PdfmVbU+z4ay/x5lWPIXqCWaPM2kR71jvFf3HVe9sSrN nQTz/XccrwGYQCWn76I8uhpyV2H3StfHMu8Oam7Y7/4dG8zjpGZks+IdDByMV2WirIyD ycFH1IeuZB6dlALpSy41wH4M4RebaDNECd+vGGIsIYpoteuWZUI4ODiPeLLLiru8DByO 9wXvq6K36aBGhm7c1yli4u2DnQPEG8RAix45YVs6r/mTth0Fij8dDpZNVvpTQcamZ688 BnP8y20IL4XErGV7XLZA38ExGsfWpvYB5iHdkIkYzHn8ddw6wG4EkIVcpS7aAjiOfvCV jOkw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1784185400; x=1784790200; h=content-type:cc:to:from:subject:message-id:references:mime-version :in-reply-to:date:x-gm-message-state:from:to:cc:subject:date :message-id:reply-to:content-type; bh=rspboKk0H9Ay4WrfHqJFg1sxsAtnF7cVYDfdtCVodfw=; b=fGjbnUZ4B5wQIaYoxMPczKBQb/p/An2wxP15qtOhFh+OBHP9YeXtCrqXeRMl033PSI sHK2I7vrm+Gi82BFV/O4T3fskGR4MYj/BScaJTEkopZysr+Q4/W5dJZK9EULB/0Ul7BI FowvK/TMQNTfMy+WT+l7H9AeLYK0myavTRgcY+ReuTqjk5D58pQGTi3KPdOq3EkYC+GA 7y8M4lLcKdHMnon/+kjNmzjyB9OA4zewj8qPhT5eOPayXiwnX/p+ov9q8T5bs0frCp7E p6OVb6qd0tWjAahCqv4I4vsuPEKtd3+pHslnQi3bdIC5mTEAHpsl1msG7zfCBCk2UHK7 SEkg== X-Forwarded-Encrypted: i=1; AHgh+Rpz9cKOTedQAO3RwfHLwxcm+sV79WYDjpE2GZcvQn/wJWCVjNWFT+PVwhm4m/bnQ7bNls+Oi1PjtuL0GvI=@vger.kernel.org X-Gm-Message-State: AOJu0Yz1dIBJYi+y16aLnHjwOlVRjWhiGJ38lp+RzarEdwBp3rv6fCFD FJQ6of2aim8sF9hml/aUWTY7Pf7u5SbDRdsEC/SiWA+NCta5o8DrxtScX5uhGXtu2Clb0YD1NZJ 9+gNyttCS7Q== X-Received: from dlb30.prod.google.com ([2002:a05:7022:61e:b0:139:a938:eeee]) (user=irogers job=prod-delivery.src-stubby-dispatcher) by 2002:a05:6a21:3a41:b0:3c3:875d:704d with SMTP id adf61e73a8af0-3c3875d81e1mr2641509637.25.1784185400126; Thu, 16 Jul 2026 00:03:20 -0700 (PDT) Date: Thu, 16 Jul 2026 00:02:53 -0700 In-Reply-To: <20260716070303.507066-1-irogers@google.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: Mime-Version: 1.0 References: <20260716043223.388233-1-irogers@google.com> <20260716070303.507066-1-irogers@google.com> X-Mailer: git-send-email 2.55.0.141.g00534a21ce-goog Message-ID: <20260716070303.507066-5-irogers@google.com> Subject: [PATCH v4 04/14] perf stat: Extend STD output linter to test core aggregation checks From: Ian Rogers To: irogers@google.com, acme@kernel.org, ctshao@google.com, namhyung@kernel.org 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 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" This patch extends tools/perf/tests/shell/stat+std_output.sh to run all standard CPU and thread aggregation checks under the --new print flag: - check_system_wide - check_system_wide_no_aggr - check_interval - check_per_thread - check_per_node - check_per_core - check_per_socket - check_per_die This guarantees that standard console outputs produced by the decoupled printing engine are verified across all core CPU-aggregation modes. Signed-off-by: Ian Rogers Assisted-by: Antigravity:gemini-3.5-flash Acked-by: Chun-Tse Shao --- tools/perf/tests/shell/stat+std_output.sh | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/tools/perf/tests/shell/stat+std_output.sh b/tools/perf/tests/s= hell/stat+std_output.sh index 233e0a50eb33..8dee005a7281 100755 --- a/tools/perf/tests/shell/stat+std_output.sh +++ b/tools/perf/tests/shell/stat+std_output.sh @@ -121,6 +121,17 @@ fi # New API basic checks perf_cmd=3D"--new -o ${stat_output}" check_no_args "STD (New API)" "$perf_cmd" +check_system_wide "STD (New API)" "$perf_cmd" +check_interval "STD (New API)" "$perf_cmd" +check_per_thread "STD (New API)" "$perf_cmd" +check_per_node "STD (New API)" "$perf_cmd" +if [ $skip_test -ne 1 ] +then + check_system_wide_no_aggr "STD (New API)" "$perf_cmd" + check_per_core "STD (New API)" "$perf_cmd" + check_per_die "STD (New API)" "$perf_cmd" + check_per_socket "STD (New API)" "$perf_cmd" +fi =20 cleanup exit 0 --=20 2.55.0.141.g00534a21ce-goog From nobody Sat Jul 25 16:18:33 2026 Received: from mail-pg1-f201.google.com (mail-pg1-f201.google.com [209.85.215.201]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id DB92030274D for ; Thu, 16 Jul 2026 07:03:22 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.215.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1784185406; cv=none; b=njpZEh+G/k+/D4WGp0B5P2sGDc7+waquj6n2TX/ejgNzSnfAGIOTPNWpzpGCp0vwOIkUMZGnIY1poTDQUXgvNXldxv9ezMy3sHZ2a72dFA/iTwfYQwQlEckvv5sKhEV2V6DP1i9bTvjCqOyYQe+YVnqfrBrAKQih+qXkuDUqZPU= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1784185406; c=relaxed/simple; bh=BtH0j4D4ANSbyEHLXUSbHJtMwiCmgti0BTLJkI1cLNQ=; h=Date:In-Reply-To:Mime-Version:References:Message-ID:Subject:From: To:Cc:Content-Type; b=QRT+5mM7OFzNZiLe/nuQeytmWjol5t7jkXz2PonnNA3n2bWHNMGQ5QN079hwC0U4XvQySkH1QHSPKc7YndBJwN+ZEjudlY/SPdUigfVQUzy0QhvSr6knhkq4+0dsqiZjtudtKr2U63nwvLjmkVh5UUSvRnINipkxslG3v85Y3/U= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=google.com; spf=pass smtp.mailfrom=flex--irogers.bounces.google.com; dkim=pass (2048-bit key) header.d=google.com header.i=@google.com header.b=fcu+tUUp; arc=none smtp.client-ip=209.85.215.201 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=google.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=flex--irogers.bounces.google.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=google.com header.i=@google.com header.b="fcu+tUUp" Received: by mail-pg1-f201.google.com with SMTP id 41be03b00d2f7-c916d17dd43so10388026a12.3 for ; Thu, 16 Jul 2026 00:03:22 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=20251104; t=1784185402; x=1784790202; darn=vger.kernel.org; h=content-type:cc:to:from:subject:message-id:references:mime-version :in-reply-to:date:from:to:cc:subject:date:message-id:reply-to :content-type; bh=3n+NLEtgkVKcMq53XD7BwLdhHU2hY7RObJiI/aTQumc=; b=fcu+tUUpXZMglh+50sv1HX0c15TVzOQEJUvL+Ad92Czxyk1MhWMgY6K4fRMGh/PiBo 2NGLWmOMUVaU7OUC5gzOdtVpUPLpcA2Z9YViQsopl7n6yqW8fp5QnJRBflOh+b25u51m nXVcV1T0DXHWmyuOt3UQpDhYS3gcZNmobAqktPxmwrnqP+g9DdeurcLx0UHsQ+WbPNFJ tWr8RT7tN91GuNmaW9Py4HDL2+RsX3KR9JatZHhpHN0skiWGf7PUEzb4gYhHnMZT5diC syZcN85rMzDbQ7Ex3Nd0YcZdzQOKzGWtOTH1wcaOOPeqzPq/jUDk7tNMJ9a/UAd5BOiQ WaIg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1784185402; x=1784790202; h=content-type:cc:to:from:subject:message-id:references:mime-version :in-reply-to:date:x-gm-message-state:from:to:cc:subject:date :message-id:reply-to:content-type; bh=3n+NLEtgkVKcMq53XD7BwLdhHU2hY7RObJiI/aTQumc=; b=lcQr4+Z2s7Tj4Av0U5Ck3FNuUd+1DFiDN0Tb+vthBCgb7agGDCsEzmEud/QJzdZNFp LQ/Yh2RdivSExUJsJFZe/FzQLlrxFGP2S/Z4AL00+pMvtVMD+SR23M6WiY3bcFp6w8j3 kdYtuQrdJUQUyjl1SWu3zU4DxAFROLkpw8FxHDlXTFXjILRDJqzTD4XpcWmTKvFO3QsB h7goG4C5LSe6JI60VQ/G+VAiMs+KHfjAOdr3wIYRxVSSR0ns9QaStmFAIX5pn0f02pkQ DlhsYzIDmfOuWm7XTC2d1DhgJoCMEIyhDoFI+p12MF1g8cTB0uKKcbJAMsdel/MTUDFu chEQ== X-Forwarded-Encrypted: i=1; AHgh+Rp73qpCDoc0PrEfS/m5gWJWATJpizOsfqfYFtegHLHPxSlhlT0YIBmbRwb36bhv3cVQyTyj90PQ1CbCjTU=@vger.kernel.org X-Gm-Message-State: AOJu0Yy4c9RdOtznlcv3bWaUiZPdP+h87tFLdA5adETMRwfcRV2MDZNR xnsrkoH62Wtn9CzLxrGs4jvr4hj9OiMU5sRqKr/y8EbAHilLiaaZTeRxDkBSu0XB4+ZGhavb3rk OodwFZIXI+g== X-Received: from dlj19.prod.google.com ([2002:a05:7022:513:b0:13b:340f:d011]) (user=irogers job=prod-delivery.src-stubby-dispatcher) by 2002:a05:6a21:114c:b0:3b4:b275:c5b1 with SMTP id adf61e73a8af0-3c36c1cf118mr7506417637.26.1784185402102; Thu, 16 Jul 2026 00:03:22 -0700 (PDT) Date: Thu, 16 Jul 2026 00:02:54 -0700 In-Reply-To: <20260716070303.507066-1-irogers@google.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: Mime-Version: 1.0 References: <20260716043223.388233-1-irogers@google.com> <20260716070303.507066-1-irogers@google.com> X-Mailer: git-send-email 2.55.0.141.g00534a21ce-goog Message-ID: <20260716070303.507066-6-irogers@google.com> Subject: [PATCH v4 05/14] perf stat: Extend STD output linter to test advanced PMU checks From: Ian Rogers To: irogers@google.com, acme@kernel.org, ctshao@google.com, namhyung@kernel.org 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 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" This patch extends tools/perf/tests/shell/stat+std_output.sh to run the advanced hardware PMU and topology checks under the --new print flag: - check_per_cache_instance - check_per_cluster This ensures that standard console outputs are verified under advanced topology-aware aggregation environments. Signed-off-by: Ian Rogers Assisted-by: Antigravity:gemini-3.5-flash Acked-by: Chun-Tse Shao --- tools/perf/tests/shell/stat+std_output.sh | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tools/perf/tests/shell/stat+std_output.sh b/tools/perf/tests/s= hell/stat+std_output.sh index 8dee005a7281..0d38a1283967 100755 --- a/tools/perf/tests/shell/stat+std_output.sh +++ b/tools/perf/tests/shell/stat+std_output.sh @@ -131,6 +131,8 @@ then check_per_core "STD (New API)" "$perf_cmd" check_per_die "STD (New API)" "$perf_cmd" check_per_socket "STD (New API)" "$perf_cmd" + check_per_cache_instance "STD (New API)" "$perf_cmd" + check_per_cluster "STD (New API)" "$perf_cmd" fi =20 cleanup --=20 2.55.0.141.g00534a21ce-goog From nobody Sat Jul 25 16:18:33 2026 Received: from mail-pl1-f201.google.com (mail-pl1-f201.google.com [209.85.214.201]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 5222B388E62 for ; Thu, 16 Jul 2026 07:03:25 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.214.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1784185408; cv=none; b=kdTXN03u27XgxZ5vIUGmRdUQdQv/2+G/S2IDZNqmKsSvkj//uHAcqiJwPPMMhP2kMs/objaOO5OIMdQW5QSzBFgfgKYH2IY/PbwP5/pO4AvL2NW1+Z73ypapVs0IN1HjM+K9l5YYqTqHEUAt6G3QxCIaFqciQjGrkrD2D3FmFDI= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1784185408; c=relaxed/simple; bh=UftcpZmurSmWOkHRhAfumQiBO9fPZRiBECvBmt0QEjY=; h=Date:In-Reply-To:Mime-Version:References:Message-ID:Subject:From: To:Cc:Content-Type; b=g+x6s1ky4ZM8MpBj4ZZaioaobrnbDZ5A5CLW7OwQTxxtFv/XeL3gYv7i15UYB8rSrRsfaJMywZaAzLBL5fMTUckEA/+U6bIBTBQ1ZD8vBE68o9eacZJ6H3fjVImE78L1xxp0T1v7uWSrBDqWbgvfoATc6Umy6v1GwMmd0Nfhqsw= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=google.com; spf=pass smtp.mailfrom=flex--irogers.bounces.google.com; dkim=pass (2048-bit key) header.d=google.com header.i=@google.com header.b=dk/qk7Q/; arc=none smtp.client-ip=209.85.214.201 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=google.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=flex--irogers.bounces.google.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=google.com header.i=@google.com header.b="dk/qk7Q/" Received: by mail-pl1-f201.google.com with SMTP id d9443c01a7336-2ccb687f82eso95514995ad.3 for ; Thu, 16 Jul 2026 00:03:25 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=20251104; t=1784185404; x=1784790204; darn=vger.kernel.org; h=content-type:cc:to:from:subject:message-id:references:mime-version :in-reply-to:date:from:to:cc:subject:date:message-id:reply-to :content-type; bh=NAPh6UvpkD4fk5vrJ+FvIGuSjW09efU2gKSB4rCskWA=; b=dk/qk7Q/WhX9TAH8zn9FnF2vLNpt22mW+mwPa+MbgpYii9wjFNzzHC0nksggioUG0K 5FUjIe9OJElPN+vM6fz2skX9qHvp6jjr7Xos+n08GGP755j8iCEYXo78u+w3Mj8pjv/I 5lzg/8CsxptDXRCpD4SaTHMSkk8UFpp1DRyKkmTZ54AamsomZXzj5gua3dBtib0BiSqB zaKlR9+c0IIj2ArJukVG1t1v6Utz++jhAt/5uX2NhY+n0n3Lvk3sqNTrUqP49lsN1XPu +1FdUepLs0I7m3J7ZMgAmajSJzpufYgdf7M7mnpgrGwa/3DdyOkevS9hNpvK8la7Fm5O WEZA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1784185404; x=1784790204; h=content-type:cc:to:from:subject:message-id:references:mime-version :in-reply-to:date:x-gm-message-state:from:to:cc:subject:date :message-id:reply-to:content-type; bh=NAPh6UvpkD4fk5vrJ+FvIGuSjW09efU2gKSB4rCskWA=; b=cyo+eXoftymSwGVY2v/syGOBPU7IGN4ZlOHgrAGw4CKzH+8ScCBa6S1TEskjhwvG3r tDQ+tByC+UcLRndVpHxD93LaZ7XJN4nBjn0ELBJ0F+dBP+l5gpP5NWnFjQxa4Bra96Am rc4wPlWh3TdTcwBCNYG+3+LBoBTRK70DyP7dy+CRd5cX4PBL3c+mr1V5u1la9gc7LGlq SYMKkewZ77X3gdCMX2k+77/ckcnmPmRRRpwzjJXJGbcJ5XsWoeastmadkBlS63xzXrW8 BAivRxxHHgwidaaieCgIY8j/x70eF+3LmGH8/4IcqoSlbsAaAeuLw9Nx1I1hfu3tM1Va y5Dw== X-Forwarded-Encrypted: i=1; AHgh+Rp6r9duBUKd1Xk638QDarbmqC1o1YlooXI5fzzWfgsevyDYexKddJoQy3Drelsxu80BZrEnY+zzxYvCE3o=@vger.kernel.org X-Gm-Message-State: AOJu0Yzvwaf+I/+r6xv18Wue6joy/ILLrGKhb8IrXSrverQ9IhHViOKK CHRw1KXfkQeDf43VpGAFhAcZVY2VlcBpa0TEwQqveOvaw79gCtxJDvZ0lwfemMl1PsnmU0c4vek oKvDoL8+sXQ== X-Received: from dlbcm27.prod.google.com ([2002:a05:7022:689b:b0:13b:a023:b237]) (user=irogers job=prod-delivery.src-stubby-dispatcher) by 2002:a17:902:c948:b0:2cc:d192:50b8 with SMTP id d9443c01a7336-2cf03e1f1fcmr57048515ad.47.1784185404037; Thu, 16 Jul 2026 00:03:24 -0700 (PDT) Date: Thu, 16 Jul 2026 00:02:55 -0700 In-Reply-To: <20260716070303.507066-1-irogers@google.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: Mime-Version: 1.0 References: <20260716043223.388233-1-irogers@google.com> <20260716070303.507066-1-irogers@google.com> X-Mailer: git-send-email 2.55.0.141.g00534a21ce-goog Message-ID: <20260716070303.507066-7-irogers@google.com> Subject: [PATCH v4 06/14] perf stat: Extend STD output linter to test metric-only checks From: Ian Rogers To: irogers@google.com, acme@kernel.org, ctshao@google.com, namhyung@kernel.org 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 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" This patch extends tools/perf/tests/shell/stat+std_output.sh to run the metric-only check under the --new print flag: - check_metric_only This guarantees that standard console metric-only outputs produced by the decoupled printing callbacks are formally validated by the test linter. Signed-off-by: Ian Rogers Assisted-by: Antigravity:gemini-3.5-flash Acked-by: Chun-Tse Shao --- tools/perf/tests/shell/stat+std_output.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/tools/perf/tests/shell/stat+std_output.sh b/tools/perf/tests/s= hell/stat+std_output.sh index 0d38a1283967..69720b19b908 100755 --- a/tools/perf/tests/shell/stat+std_output.sh +++ b/tools/perf/tests/shell/stat+std_output.sh @@ -134,6 +134,7 @@ then check_per_cache_instance "STD (New API)" "$perf_cmd" check_per_cluster "STD (New API)" "$perf_cmd" fi +check_metric_only "STD (New API)" "$perf_cmd" =20 cleanup exit 0 --=20 2.55.0.141.g00534a21ce-goog From nobody Sat Jul 25 16:18:33 2026 Received: from mail-pg1-f201.google.com (mail-pg1-f201.google.com [209.85.215.201]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 07C6F386575 for ; Thu, 16 Jul 2026 07:03:26 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.215.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1784185410; cv=none; b=Ph5T6U756/t7sR6ZLBTHVWDRj2U6+c13XS3rszmQlAR2olLwYK7IhZvGtF/psy731hsKRiY/vOphL8XJcIOMRFpbJVnSCJTA7jLk5u+ls6Io36qAYyfNVAcCtppdc8+lHCWlhnuS1SH8VrpQvK5/Q0nkiYo1a4FbXasDKEmg/VA= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1784185410; c=relaxed/simple; bh=bJn2dSlAtNjkN0pqwISvOmQIcdAlFhaQ3l8Q6czi+fQ=; h=Date:In-Reply-To:Mime-Version:References:Message-ID:Subject:From: To:Cc:Content-Type; b=gGJ70flqVP8+G/ZEhCY9sWQEsmRoTAT/pG5Cksmow5/nOYXn9XnQMQ25gvnymFGytlBNStzyVDWoR4H/cEv2UjaS0fjVieZfRo4yTxk46wcmUwzsTlvGmLV3ZSLLq/rFqgVKxQXzwsmJaDnnjFWsUPMJtz5DNvetNlcezNcHFbA= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=google.com; spf=pass smtp.mailfrom=flex--irogers.bounces.google.com; dkim=pass (2048-bit key) header.d=google.com header.i=@google.com header.b=JzsXFUtd; arc=none smtp.client-ip=209.85.215.201 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=google.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=flex--irogers.bounces.google.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=google.com header.i=@google.com header.b="JzsXFUtd" Received: by mail-pg1-f201.google.com with SMTP id 41be03b00d2f7-c890bac374eso15672312a12.1 for ; Thu, 16 Jul 2026 00:03:26 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=20251104; t=1784185406; x=1784790206; darn=vger.kernel.org; h=content-type:cc:to:from:subject:message-id:references:mime-version :in-reply-to:date:from:to:cc:subject:date:message-id:reply-to :content-type; bh=gD77hpfjwozG6ZVYj8Tm71/tl5Acvufs/zc7tAqWd3s=; b=JzsXFUtdnfdmbx2JmgaiwU9yqNRJuygH2g8vi9VqtsGpsrip6AyiPa36MxyviNutGx wWY/sngd7u2RPGEwC6JbmfKFIYtApT5YVB9ZbMAkBtyJGI4oif24OuEFGdE7CaVQLeay TGalRlHydwOgvDfx9eK6opdQylw1H2lCa6FVNB9hOfUspRdVYK8F3RRpNCZjy9GIWuz1 D8O1aRbMzK2srdER07p6wuYQaVxtT2IIMiYxJjccwCA04itGilabzYMBdd3eL20PblYm +IEdVPfN+gK/TmvZjxWVPbTidoLQic+01+hjL1kve8biVu8vQHDQ2syPzJNtuYvc/Irk z+1Q== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1784185406; x=1784790206; h=content-type:cc:to:from:subject:message-id:references:mime-version :in-reply-to:date:x-gm-message-state:from:to:cc:subject:date :message-id:reply-to:content-type; bh=gD77hpfjwozG6ZVYj8Tm71/tl5Acvufs/zc7tAqWd3s=; b=W6oylehqInUjy9jhmdXDb4d6UCsSiPuCmbQnJ8rKaamWFGTUvcpz71euHtBivwS+Ez tzGMSCLSr8Z/74idzhiZF45gN5OisfXDDztT136N0pHBxTaQtfrKr+HJI/Zsm5SVElO4 O+1AuwNg+b/YmX0BbgVj+M2HcLmmsxbV2sxbNITJwCsre5/3Ulm4/Yk5FyC2D4YcIT2p u7Pv26DbxNs/MYEgLT8ZMkK6g2Zbb4iHQD1i8fWzBBGPDsZLFUHagzsithU4GO3P9nEn BLf/CT3H+9hJREF+DhwSOW9dmvqyCQ7q6VieVpHsg/tYTyhKQ56qfeiSecYLNIGqqiSW 9iYA== X-Forwarded-Encrypted: i=1; AHgh+Rrcah2mdVWs3LQVRQULyeVjhf1VKzI1jDNQrbIA5hw4ByLnmDzkCinw8FDd8e7MAF8hAyTALLt4jdozm+g=@vger.kernel.org X-Gm-Message-State: AOJu0YxPcjUAAGUYYy+JG7EVXFBqQsWCDVR0UBC9cFlu57adq9d5zt89 ApTRwsazEvShCNFeNLgfIXyyOuNgBcfy75clGzFKMxexmho9DZiB3SMUONpgX2lPwFWAPlTvD6H 0/+0HSwQRog== X-Received: from dlaj18.prod.google.com ([2002:a05:701b:2812:b0:13c:ca64:1675]) (user=irogers job=prod-delivery.src-stubby-dispatcher) by 2002:a05:6a20:72a2:b0:3c3:83e6:c705 with SMTP id adf61e73a8af0-3c383e71c7cmr3044127637.35.1784185406076; Thu, 16 Jul 2026 00:03:26 -0700 (PDT) Date: Thu, 16 Jul 2026 00:02:56 -0700 In-Reply-To: <20260716070303.507066-1-irogers@google.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: Mime-Version: 1.0 References: <20260716043223.388233-1-irogers@google.com> <20260716070303.507066-1-irogers@google.com> X-Mailer: git-send-email 2.55.0.141.g00534a21ce-goog Message-ID: <20260716070303.507066-8-irogers@google.com> Subject: [PATCH v4 07/14] perf stat: Implement CSV formatting callbacks From: Ian Rogers To: irogers@google.com, acme@kernel.org, ctshao@google.com, namhyung@kernel.org 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 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" This patch implements CSV output formatting callbacks inside util/stat-print-csv.c, replacing the empty stubs introduced in Commit 1. Defines the format-private `struct queued_event` and `struct queued_metric` DOM nodes to buffer traversal streams, and fully encapsulates CSV queued lists lifecycle and deallocations inside csv_print_start() and csv_print_end(). Utilizes the newly centralized unified aggregation helpers to format CPU and thread column prefixes cleanly, fixes metrics separators padding, and incorporates full interval-mode timestamp printing support. Signed-off-by: Ian Rogers Assisted-by: Antigravity:gemini-3.5-flash Acked-by: Chun-Tse Shao --- tools/perf/util/stat-print-csv.c | 678 ++++++++++++++++++++++++++++++- 1 file changed, 671 insertions(+), 7 deletions(-) diff --git a/tools/perf/util/stat-print-csv.c b/tools/perf/util/stat-print-= csv.c index ee86d07636ea..f93ca1885d46 100644 --- a/tools/perf/util/stat-print-csv.c +++ b/tools/perf/util/stat-print-csv.c @@ -1,13 +1,677 @@ // SPDX-License-Identifier: GPL-2.0 -#include "stat-print.h" +#include +#include +#include +#include +#include +#include + +#include +#include #include +#include + +#include "cpumap.h" +#include "evlist.h" +#include "evsel.h" +#include "stat-print.h" +#include "stat.h" +#include "thread_map.h" +#include "debug.h" + +#define COMM_LEN 16 +#define PID_LEN 7 + +struct queued_metric { + struct list_head list; + struct evsel *evsel; + char *name; + char *unit; + double val; + int aggr_idx; +}; + +/** + * struct queued_event - In-memory record of a buffered CSV counter event. + * @list: Linked list node for queueing. + * @evsel: The associated performance event selector. + * @name: The uniquely formatted/resolved event name. + * @unit: The event's unit (e.g. "msec", "cycles"). + * @cgrp: Cgroup name (optional). + * @val: Raw aggregated counter value. + * @ena: Enabled time for multiplexing percentage. + * @run: Running time for multiplexing percentage. + * @scale: Event scale factor. + * @stdev_pct: Standard deviation percentage. + * @supported: Event hardware support indicator. + * @aggr_idx: Aggregation index. + * @metrics_list: Linked list head containing nested queued_metric structu= res. + */ +struct queued_event { + struct list_head list; + struct evsel *evsel; + char *name; + char *unit; + char *cgrp; + u64 val, ena, run; + double scale; + double stdev_pct; + bool supported; + int aggr_idx; + struct list_head metrics_list; +}; + +/** + * struct csv_print_state - Print state context for CSV output. + * @fp: File descriptor to output to. + * @sep: CSV column separator character/string. + * @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 met= rics back to + * their parent event node during list buffering. This rel= ies on a + * strict temporal coupling in the traversal driver: the d= river always + * invokes print_metric() callbacks for a counter synchron= ously and + * immediately after its print_event() callback, prior to = advancing + * to the next event or aggregation node. This pointer is = completely + * private to CSV printing, keeping the traversal driver d= ecoupled + * and preserving strict encapsulation. + */ +struct csv_print_state { + FILE *fp; + const char *sep; + char timestamp[64]; + struct list_head events_list; + struct queued_event *current_event; +}; + +/** + * struct csv_metric_only_print_state - Metric-only print state context fo= r CSV output. + * @fp: File descriptor to output to. + * @sep: CSV column separator. + * @timestamp: Formatted interval timestamp (optional). + * @evlist: Evlist to query entries from. + * @queued_metrics: Linked list head containing queued_metric nodes. + */ +struct csv_metric_only_print_state { + FILE *fp; + const char *sep; + char timestamp[64]; + struct evlist *evlist; + struct list_head queued_metrics; +}; + +/** + * print_aggr_id_csv - Print the aggregation prefix for CSV format. + * + * Copied and adapted from stat-display.c. + */ +static void print_aggr_id_csv(const struct perf_stat_config *config, FILE = *output, + struct evsel *evsel, struct aggr_cpu_id id, int aggr_nr) +{ + const char *sep =3D config->csv_sep; + + switch (config->aggr_mode) { + case AGGR_CORE: + fprintf(output, "S%d-D%d-C%d%s%d%s", id.socket, id.die, id.core, sep, ag= gr_nr, sep); + break; + case AGGR_CACHE: + fprintf(output, "S%d-D%d-L%d-ID%d%s%d%s", id.socket, id.die, id.cache_lv= l, id.cache, + sep, aggr_nr, sep); + break; + case AGGR_CLUSTER: + fprintf(output, "S%d-D%d-CLS%d%s%d%s", id.socket, id.die, id.cluster, se= p, aggr_nr, + sep); + break; + case AGGR_DIE: + fprintf(output, "S%d-D%d%s%d%s", id.socket, id.die, sep, aggr_nr, sep); + break; + case AGGR_SOCKET: + fprintf(output, "S%d%s%d%s", id.socket, sep, aggr_nr, sep); + break; + case AGGR_NODE: + fprintf(output, "N%d%s%d%s", id.node, sep, aggr_nr, sep); + break; + case AGGR_NONE: + if (evsel->percore && !config->percore_show_thread) + fprintf(output, "S%d-D%d-C%d%s", id.socket, id.die, id.core, sep); + else if (id.cpu.cpu > -1) + fprintf(output, "CPU%d%s", id.cpu.cpu, sep); + break; + case AGGR_THREAD: { + const char *comm =3D "unknown"; + int pid =3D -1; + + if (evsel && evsel->core.threads && id.thread_idx >=3D 0 && + id.thread_idx < perf_thread_map__nr(evsel->core.threads)) { + comm =3D perf_thread_map__comm(evsel->core.threads, + id.thread_idx); + pid =3D perf_thread_map__pid(evsel->core.threads, + id.thread_idx); + } + fprintf(output, "%s-%d%s", comm, pid, sep); + break; + } + case AGGR_GLOBAL: + case AGGR_UNSET: + case AGGR_MAX: + default: + break; + } +} + +/* + * CSV Output Callbacks - Normal Mode + */ + +static int csv_print_start(void *ctx, struct perf_stat_config *config __ma= ybe_unused) +{ + struct csv_print_state *ps =3D ctx; + + INIT_LIST_HEAD(&ps->events_list); + ps->current_event =3D NULL; + return 0; +} + +static int csv_print_event(void *ctx, struct perf_stat_config *config, + struct evsel *evsel, int aggr_idx, u64 val, u64 ena, + u64 run, double stdev_pct, const char *cgrp) +{ + struct csv_print_state *ps =3D ctx; + struct queued_event *ev; + struct aggr_cpu_id id =3D aggr_cpu_id__empty(); + + if (config && config->aggr_map && aggr_idx >=3D 0 && + aggr_idx < config->aggr_map->nr) + id =3D config->aggr_map->map[aggr_idx]; + + /* Skip zero counters in CSV callbacks if they qualify */ + if (val =3D=3D 0 && should_skip_zero_counter(config, evsel, &id)) { + ps->current_event =3D NULL; + return 0; + } + + ev =3D malloc(sizeof(*ev)); + if (!ev) + return -ENOMEM; + + ev->name =3D strdup(evsel__name(evsel)); + if (!ev->name) { + free(ev); + return -ENOMEM; + } + + if (evsel->unit) { + ev->unit =3D strdup(evsel->unit); + if (!ev->unit) { + free(ev->name); + free(ev); + return -ENOMEM; + } + } else { + ev->unit =3D NULL; + } + + if (cgrp && cgrp[0]) { + ev->cgrp =3D strdup(cgrp); + if (!ev->cgrp) { + free(ev->unit); + free(ev->name); + free(ev); + return -ENOMEM; + } + } else { + ev->cgrp =3D NULL; + } + + ev->evsel =3D evsel; + ev->val =3D val; + ev->ena =3D ena; + ev->run =3D run; + ev->scale =3D evsel->scale; + ev->stdev_pct =3D stdev_pct; + ev->supported =3D evsel->supported; + ev->aggr_idx =3D aggr_idx; + INIT_LIST_HEAD(&ev->metrics_list); + + list_add_tail(&ev->list, &ps->events_list); + ps->current_event =3D ev; + + return 0; +} + +static int csv_print_metric(void *ctx, struct perf_stat_config *config __m= aybe_unused, + struct evsel *evsel, int aggr_idx, + const char *name, const char *unit, double val, + enum metric_threshold_classify thresh __maybe_unused) +{ + struct csv_print_state *ps =3D ctx; + struct queued_metric *b; + + if (!ps->current_event) + return 0; + + if (evsel !=3D ps->current_event->evsel) { + pr_err("decoupled print engine: temporal coupling violation: evsel misma= tch!\n"); + return -EINVAL; + } + + b =3D malloc(sizeof(*b)); + if (!b) + return -ENOMEM; + + b->evsel =3D evsel; + b->name =3D strdup(name); + if (!b->name) { + free(b); + return -ENOMEM; + } + + if (unit && unit[0]) { + b->unit =3D strdup(unit); + if (!b->unit) { + free(b->name); + free(b); + return -ENOMEM; + } + } else { + b->unit =3D NULL; + } + + b->val =3D val; + b->aggr_idx =3D aggr_idx; + list_add_tail(&b->list, &ps->current_event->metrics_list); + + return 0; +} + +static int csv_print_end(void *ctx, struct perf_stat_config *config) +{ + struct csv_print_state *ps =3D ctx; + struct queued_event *ev, *tmp_ev; + struct queued_metric *met, *tmp_met; + FILE *output =3D ps->fp; + const char *sep =3D ps->sep; + bool has_metrics; + + list_for_each_entry_safe(ev, tmp_ev, &ps->events_list, list) { + struct evsel *evsel =3D ev->evsel; + bool ok =3D (ev->run !=3D 0 && ev->ena !=3D 0); + const char *bad_count =3D ev->supported ? CNTR_NOT_COUNTED : CNTR_NOT_SU= PPORTED; + double enabled_percent =3D 100; + + /* Print interval timestamp first if configured */ + if (config->interval && ps->timestamp[0]) + fprintf(output, "%s", ps->timestamp); + + /* Print aggregation prefix first in CSV normal mode */ + if (config->aggr_map && ev->aggr_idx >=3D 0) { + struct aggr_cpu_id id =3D config->aggr_map->map[ev->aggr_idx]; + int aggr_nr =3D 0; + + if (evsel->stats && evsel->stats->aggr) + aggr_nr =3D evsel->stats->aggr[ev->aggr_idx].nr; + + print_aggr_id_csv(config, output, evsel, id, aggr_nr); + } + + /* 1. Print Value, Unit, Name (Columns 1, 2, 3) */ + if (ok) { + double sc =3D ev->scale; + double avg =3D ev->val * sc; + const char *fmt =3D floor(sc) !=3D sc ? "%.2f%s" : "%.0f%s"; + + fprintf(output, fmt, avg, sep); + } else { + fprintf(output, "%s%s", bad_count, sep); + } =20 -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) + if (ev->unit) + fprintf(output, "%s%s", ev->unit, sep); + else + fprintf(output, "%s", sep); + + fprintf(output, "%s", ev->name); + + /* 2. Print Runtime and Enabled Percentage (Columns 4, 5) */ + if (ev->run !=3D ev->ena) + enabled_percent =3D 100.0 * ev->run / ev->ena; + fprintf(output, "%s%" PRIu64 "%s%.2f", sep, ev->run, sep, + enabled_percent); + + /* 3. Print Metrics (Columns 6, 7) */ + has_metrics =3D false; + list_for_each_entry_safe(met, tmp_met, &ev->metrics_list, list) { + if (!has_metrics) { + has_metrics =3D true; + } else { + fprintf(output, "\n"); + if (config->interval && ps->timestamp[0]) + fprintf(output, "%s", ps->timestamp); + if (config->aggr_map && ev->aggr_idx >=3D 0) { + struct aggr_cpu_id id =3D config->aggr_map->map[ev->aggr_idx]; + int aggr_nr =3D 0; + + if (evsel->stats && evsel->stats->aggr) + aggr_nr =3D evsel->stats->aggr[ev->aggr_idx].nr; + + print_aggr_id_csv(config, output, evsel, id, aggr_nr); + } + /* + * Pad exactly 4 commas (Value, Unit, Name, Run, Enabled) + * to line up with the Metric Value column. + */ + fprintf(output, "%s%s%s%s", sep, sep, sep, sep); + } + fprintf(output, "%s%.2f%s", sep, met->val, sep); + if (met->name && met->name[0]) + fprintf(output, "%s", met->name); + + list_del(&met->list); + free(met->name); + free(met->unit); + free(met); + } + if (!has_metrics) + fprintf(output, "%s%s", sep, sep); + fprintf(output, "\n"); + + list_del(&ev->list); + free(ev->name); + free(ev->unit); + free(ev->cgrp); + free(ev); + } + return 0; +} + +static const struct perf_stat_print_callbacks csv_print_callbacks =3D { + .print_start =3D csv_print_start, + .print_end =3D csv_print_end, + .print_event =3D csv_print_event, + .print_metric =3D csv_print_metric, +}; + +/* + * CSV Output Callbacks - Metric-Only Mode + */ + +static int csv_metric_only_print_start(void *ctx, + struct perf_stat_config *config __maybe_unused) { + struct csv_metric_only_print_state *ps =3D ctx; + + INIT_LIST_HEAD(&ps->queued_metrics); return 0; } + +static int csv_metric_only_print_metric(void *ctx, + struct perf_stat_config *config __maybe_unused, + struct evsel *evsel, int aggr_idx, + const char *name, const char *unit, double val, + enum metric_threshold_classify thresh __maybe_unused) +{ + struct csv_metric_only_print_state *ps =3D ctx; + struct queued_metric *b =3D malloc(sizeof(*b)); + + if (!b) + return -ENOMEM; + + b->evsel =3D evsel; + b->name =3D strdup(name); + if (!b->name) { + free(b); + return -ENOMEM; + } + + if (unit && unit[0]) { + b->unit =3D strdup(unit); + if (!b->unit) { + free(b->name); + free(b); + return -ENOMEM; + } + } else { + b->unit =3D NULL; + } + + b->val =3D val; + b->aggr_idx =3D aggr_idx; + list_add_tail(&b->list, &ps->queued_metrics); + + return 0; +} + +static int csv_metric_only_print_end(void *ctx, struct perf_stat_config *c= onfig) +{ + struct csv_metric_only_print_state *ps =3D ctx; + FILE *output =3D ps->fp; + const char *sep =3D ps->sep; + struct queued_metric *b, *tmp; + char **unique_names =3D NULL; + size_t num_unique =3D 0; + size_t unique_alloc =3D 0; + int *unique_aggrs =3D NULL; + size_t num_aggrs =3D 0; + size_t aggr_alloc =3D 0; + size_t i, j; + int ret =3D 0; + int err; + + if (list_empty(&ps->queued_metrics)) + return 0; + + /* 1. Build unique metric names and unique aggr_idx lists */ + list_for_each_entry(b, &ps->queued_metrics, list) { + bool found_metric =3D false; + bool found_aggr =3D false; + + for (i =3D 0; i < num_unique; i++) { + if (!strcmp(unique_names[i], b->name)) { + found_metric =3D true; + break; + } + } + if (!found_metric) { + if (num_unique >=3D unique_alloc) { + unique_alloc =3D unique_alloc ? unique_alloc * 2 : 16; + unique_names =3D realloc(unique_names, + unique_alloc * sizeof(char *)); + if (!unique_names) { + ret =3D -ENOMEM; + goto cleanup_grid; + } + } + unique_names[num_unique++] =3D strdup(b->name); + if (!unique_names[num_unique - 1]) { + num_unique--; + ret =3D -ENOMEM; + goto cleanup_grid; + } + } + + for (i =3D 0; i < num_aggrs; i++) { + if (unique_aggrs[i] =3D=3D b->aggr_idx) { + found_aggr =3D true; + break; + } + } + if (!found_aggr) { + if (num_aggrs >=3D aggr_alloc) { + aggr_alloc =3D aggr_alloc ? aggr_alloc * 2 : 16; + unique_aggrs =3D realloc(unique_aggrs, + aggr_alloc * sizeof(int)); + if (!unique_aggrs) { + ret =3D -ENOMEM; + goto cleanup_grid; + } + } + unique_aggrs[num_aggrs++] =3D b->aggr_idx; + } + } + + /* 2. Print Headers (if not already printed) */ + if (!config->metric_only_headers_printed) { + if (config->interval) { + fprintf(output, "#%s%s", "time", sep); + } else { + if (config->aggr_map && num_aggrs > 0 && + config->aggr_mode !=3D AGGR_GLOBAL) { + const char *header =3D aggr_header_csv[config->aggr_mode]; + const char *p =3D header; + + while (*p) { + if (*p =3D=3D ',') + fputs(sep, output); + else + fputc(*p, output); + p++; + } + } + } + + /* Print uniquely collected headers in exact columnar order */ + for (i =3D 0; i < num_unique; i++) { + char *header_name =3D NULL; + /* Find first matching metric to grab its unit for the header */ + char *unit =3D NULL; + + list_for_each_entry(b, &ps->queued_metrics, list) { + if (!strcmp(b->name, unique_names[i])) { + unit =3D b->unit; + break; + } + } + + if (unit && unit[0]) + err =3D asprintf(&header_name, "%s %s", unit, + unique_names[i]); + else + err =3D asprintf(&header_name, "%s", + unique_names[i]); + + if (err < 0 || !header_name) { + ret =3D -ENOMEM; + goto cleanup_grid; + } + fprintf(output, "%s%s", header_name, sep); + free(header_name); + } + fprintf(output, "\n"); + config->metric_only_headers_printed =3D true; + } + + /* 3. Print Values for Each Aggregation Node */ + for (i =3D 0; i < num_aggrs; i++) { + int current_aggr =3D unique_aggrs[i]; + struct evsel *last_evsel =3D NULL; + + /* Print aggregation prefix */ + if (config->interval && ps->timestamp[0]) + fprintf(output, "%s", ps->timestamp); + + if (config->aggr_map && current_aggr >=3D 0 && + current_aggr < config->aggr_map->nr) { + struct aggr_cpu_id id =3D config->aggr_map->map[current_aggr]; + int aggr_nr =3D 0; + + /* Find evsel for this aggr_idx */ + list_for_each_entry(b, &ps->queued_metrics, list) { + if (b->aggr_idx =3D=3D current_aggr) { + last_evsel =3D b->evsel; + break; + } + } + + if (last_evsel && last_evsel->stats && + last_evsel->stats->aggr && + current_aggr < last_evsel->stats->nr_aggr) + aggr_nr =3D last_evsel->stats->aggr[current_aggr].nr; + + print_aggr_id_csv(config, output, last_evsel, id, aggr_nr); + } + + /* Print each metric, or pad with empty comma if missing for this node */ + for (j =3D 0; j < num_unique; j++) { + struct queued_metric *found_metric =3D NULL; + + list_for_each_entry(b, &ps->queued_metrics, list) { + if (b->aggr_idx =3D=3D current_aggr && + !strcmp(b->name, unique_names[j])) { + found_metric =3D b; + break; + } + } + + if (found_metric) { + if (isnan(found_metric->val)) + fprintf(output, "%s", sep); + else + fprintf(output, "%.1f%s", + found_metric->val, sep); + } else { + /* Padding for missing metric to preserve column alignment */ + fprintf(output, "%s", sep); + } + } + fprintf(output, "\n"); + } + +cleanup_grid: + for (i =3D 0; i < num_unique; i++) + free(unique_names[i]); + free(unique_names); + free(unique_aggrs); + + 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 csv_metric_only_print_callba= cks =3D { + .print_start =3D csv_metric_only_print_start, + .print_end =3D csv_metric_only_print_end, + .print_event =3D NULL, + .print_metric =3D csv_metric_only_print_metric, +}; + +int perf_stat__print_csv(struct evlist *evlist, struct perf_stat_config *c= onfig, + const struct target *target, const struct timespec *ts, + int argc, const char **argv) +{ + if (config->metric_only) { + struct csv_metric_only_print_state ps =3D { + .fp =3D config->output, + .sep =3D config->csv_sep, + .evlist =3D evlist, + }; + if (config->interval && ts) { + scnprintf(ps.timestamp, sizeof(ps.timestamp), "%lu.%09lu%s", + (unsigned long)ts->tv_sec, ts->tv_nsec, config->csv_sep); + } else { + ps.timestamp[0] =3D '\0'; + } + return perf_stat__print_cb(evlist, config, target, ts, argc, argv, + &csv_metric_only_print_callbacks, &ps); + } else { + struct csv_print_state ps =3D { + .fp =3D config->output, + .sep =3D config->csv_sep, + }; + + + + if (config->interval && ts) { + scnprintf(ps.timestamp, sizeof(ps.timestamp), "%lu.%09lu%s", + (unsigned long)ts->tv_sec, ts->tv_nsec, config->csv_sep); + } else { + ps.timestamp[0] =3D '\0'; + } + return perf_stat__print_cb(evlist, config, target, ts, argc, argv, + &csv_print_callbacks, &ps); + } +} --=20 2.55.0.141.g00534a21ce-goog From nobody Sat Jul 25 16:18:33 2026 Received: from mail-pg1-f201.google.com (mail-pg1-f201.google.com [209.85.215.201]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id B2494388395 for ; Thu, 16 Jul 2026 07:03:28 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.215.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1784185410; cv=none; b=T0ymNqqecrAcjrZsUOZt/9ybllNa2DBl7FDP54t3uHF75DW83OjWETN9xWV/ytJJ8PNvHyK3pMWTcwkkYXVbTg/aMUu8aUzO48Mf0/6Kq2S04zB9+M5TXc9aoAD/eUpQjab+pcfiQ3T45KihTmrv/1LRn3FLKy0wtd85PCIy6aI= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1784185410; c=relaxed/simple; bh=lIByhbsGDdTBHg6SoB2Lw1YUVdW86WkDZiUTwZ3OgmM=; h=Date:In-Reply-To:Mime-Version:References:Message-ID:Subject:From: To:Cc:Content-Type; b=ll4mj+Jta9zZTawpginYFL+1hnEKWHmOaaUGAvccjGiMP7qam4GYibezAtyzGMYWHM+Dd/S6zEVOQ69P2BnWA/wSKbJn4yCgfUV/OrdbDTYrFhS1//Jf19Ux20i0RQYTWcwoU6HIjKK5hJWyWkPT7SNR+ZBtJ7IRO5t9dZGpXmw= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=google.com; spf=pass smtp.mailfrom=flex--irogers.bounces.google.com; dkim=pass (2048-bit key) header.d=google.com header.i=@google.com header.b=obbXuh52; arc=none smtp.client-ip=209.85.215.201 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=google.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=flex--irogers.bounces.google.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=google.com header.i=@google.com header.b="obbXuh52" Received: by mail-pg1-f201.google.com with SMTP id 41be03b00d2f7-c88fc985a65so11451810a12.2 for ; Thu, 16 Jul 2026 00:03:28 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=20251104; t=1784185408; x=1784790208; darn=vger.kernel.org; h=content-type:cc:to:from:subject:message-id:references:mime-version :in-reply-to:date:from:to:cc:subject:date:message-id:reply-to :content-type; bh=c66jClBPGZu3ebFTqfTvjA1RhAEN8+/bEpFQ2FJgp9g=; b=obbXuh52qRi5MBDjz28P7OQcIbeYMWkEapA3f6PBohi0XUajmPPZPYnPpleBlpHshZ 8DA+XzNqbRCWBMyBKwq3tdTHMymWYekiPzVprFjOoa/vmMNA6099vthoaBotWOAYRCLn 3RbjBvavk0NoddIGm7DPagS4j52iWPi6YEFfCxXRqdw0xGvdiPJby0gRPvg8P3pZSAJK +sbff3d2YHXj3wLM9JlKS1ZdDAAPc6hSUrZVzhS25uH07Pz+D3Bl2N1kto7XX3YWF/nk ioWIPo5n4MxlbOezg/bOFlszvw5PnFPAWb5YKPh9QAGkyJaZPSkQMa77ZrYiY70ZFh0y cutw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1784185408; x=1784790208; h=content-type:cc:to:from:subject:message-id:references:mime-version :in-reply-to:date:x-gm-message-state:from:to:cc:subject:date :message-id:reply-to:content-type; bh=c66jClBPGZu3ebFTqfTvjA1RhAEN8+/bEpFQ2FJgp9g=; b=Dm9HfjlCZE0ddVpnC82qflm92DKmTt63DawjiL3U67+/VxVI0z4QIKDbCfLlSJDv+r d7KCmPKamJuTckQpQCKe9THBHiQPpdLOv2FXv/gFdptjzapZBL4IvPhUJRaT4BplQTnm 6bOgKCHNcdHDkdR9fR3kBvmXWBA/raaCYf6Deh5TQXnbeWAdGJtybDoMo8BGeHio5HXf fp7ZImuzcFE/elCMyW0fE7wUKrOdAlJ+pouUlxR+CvfyMlirR8Rqp6m6wWLyVAQCfoCG rLky+3yINrJy4xBc2tdWIA/Lwm9b7UfUiJab4fkfBBmMCRIDP46uNifftkGE61KWyAyL kOzA== X-Forwarded-Encrypted: i=1; AHgh+Rojvc+rfcb9gKuKKcwtSyDdU1WRqaZ84DXpFtkU66nEZ/sRKmM+PUeH4IZQX0VrdC0lhr0yEWIyVyAqD8E=@vger.kernel.org X-Gm-Message-State: AOJu0Yzta1FObXmaXEse7HQuOfyGsncnkUhWi7eezWxfsl0p82j4lNqz cOKHsXwh8FNsbMipHoAHupvkzryBbZEcmqmUIR0YUr9OR6349luILDNBaClUozS3x77jgjllFOM P8wBej6sZ0w== X-Received: from dlnn30.prod.google.com ([2002:a05:7022:619e:b0:13b:a0a0:3b98]) (user=irogers job=prod-delivery.src-stubby-dispatcher) by 2002:a05:6a21:4cc5:b0:3c0:9c19:65a7 with SMTP id adf61e73a8af0-3c36c4447camr6657730637.63.1784185407979; Thu, 16 Jul 2026 00:03:27 -0700 (PDT) Date: Thu, 16 Jul 2026 00:02:57 -0700 In-Reply-To: <20260716070303.507066-1-irogers@google.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: Mime-Version: 1.0 References: <20260716043223.388233-1-irogers@google.com> <20260716070303.507066-1-irogers@google.com> X-Mailer: git-send-email 2.55.0.141.g00534a21ce-goog Message-ID: <20260716070303.507066-9-irogers@google.com> Subject: [PATCH v4 08/14] perf stat: Extend CSV output linter to test core aggregation checks From: Ian Rogers To: irogers@google.com, acme@kernel.org, ctshao@google.com, namhyung@kernel.org 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 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" This patch extends tools/perf/tests/shell/stat+csv_output.sh to run all basic and core aggregation linter checks a second time under the --new CSV print flag: - check_no_args - check_system_wide - check_interval - check_event - check_per_thread - check_per_node - check_system_wide_no_aggr - check_per_core - check_per_socket - check_per_die This guarantees that CSV outputs produced by the decoupled printing engine are formally verified and column-valid across standard and interval modes. Signed-off-by: Ian Rogers Assisted-by: Antigravity:gemini-3.5-flash Acked-by: Chun-Tse Shao --- tools/perf/tests/shell/stat+csv_output.sh | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/tools/perf/tests/shell/stat+csv_output.sh b/tools/perf/tests/s= hell/stat+csv_output.sh index cd6fff597091..e4153a03d716 100755 --- a/tools/perf/tests/shell/stat+csv_output.sh +++ b/tools/perf/tests/shell/stat+csv_output.sh @@ -88,5 +88,21 @@ then else echo "[Skip] Skipping tests for system_wide_no_aggr, per_core, per_die an= d per_socket since socket id exposed via topology is invalid" fi +# New API CSV checks +perf_cmd=3D"--new -x$csv_sep -o ${stat_output}" +check_no_args "CSV (New API)" "$perf_cmd" +check_system_wide "CSV (New API)" "$perf_cmd" +check_interval "CSV (New API)" "$perf_cmd" +check_event "CSV (New API)" "$perf_cmd" +check_per_thread "CSV (New API)" "$perf_cmd" +check_per_node "CSV (New API)" "$perf_cmd" +if [ $skip_test -ne 1 ] +then + check_system_wide_no_aggr "CSV (New API)" "$perf_cmd" + check_per_core "CSV (New API)" "$perf_cmd" + check_per_die "CSV (New API)" "$perf_cmd" + check_per_socket "CSV (New API)" "$perf_cmd" +fi + cleanup exit 0 --=20 2.55.0.141.g00534a21ce-goog From nobody Sat Jul 25 16:18:33 2026 Received: from mail-pg1-f202.google.com (mail-pg1-f202.google.com [209.85.215.202]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id ACD0B387341 for ; Thu, 16 Jul 2026 07:03:30 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.215.202 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1784185411; cv=none; b=cri6N5QtpzlxzQ3rwYBydYF3OKXDRGMRQBnoy3PLyP/MSXPIooAAwFxkMLh5mOBST/deZun2nfeZAbVTICzNUWlWdTxHMEsDRd2SjtUraK6PwDBiWyGJPKAjef6lDA9r/cxlJhg80ygGET7zlrRXSxh6WZDi1jSgzXOFTbenegY= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1784185411; c=relaxed/simple; bh=2zRqavbTgYkq8HwTfEKM0Me45DGRRl8qebYC5U0RZho=; h=Date:In-Reply-To:Mime-Version:References:Message-ID:Subject:From: To:Cc:Content-Type; b=CauVG4New6J7Sl6Wg1R70XjJ3WyJGBGY8n8KP+vg65jkNamUfEG3eP006LFCEsjEQQMw8aEqA0KQ0czaKJVxWzfeGzX47JuWrt6oRU7ezlWCHR4SuhzzpTvSO1R9XN2EDWRiMNPeALerDGuCq0LkB36D4Cw2tbcWWQ+VO6e8kCM= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=google.com; spf=pass smtp.mailfrom=flex--irogers.bounces.google.com; dkim=pass (2048-bit key) header.d=google.com header.i=@google.com header.b=Zsx6x2f1; arc=none smtp.client-ip=209.85.215.202 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=google.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=flex--irogers.bounces.google.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=google.com header.i=@google.com header.b="Zsx6x2f1" Received: by mail-pg1-f202.google.com with SMTP id 41be03b00d2f7-c88e0d11a3fso2656806a12.0 for ; Thu, 16 Jul 2026 00:03:30 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=20251104; t=1784185410; x=1784790210; darn=vger.kernel.org; h=content-type:cc:to:from:subject:message-id:references:mime-version :in-reply-to:date:from:to:cc:subject:date:message-id:reply-to :content-type; bh=zcjJZCGcC+t8od9G4IqAATvOsW/z+G4n2l1/yDPO6Gk=; b=Zsx6x2f1eunK4mvl9zvFzNlc7yKOtsOkI426GkKgql2zhfhuFmW+O6SbfaUNMx5Pc3 GtKqvk+XG54Lii6MxmmQ3hZ93dFdS43FaXeElDJDja8kleVymeBvevOcHCtn79HmhKst paLIv4U1oDGDVBDMWtNwAdQOOh2sGwiieV5kdvT6IWygh5R24lWzQg2YqbLSM1TFfC0z vlWzxg+v3+7fYcqFmvdS+T/f8033LK2/KuSOMPs8hXdT5cYlN8Kwb+ap58SsEf+7hV0I OpMXMt5qqIcrlDaHmHRyAIWcjs/Z2+lwWrz6VpOD5lFnB14/9NXxr8+94K5CBnqK76wi KmxQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1784185410; x=1784790210; h=content-type:cc:to:from:subject:message-id:references:mime-version :in-reply-to:date:x-gm-message-state:from:to:cc:subject:date :message-id:reply-to:content-type; bh=zcjJZCGcC+t8od9G4IqAATvOsW/z+G4n2l1/yDPO6Gk=; b=TsVVwL3zIQDBsZ4DGXS/yB7Gkon5NkemGCtAboCeUZZV8cGGHNdGTr9qkmbkniJUIg iOGl9gpMuiK72EeztE6CAdvYl/Jl6pwkwrqw7HTlHOOwNJXCwHKOEpSz+0Z9XqQmIRi4 AsyPi+TkvPCfu1qLzGb3+99CWP4GlMdKYg3WOYV0dUmI26nvbm2YKTjUIxmHvPQA4Yho +8K4ByqePc18I6GjNAPf1kZ9hDsGTT3OWPCNME4IviaUYQmKp87vxTUt7P2gK7qMWuEp rJfLEYPQa/mZnInYIjiOzACIFOM+8B267jE1rsPxVj3v0jys6+tbsWF7Ikfzwf7ksl4D Y3UQ== X-Forwarded-Encrypted: i=1; AHgh+RrgRhlSqihzDVj6xqq4BkTJZ32niAJyZdO70FilGIeOoFbbQkKnUccjnz1PMmM50kcCkW+lB7zHUwmbEKM=@vger.kernel.org X-Gm-Message-State: AOJu0Yy9KJg9oOHioU7nYR3z5JDgAcJFNG881PfLXa0Gnsb44u9w59RN z7bDMQfh0VtKbodcoJMtKMzqTuSy66Xz8gO/oeBwGHSF6cj0xfdSDcDdFLMoeUt/O1X+4r8Ix25 JE0t4dRykkA== X-Received: from dlbph10.prod.google.com ([2002:a05:7022:370a:b0:13b:9d10:4fe5]) (user=irogers job=prod-delivery.src-stubby-dispatcher) by 2002:a05:6a21:9216:b0:3bf:bde7:d66f with SMTP id adf61e73a8af0-3c11076c6ecmr23488176637.2.1784185409798; Thu, 16 Jul 2026 00:03:29 -0700 (PDT) Date: Thu, 16 Jul 2026 00:02:58 -0700 In-Reply-To: <20260716070303.507066-1-irogers@google.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: Mime-Version: 1.0 References: <20260716043223.388233-1-irogers@google.com> <20260716070303.507066-1-irogers@google.com> X-Mailer: git-send-email 2.55.0.141.g00534a21ce-goog Message-ID: <20260716070303.507066-10-irogers@google.com> Subject: [PATCH v4 09/14] perf stat: Extend CSV output linter to test advanced PMU and metric-only checks From: Ian Rogers To: irogers@google.com, acme@kernel.org, ctshao@google.com, namhyung@kernel.org 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 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" This patch extends tools/perf/tests/shell/stat+csv_output.sh to run the advanced hardware PMU, topology-aware aggregation, and metric-only checks a second time under the --new CSV print flag: - check_per_cache_instance - check_per_cluster - check_metric_only This guarantees that CSV outputs produced by the decoupled printing callbacks are verified and column-valid under advanced aggregation modes and metric-only CSV row-column layouts. Signed-off-by: Ian Rogers Assisted-by: Antigravity:gemini-3.5-flash Acked-by: Chun-Tse Shao --- tools/perf/tests/shell/stat+csv_output.sh | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tools/perf/tests/shell/stat+csv_output.sh b/tools/perf/tests/s= hell/stat+csv_output.sh index e4153a03d716..c48d9e2b6429 100755 --- a/tools/perf/tests/shell/stat+csv_output.sh +++ b/tools/perf/tests/shell/stat+csv_output.sh @@ -102,7 +102,10 @@ then check_per_core "CSV (New API)" "$perf_cmd" check_per_die "CSV (New API)" "$perf_cmd" check_per_socket "CSV (New API)" "$perf_cmd" + check_per_cache_instance "CSV (New API)" "$perf_cmd" + check_per_cluster "CSV (New API)" "$perf_cmd" fi +check_metric_only "CSV (New API)" "$perf_cmd" =20 cleanup exit 0 --=20 2.55.0.141.g00534a21ce-goog From nobody Sat Jul 25 16:18:33 2026 Received: from mail-pg1-f201.google.com (mail-pg1-f201.google.com [209.85.215.201]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 5529938AC8C for ; Thu, 16 Jul 2026 07:03:32 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.215.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1784185414; cv=none; b=T7poChnqzm6BQzROP8J+bFaubTr2OlGN2Zcf8Zu+WQbDOWUua2zcmuowzYGjh7hhIxchZBkg1bDaA4bNXZRe0uOsgVAiwDa/LtYQSIzf9e609epvNt0/ESc8PF8tdVvCLzxj/G/Luy0TQTldyt5M2hJzqrFTmTfbOhOMEbJHPcI= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1784185414; c=relaxed/simple; bh=PhANDbGWND/DrXbk580KY77RKwTd4rvJ06CKfQYVGoQ=; h=Date:In-Reply-To:Mime-Version:References:Message-ID:Subject:From: To:Cc:Content-Type; b=l7iJOW0Ywki5AL129+Kh3B1/g52sZpOsb0biB5gEDe72bO6V4rHWYky6RdR3VttARJM69PjrpcBZfZsf3rOD5xMTJtmWXEYOmJcyRNfuKfh7pm04CGN5SLD/H59j/WJAX3rYJ5PuzSbCZGwuaQeNHRQ5CTZVDFfbb7vm2afCWCc= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=google.com; spf=pass smtp.mailfrom=flex--irogers.bounces.google.com; dkim=pass (2048-bit key) header.d=google.com header.i=@google.com header.b=IWVTYYsS; arc=none smtp.client-ip=209.85.215.201 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=google.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=flex--irogers.bounces.google.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=google.com header.i=@google.com header.b="IWVTYYsS" Received: by mail-pg1-f201.google.com with SMTP id 41be03b00d2f7-c9667280edeso7561793a12.2 for ; Thu, 16 Jul 2026 00:03:32 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=20251104; t=1784185412; x=1784790212; darn=vger.kernel.org; h=content-type:cc:to:from:subject:message-id:references:mime-version :in-reply-to:date:from:to:cc:subject:date:message-id:reply-to :content-type; bh=HwO/sP+rhydtkKNqO7NSv33UrBaqJY5nVaNGEQvI3j0=; b=IWVTYYsSG6cdX+KL7BI3RgSeUbBfsUGdEYGHuaDimKInKHrlL+0uNEg87h75n/BuPW oy05stTHceyJ+cSHvkyr/hGV2ycxy43Qd1xRPN9PSU56SaPSrnTqpcYQou+B6mvO/14a 68qGi8hyHkyUr/dwrLi6RJ4Lqihlh6wCgIRkbKlXUyF+VLUcLKmjoLxwDCYC1wh2wQjy ahQU0GFNW6jSIsP89+Nqw6W8dYq7+Cm5Ybn+y3/K+V8ILqyVaWEyXb+VBG2JKUNbRcKi liTSsoPfwIcFpUc6OLbms8XSxlZOGX08ebnCORTDiznvzTnCTJIRQZn3TU1Xz2bxQInJ Ofaw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1784185412; x=1784790212; h=content-type:cc:to:from:subject:message-id:references:mime-version :in-reply-to:date:x-gm-message-state:from:to:cc:subject:date :message-id:reply-to:content-type; bh=HwO/sP+rhydtkKNqO7NSv33UrBaqJY5nVaNGEQvI3j0=; b=ivyksVyETVdT5csTPI1DKtacEa/DE6YSUAkudIQkQy1cNA6B5bja5Jj9IQGyXtZi3j HiwoU4+PtaQLMgwazkBLW564yDPYLWQcb9+F3p30pL7A9mMvHs6UOpDN3KHGUnaZ3lKs h3EqU2L12FWJpv62N3zNO3E7ja01GSZ/jhh02GYTB5BuuFfWIKCesey3KdwQnHKujEjJ DjkpSYdGSZzI/8ZvMqgZcrn7WTNm8f7rDio4ktvib2ck4/DHVpunQqoCnA921jNt4FBk 1QKeKjvc5L82MKS0A+3Ysziy37IBh77pEUfWeSD4YOgj8wt8g49QuIMZ5G1j4FzsIRFu ob8Q== X-Forwarded-Encrypted: i=1; AHgh+RrrxKKeJfuzvdr/VKWLHR+CmRoagP+yyY8zeA1Aec2I73yCZTGBM4bQ95wPgHFT3rNTKDJ6pqdP0CZddcI=@vger.kernel.org X-Gm-Message-State: AOJu0Yz1sXQ11GbJgxECKMjMmvg66dCjIi/WJLvCW2ass/t+I23wH4KV 1Kkq4eSrYwjEQaMjp6bgXsdm4VSwrYIDXso8twVp8IvBjl0xVJD/Cj0WL1jYhkhVusLLwJk0bRl Hq62CgBxI0g== X-Received: from dlbut12.prod.google.com ([2002:a05:7022:7e0c:b0:13b:9d65:a542]) (user=irogers job=prod-delivery.src-stubby-dispatcher) by 2002:a05:6300:6705:b0:3c3:8ce8:1fd with SMTP id adf61e73a8af0-3c38ce8075amr1824909637.73.1784185411403; Thu, 16 Jul 2026 00:03:31 -0700 (PDT) Date: Thu, 16 Jul 2026 00:02:59 -0700 In-Reply-To: <20260716070303.507066-1-irogers@google.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: Mime-Version: 1.0 References: <20260716043223.388233-1-irogers@google.com> <20260716070303.507066-1-irogers@google.com> X-Mailer: git-send-email 2.55.0.141.g00534a21ce-goog Message-ID: <20260716070303.507066-11-irogers@google.com> Subject: [PATCH v4 10/14] perf stat: Implement streaming JSON formatting callbacks From: Ian Rogers To: irogers@google.com, acme@kernel.org, ctshao@google.com, namhyung@kernel.org 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 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" This patch implements streaming JSON formatting callbacks inside util/stat-print-json.c, replacing the empty stubs introduced in Commit 1. Delivers a highly optimized, zero-allocation, and 100% streaming print engine for JSON normal and metric-only modes. It bypasses dynamic queue events and metric lists entirely, formatting and streaming JSON objects directly onto the output file descriptor. Utilizes the newly centralized unified aggregation helpers to format CPU and thread keys inside the JSON objects, and incorporates full interval-mode timestamp printing support. Signed-off-by: Ian Rogers Assisted-by: Antigravity:gemini-3.5-flash Acked-by: Chun-Tse Shao --- tools/perf/util/stat-print-json.c | 340 +++++++++++++++++++++++++++++- 1 file changed, 333 insertions(+), 7 deletions(-) diff --git a/tools/perf/util/stat-print-json.c b/tools/perf/util/stat-print= -json.c index 4690c4286fd2..7ed00aae89f4 100644 --- a/tools/perf/util/stat-print-json.c +++ b/tools/perf/util/stat-print-json.c @@ -1,13 +1,339 @@ // SPDX-License-Identifier: GPL-2.0 -#include "stat-print.h" +#include +#include +#include +#include +#include +#include + +#include +#include #include +#include + +#include "cpumap.h" +#include "evlist.h" +#include "evsel.h" +#include "stat-print.h" +#include "stat.h" +#include "thread_map.h" + +static const char *metric_threshold_classify__str(enum metric_threshold_cl= assify thresh) +{ + const char *const strs[] =3D { + "unknown", "bad", "nearly bad", "less good", "good", + }; + _Static_assert(ARRAY_SIZE(strs) - 1 =3D=3D METRIC_THRESHOLD_GOOD, "missin= g enum value"); + return strs[thresh]; +} + +/** + * struct json_print_state - Print state context for JSON output. + * @fp: File descriptor to output to. + * @timestamp: Formatted interval timestamp (optional). + */ +struct json_print_state { + FILE *fp; + char timestamp[64]; +}; + +/** + * struct json_metric_only_print_state - Metric-only print state context f= or JSON output. + * @fp: File descriptor to output to. + * @timestamp: Formatted interval timestamp (optional). + * @evlist: Evlist to query entries from. + * @last_aggr_idx: The aggregation index of the last printed metric. + * @first_in_group: Whether the current metric is the first in its group. + */ +struct json_metric_only_print_state { + FILE *fp; + char timestamp[64]; + struct evlist *evlist; + int last_aggr_idx; + bool first_in_group; +}; + +/** + * print_aggr_id_json - Print the aggregation prefix for JSON format. + * + * Copied and adapted from stat-display.c. + */ +static void print_aggr_id_json(const struct perf_stat_config *config, FILE= *output, + struct evsel *evsel, struct aggr_cpu_id id, int aggr_nr) +{ + switch (config->aggr_mode) { + case AGGR_CORE: + fprintf(output, "\"core\" : \"S%d-D%d-C%d\", \"counters\" : %d, ", id.so= cket, + id.die, id.core, aggr_nr); + break; + case AGGR_CACHE: + fprintf(output, "\"cache\" : \"S%d-D%d-L%d-ID%d\", \"counters\" : %d, ",= id.socket, + id.die, id.cache_lvl, id.cache, aggr_nr); + break; + case AGGR_CLUSTER: + fprintf(output, "\"cluster\" : \"S%d-D%d-CLS%d\", \"counters\" : %d, ", = id.socket, + id.die, id.cluster, aggr_nr); + break; + case AGGR_DIE: + fprintf(output, "\"die\" : \"S%d-D%d\", \"counters\" : %d, ", id.socket,= id.die, + aggr_nr); + break; + case AGGR_SOCKET: + fprintf(output, "\"socket\" : \"S%d\", \"counters\" : %d, ", id.socket, = aggr_nr); + break; + case AGGR_NODE: + fprintf(output, "\"node\" : \"N%d\", \"counters\" : %d, ", id.node, aggr= _nr); + break; + case AGGR_NONE: + if (evsel->percore && !config->percore_show_thread) + fprintf(output, "\"core\" : \"S%d-D%d-C%d\", ", id.socket, id.die, id.c= ore); + else if (id.cpu.cpu > -1) + fprintf(output, "\"cpu\" : \"%d\", ", id.cpu.cpu); + break; + case AGGR_THREAD: + fprintf(output, "\"thread\" : \"%s-%d\", ", + perf_thread_map__comm(evsel->core.threads, id.thread_idx), + perf_thread_map__pid(evsel->core.threads, id.thread_idx)); + break; + case AGGR_GLOBAL: + case AGGR_UNSET: + case AGGR_MAX: + default: + break; + } +} + +/* + * JSON Output Callbacks - Normal Mode (100% Streaming & Zero-Allocation) + */ + +static int json_print_start(void *ctx __maybe_unused, + struct perf_stat_config *config __maybe_unused) +{ + return 0; +} + +static int json_print_event(void *ctx, struct perf_stat_config *config, + struct evsel *evsel, int aggr_idx, u64 val, + u64 ena, u64 run, double stdev_pct __maybe_unused, + const char *cgrp __maybe_unused) +{ + struct json_print_state *ps =3D ctx; + FILE *output =3D config->output; + bool ok =3D (run !=3D 0 && ena !=3D 0); + double enabled_percent =3D 100.0; + + fprintf(output, "{"); + + /* Print interval timestamp first if configured */ + if (config->interval && ps && ps->timestamp[0]) + fprintf(output, "%s", ps->timestamp); + + /* Print aggregation JSON fields if configured */ + if (config->aggr_map && aggr_idx >=3D 0) { + struct aggr_cpu_id id =3D config->aggr_map->map[aggr_idx]; + int aggr_nr =3D 0; + + if (evsel->stats && evsel->stats->aggr) + aggr_nr =3D evsel->stats->aggr[aggr_idx].nr; + + print_aggr_id_json(config, output, evsel, id, aggr_nr); + } + + if (ok) { + double sc =3D evsel->scale; + double avg =3D val * sc; + + fprintf(output, "\"counter-value\" : \"%f\"", avg); + } else { + const char *bad_count =3D evsel->supported ? CNTR_NOT_COUNTED : CNTR_NOT= _SUPPORTED; + + fprintf(output, "\"counter-value\" : \"%s\"", bad_count); + } + + fprintf(output, ", \"unit\" : \"%s\"", evsel->unit ?: ""); + /* Cast away const for legacy evsel__name */ + fprintf(output, ", \"event\" : \"%s\"", evsel__name((struct evsel *)evsel= )); + + if (run !=3D ena) + enabled_percent =3D 100.0 * run / ena; + fprintf(output, ", \"event-runtime\" : %" PRIu64 ", \"pcnt-running\" : %.= 2f", run, + enabled_percent); + fprintf(output, "}\n"); + + return 0; +} + +static int json_print_metric(void *ctx, struct perf_stat_config *config, + struct evsel *evsel, int aggr_idx, + const char *name, const char *unit, double val, + enum metric_threshold_classify thresh) +{ + struct json_print_state *ps =3D ctx; + FILE *output =3D config->output; + u64 run =3D 0, ena =3D 0; + double enabled_percent =3D 100.0; + struct perf_stat_evsel *ps_evsel =3D evsel->stats; + + if (ps_evsel && ps_evsel->aggr && aggr_idx >=3D 0 && + aggr_idx < ps_evsel->nr_aggr) { + run =3D ps_evsel->aggr[aggr_idx].counts.run; + ena =3D ps_evsel->aggr[aggr_idx].counts.ena; + } =20 -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) + fprintf(output, "{"); + + /* Print interval timestamp first if configured */ + if (config->interval && ps && ps->timestamp[0]) + fprintf(output, "%s", ps->timestamp); + + /* Print aggregation JSON fields if configured */ + if (config->aggr_map && aggr_idx >=3D 0) { + struct aggr_cpu_id id =3D config->aggr_map->map[aggr_idx]; + int aggr_nr =3D 0; + + if (evsel->stats && evsel->stats->aggr) + aggr_nr =3D evsel->stats->aggr[aggr_idx].nr; + + print_aggr_id_json(config, output, evsel, id, aggr_nr); + } + + if (run !=3D ena) + enabled_percent =3D 100.0 * run / ena; + fprintf(output, "\"event-runtime\" : %" PRIu64 ", \"pcnt-running\" : %.2f= ", run, + enabled_percent); + fprintf(output, ", \"metric-value\" : \"%f\"", val); + if (unit && unit[0] && name && name[0]) + fprintf(output, ", \"metric-unit\" : \"%s %s\"", unit, name); + else if (name && name[0]) + fprintf(output, ", \"metric-unit\" : \"%s\"", name); + else if (unit && unit[0]) + fprintf(output, ", \"metric-unit\" : \"%s\"", unit); + if (thresh !=3D METRIC_THRESHOLD_UNKNOWN) { + fprintf(output, ", \"metric-threshold\" : \"%s\"", + metric_threshold_classify__str(thresh)); + } + fprintf(output, "}\n"); + + return 0; +} + +static int json_print_end(void *ctx __maybe_unused, + struct perf_stat_config *config __maybe_unused) { return 0; } + +static const struct perf_stat_print_callbacks json_print_callbacks =3D { + .print_start =3D json_print_start, + .print_end =3D json_print_end, + .print_event =3D json_print_event, + .print_metric =3D json_print_metric, +}; + +/* + * JSON Output Callbacks - Metric-Only Mode (100% Streaming & Zero-Allocat= ion) + */ + +static int json_metric_only_print_start(void *ctx, + struct perf_stat_config *config __maybe_unused) +{ + struct json_metric_only_print_state *ps =3D ctx; + + /* Initialize to -2 to distinguish from -1 (a valid index in AGGR_GLOBAL = mode) */ + ps->last_aggr_idx =3D -2; + ps->first_in_group =3D true; + return 0; +} + +static int json_metric_only_print_metric(void *ctx, + struct perf_stat_config *config __maybe_unused, + struct evsel *evsel, int aggr_idx, + const char *name, const char *unit, double val, + enum metric_threshold_classify thresh __maybe_unused) +{ + struct json_metric_only_print_state *ps =3D ctx; + FILE *output =3D ps->fp; + + + if (aggr_idx !=3D ps->last_aggr_idx) { + if (ps->last_aggr_idx !=3D -2) + fprintf(output, "}\n"); + fprintf(output, "{"); + if (config->interval && ps->timestamp[0]) + fprintf(output, "%s", ps->timestamp); + if (config->aggr_map && aggr_idx >=3D 0 && + aggr_idx < config->aggr_map->nr) { + struct aggr_cpu_id id =3D config->aggr_map->map[aggr_idx]; + int aggr_nr =3D 0; + + if (evsel && evsel->stats && evsel->stats->aggr && + aggr_idx < evsel->stats->nr_aggr) + aggr_nr =3D evsel->stats->aggr[aggr_idx].nr; + + print_aggr_id_json(config, output, evsel, id, aggr_nr); + } + ps->last_aggr_idx =3D aggr_idx; + ps->first_in_group =3D true; + } + + if (!ps->first_in_group) + fprintf(output, ", "); + ps->first_in_group =3D false; + + if (unit && unit[0]) + fprintf(output, "\"%s %s\" : \"%.1f\"", unit, name, val); + else + fprintf(output, "\"%s\" : \"%.1f\"", name, val); + return 0; +} + +static int json_metric_only_print_end(void *ctx, + struct perf_stat_config *config __maybe_unused) +{ + struct json_metric_only_print_state *ps =3D ctx; + FILE *output =3D ps->fp; + + if (ps->last_aggr_idx !=3D -2) + fprintf(output, "}\n"); + return 0; +} + +static const struct perf_stat_print_callbacks json_metric_only_print_callb= acks =3D { + .print_start =3D json_metric_only_print_start, + .print_end =3D json_metric_only_print_end, + .print_event =3D NULL, + .print_metric =3D json_metric_only_print_metric, +}; + +int perf_stat__print_json(struct evlist *evlist, struct perf_stat_config *= config, + const struct target *target, const struct timespec *ts, + int argc, const char **argv) +{ + if (config->metric_only) { + struct json_metric_only_print_state ps =3D { + .fp =3D config->output, + .evlist =3D evlist, + }; + if (config->interval && ts) { + scnprintf(ps.timestamp, sizeof(ps.timestamp), "\"interval\" : %lu.%09lu= , ", + (unsigned long)ts->tv_sec, ts->tv_nsec); + } else { + ps.timestamp[0] =3D '\0'; + } + return perf_stat__print_cb(evlist, config, target, ts, argc, argv, + &json_metric_only_print_callbacks, &ps); + } else { + struct json_print_state ps =3D { + .fp =3D config->output, + }; + if (config->interval && ts) { + scnprintf(ps.timestamp, sizeof(ps.timestamp), "\"interval\" : %lu.%09lu= , ", + (unsigned long)ts->tv_sec, ts->tv_nsec); + } else { + ps.timestamp[0] =3D '\0'; + } + return perf_stat__print_cb(evlist, config, target, ts, argc, argv, + &json_print_callbacks, &ps); + } +} --=20 2.55.0.141.g00534a21ce-goog From nobody Sat Jul 25 16:18:33 2026 Received: from mail-pg1-f201.google.com (mail-pg1-f201.google.com [209.85.215.201]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 46C0D38D68F for ; Thu, 16 Jul 2026 07:03:34 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.215.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1784185415; cv=none; b=miZ4kgn57rrzKCYqumzv1HqZ3AArR35JzfoHtqID66GXVzJSUb+YNxLm+GWVQ3vNShsVRGXZW/35UegUJHQR+ADuvqS2oIAyKq1yggSUWXCxkZLFMpfdbQMg7QkZDWyhW6BjBqz6cf8kCMlswip1BTnyq92u8XsfiYIzlTzI/Bw= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1784185415; c=relaxed/simple; bh=FGW7ZR9yoomli7lZ8GOfioH5oDYhOp4P4xkrcpJjkpE=; h=Date:In-Reply-To:Mime-Version:References:Message-ID:Subject:From: To:Cc:Content-Type; b=QS3U0GF7tCy1WhCO79tw3lvgxKysL1OeULpJoO8P0LHIqF1jSLZIOmAkQWTMcWzuLhjRp++yWHIzYlv8W41CQEqywf/sHWvxbmujBVahRjQvOXyYiHp90sJ+eYfL4R+HHAZAD3AeLGxq4pDgi03SAnTED4jQrxr42EnfY/yCAmc= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=google.com; spf=pass smtp.mailfrom=flex--irogers.bounces.google.com; dkim=pass (2048-bit key) header.d=google.com header.i=@google.com header.b=gm43uGVA; arc=none smtp.client-ip=209.85.215.201 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=google.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=flex--irogers.bounces.google.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=google.com header.i=@google.com header.b="gm43uGVA" Received: by mail-pg1-f201.google.com with SMTP id 41be03b00d2f7-c8924f4d0a4so9155339a12.2 for ; Thu, 16 Jul 2026 00:03:34 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=20251104; t=1784185413; x=1784790213; darn=vger.kernel.org; h=content-type:cc:to:from:subject:message-id:references:mime-version :in-reply-to:date:from:to:cc:subject:date:message-id:reply-to :content-type; bh=c2WNQJI5LKocnmbHS2DrLyD6ql7IEqENaBv126GRlho=; b=gm43uGVAreCVHisYr2cNCN89u0O4C5jQn1QkdhXKAeF5HKru9IXElEi4qYzDm1AlDV eQVkJeXnMNrEd7Bo/eCIYiSGyaVnUCsfrs6LfByofiQkt9SM35HCSuHtQI3oSsgbfn/w pzKokPV+ivvfYiNcT9zwtEsvX9LWC9S9XHKONbZPvq9y8xq8eXioY6rnMChjzvD2sFv2 GWgO8Z5JNaCgySLuLYG4VK+GXGZYz1p+gwsAK7cdACUWa+vd2D+n8cFhCOjA7ITkpR8i DyEQfnlEjLUFWM9Tfpl6XyaDPY96tE1Yk/Xuz9hz5UXYFmYAey43L40q7nditiKdoO8e bC2g== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1784185413; x=1784790213; h=content-type:cc:to:from:subject:message-id:references:mime-version :in-reply-to:date:x-gm-message-state:from:to:cc:subject:date :message-id:reply-to:content-type; bh=c2WNQJI5LKocnmbHS2DrLyD6ql7IEqENaBv126GRlho=; b=mCG2zPQU7MRg6xIIfWMLH2l583so6KE9aDeLrMBdGyJPlAALrBifliPvDUoa06pVfB Ly4sflQOm127d24TYGEuKSA63QpGXUzMJISlxfD8ane30amTRy9M26YNPGwe3/TgPHjl AaZ5ZVTg1r2QImcVZqp26dZkcjoe/cd0CayF2wtz2hKZchX+NLoU906wqeL5NLB7/hE2 hnwHURVokIMizcyxC8dr9KiaPHwfUzF/L9HV78k8DD67kISjpj8KN2Qyt5fEne/2TfkW UgXsu6xFR1K3fC1pM4YmX8mj33ItBIJ5CWV2stgwNHlJQudccgYyQKpuY9BwoYdFDpXR hi5A== X-Forwarded-Encrypted: i=1; AHgh+RrAbxx6jTFa+5qw/wQ8qHrg+rGcNPt4s5Xsw9y5fcMn7pHGP0qUONHIk5G2rp3rYh0uEN5/tbUwkvB4W4E=@vger.kernel.org X-Gm-Message-State: AOJu0Yz8GHXghtFPO6X6L6QyjY6FzKak68Gjd2KTF6o3fZtucNIs6Iyv SJgIXWxQRwwBd+OzVpKXLi7A9t5QIErSZL+4e5E/IKu93PlsT1c7lV1u04IOg/FJb92oyAhcTWX 39Jr/AOtyOg== X-Received: from dybmi5.prod.google.com ([2002:a05:7301:f85:b0:30f:2f43:117b]) (user=irogers job=prod-delivery.src-stubby-dispatcher) by 2002:a05:6a20:3d22:b0:3c0:9c19:65b7 with SMTP id adf61e73a8af0-3c36c433382mr6914165637.63.1784185413200; Thu, 16 Jul 2026 00:03:33 -0700 (PDT) Date: Thu, 16 Jul 2026 00:03:00 -0700 In-Reply-To: <20260716070303.507066-1-irogers@google.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: Mime-Version: 1.0 References: <20260716043223.388233-1-irogers@google.com> <20260716070303.507066-1-irogers@google.com> X-Mailer: git-send-email 2.55.0.141.g00534a21ce-goog Message-ID: <20260716070303.507066-12-irogers@google.com> Subject: [PATCH v4 11/14] perf stat: Extend JSON output linter to test core aggregation checks From: Ian Rogers To: irogers@google.com, acme@kernel.org, ctshao@google.com, namhyung@kernel.org 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 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" This patch extends tools/perf/tests/shell/stat+json_output.sh to run all basic and standard CPU/thread aggregation checks under the --new JSON print flag: - check_no_args - check_system_wide - check_interval - check_event - check_per_thread - check_per_node - check_system_wide_no_aggr - check_per_core - check_per_socket - check_per_die This guarantees that JSON outputs produced by the decoupled, zero-allocation, and streaming print callbacks are formally validated across standard and interval modes. Signed-off-by: Ian Rogers Assisted-by: Antigravity:gemini-3.5-flash Acked-by: Chun-Tse Shao --- tools/perf/tests/shell/stat+json_output.sh | 71 ++++++++++++++-------- 1 file changed, 45 insertions(+), 26 deletions(-) diff --git a/tools/perf/tests/shell/stat+json_output.sh b/tools/perf/tests/= shell/stat+json_output.sh index 85d1ad7186c6..8168b861ade7 100755 --- a/tools/perf/tests/shell/stat+json_output.sh +++ b/tools/perf/tests/shell/stat+json_output.sh @@ -13,6 +13,7 @@ shelldir=3D$(dirname "$0") . "${shelldir}"/lib/setup_python.sh pythonchecker=3D$(dirname $0)/lib/perf_json_output_lint.py =20 +perf_new_opt=3D"" stat_output=3D$(mktemp /tmp/__perf_test.stat_output.json.XXXXX) =20 cleanup() { @@ -35,42 +36,42 @@ function ParanoidAndNotRoot() =20 check_no_args() { - echo -n "Checking json output: no args " - perf stat -j -o "${stat_output}" true + echo -n "Checking $api_label: no args " + perf stat -j $perf_new_opt -o "${stat_output}" true $PYTHON $pythonchecker --no-args --file "${stat_output}" echo "[Success]" } =20 check_system_wide() { - echo -n "Checking json output: system wide " + echo -n "Checking $api_label: system wide " if ParanoidAndNotRoot 0 then echo "[Skip] paranoia and not root" return fi - perf stat -j -a -o "${stat_output}" true + perf stat -j $perf_new_opt -a -o "${stat_output}" true $PYTHON $pythonchecker --system-wide --file "${stat_output}" echo "[Success]" } =20 check_system_wide_no_aggr() { - echo -n "Checking json output: system wide no aggregation " + echo -n "Checking $api_label: system wide no aggregation " if ParanoidAndNotRoot 0 then echo "[Skip] paranoia and not root" return fi - perf stat -j -A -a --no-merge -o "${stat_output}" true + perf stat -j $perf_new_opt -A -a --no-merge -o "${stat_output}" true $PYTHON $pythonchecker --system-wide-no-aggr --file "${stat_output}" echo "[Success]" } =20 check_interval() { - echo -n "Checking json output: interval " - perf stat -j -I 1000 -o "${stat_output}" true + echo -n "Checking $api_label: interval " + perf stat -j $perf_new_opt -I 1000 -o "${stat_output}" true $PYTHON $pythonchecker --interval --file "${stat_output}" echo "[Success]" } @@ -78,110 +79,110 @@ check_interval() =20 check_event() { - echo -n "Checking json output: event " - perf stat -j -e cpu-clock -o "${stat_output}" true + echo -n "Checking $api_label: event " + perf stat -j $perf_new_opt -e cpu-clock -o "${stat_output}" true $PYTHON $pythonchecker --event --file "${stat_output}" echo "[Success]" } =20 check_per_core() { - echo -n "Checking json output: per core " + echo -n "Checking $api_label: per core " if ParanoidAndNotRoot 0 then echo "[Skip] paranoia and not root" return fi - perf stat -j --per-core -a -o "${stat_output}" true + perf stat -j $perf_new_opt --per-core -a -o "${stat_output}" true $PYTHON $pythonchecker --per-core --file "${stat_output}" echo "[Success]" } =20 check_per_thread() { - echo -n "Checking json output: per thread " + echo -n "Checking $api_label: per thread " if ParanoidAndNotRoot 0 then echo "[Skip] paranoia and not root" return fi - perf stat -j --per-thread -p $$ -o "${stat_output}" true + perf stat -j $perf_new_opt --per-thread -p $$ -o "${stat_output}" true $PYTHON $pythonchecker --per-thread --file "${stat_output}" echo "[Success]" } =20 check_per_cache_instance() { - echo -n "Checking json output: per cache_instance " + echo -n "Checking $api_label: per cache_instance " if ParanoidAndNotRoot 0 then echo "[Skip] paranoia and not root" return fi - perf stat -j --per-cache -a true 2>&1 | $PYTHON $pythonchecker --per-cache + perf stat -j $perf_new_opt --per-cache -a true 2>&1 | $PYTHON $pythonchec= ker --per-cache echo "[Success]" } =20 check_per_cluster() { - echo -n "Checking json output: per cluster " + echo -n "Checking $api_label: per cluster " if ParanoidAndNotRoot 0 then echo "[Skip] paranoia and not root" return fi - perf stat -j --per-cluster -a true 2>&1 | $PYTHON $pythonchecker --per-cl= uster + perf stat -j $perf_new_opt --per-cluster -a true 2>&1 | $PYTHON $pythonch= ecker --per-cluster echo "[Success]" } =20 check_per_die() { - echo -n "Checking json output: per die " + echo -n "Checking $api_label: per die " if ParanoidAndNotRoot 0 then echo "[Skip] paranoia and not root" return fi - perf stat -j --per-die -a -o "${stat_output}" true + perf stat -j $perf_new_opt --per-die -a -o "${stat_output}" true $PYTHON $pythonchecker --per-die --file "${stat_output}" echo "[Success]" } =20 check_per_node() { - echo -n "Checking json output: per node " + echo -n "Checking $api_label: per node " if ParanoidAndNotRoot 0 then echo "[Skip] paranoia and not root" return fi - perf stat -j --per-node -a -o "${stat_output}" true + perf stat -j $perf_new_opt --per-node -a -o "${stat_output}" true $PYTHON $pythonchecker --per-node --file "${stat_output}" echo "[Success]" } =20 check_per_socket() { - echo -n "Checking json output: per socket " + echo -n "Checking $api_label: per socket " if ParanoidAndNotRoot 0 then echo "[Skip] paranoia and not root" return fi - perf stat -j --per-socket -a -o "${stat_output}" true + perf stat -j $perf_new_opt --per-socket -a -o "${stat_output}" true $PYTHON $pythonchecker --per-socket --file "${stat_output}" echo "[Success]" } =20 check_metric_only() { - echo -n "Checking json output: metric only " + echo -n "Checking $api_label: metric only " if [ "$(uname -m)" =3D "s390x" ] && ! grep '^facilities' /proc/cpuinfo |= grep -qw 67 then echo "[Skip] CPU-measurement counter facility not installed" return fi - perf stat -j --metric-only -M page_faults_per_second -o "${stat_output}" = true + perf stat -j $perf_new_opt --metric-only -M page_faults_per_second -o "${= stat_output}" true $PYTHON $pythonchecker --metric-only --file "${stat_output}" echo "[Success]" } @@ -214,6 +215,7 @@ check_for_topology() } =20 check_for_topology +api_label=3D"json output" check_no_args check_system_wide check_interval @@ -232,5 +234,22 @@ then else echo "[Skip] Skipping tests for system_wide_no_aggr, per_core, per_die an= d per_socket since socket id exposed via topology is invalid" fi +# Run New API JSON basic and standard aggregation checks +perf_new_opt=3D"--new" +api_label=3D"json (New API)" +check_no_args +check_system_wide +check_interval +check_event +check_per_thread +check_per_node +if [ $skip_test -ne 1 ] +then + check_system_wide_no_aggr + check_per_core + check_per_die + check_per_socket +fi + cleanup exit 0 --=20 2.55.0.141.g00534a21ce-goog From nobody Sat Jul 25 16:18:33 2026 Received: from mail-pg1-f201.google.com (mail-pg1-f201.google.com [209.85.215.201]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 33EE738E8BA for ; Thu, 16 Jul 2026 07:03:36 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.215.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1784185417; cv=none; b=eITy0V9dkemayiszrSG7HSMc4bHqymX7bseCQDZwBfifSN4ex2yrH2+zb0NnLhQ9Us488KzJ7kCR2q3zN/E2DIVK1XK1wi7YSLGsC9HvGoJ7VNP64e8R7utJ1vMDofS40n5JANoZXgy/GAjmuYNtDdDxVw/KdR811t3aYlamR6Q= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1784185417; c=relaxed/simple; bh=n3k27QJCIoNJRzPm7J40vLj7crVqdwZ1mpVk2AWukf0=; h=Date:In-Reply-To:Mime-Version:References:Message-ID:Subject:From: To:Cc:Content-Type; b=RVK8/ks02wxt6p+weUfoQTlUzXuiE811kH7qyLJlYV+5T7SIPmmoEOU7OYYxN2jQpAPJTKLvhpDcVLYVnaev8YAL1/ZRFc4Ywrc7Ih5neA0WipGCrYRIoHjhVdioIXpgFZrxmR1mt3w5p9uLOhwYT7cZ+c3X/8C7ts2mkfAuOGY= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=google.com; spf=pass smtp.mailfrom=flex--irogers.bounces.google.com; dkim=pass (2048-bit key) header.d=google.com header.i=@google.com header.b=IkYWu/nO; arc=none smtp.client-ip=209.85.215.201 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=google.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=flex--irogers.bounces.google.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=google.com header.i=@google.com header.b="IkYWu/nO" Received: by mail-pg1-f201.google.com with SMTP id 41be03b00d2f7-c894391f000so6736199a12.1 for ; Thu, 16 Jul 2026 00:03:36 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=20251104; t=1784185415; x=1784790215; darn=vger.kernel.org; h=content-type:cc:to:from:subject:message-id:references:mime-version :in-reply-to:date:from:to:cc:subject:date:message-id:reply-to :content-type; bh=6g1rpDOHm8pAfjUBaAbALxk3paqpS889TKdqZf1z3UQ=; b=IkYWu/nOtqMbD4V149BiasG3D2HMEoL5Lz518I7dX6SVBEMzDjBQlFpdE6Qi+BADQ+ booKWgPyPqqRTvDkteopKaeHcYlmh0Ol/V3kjCwTlOc+MTDHVkkUWcueB5MA3XQLJaDO H4dM1V8AHo18ONo5MXfAns+ODVdQyING3c0Dh+GL/tLQohHJgHVelLCWoKqxG8f+wnNp MNn3sGq4f1W7mvc0CuW8/6Y3r8F/gUAZu8nmmC5q/v82VMVy6tAiOoGYPJfAToS+jaoy ryeCCR2YQMY5dh85WdI0pDF3C9G3nutSCryZVun5KM359BYlrY59fR0YCFsJb0XU+gnd RMig== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1784185415; x=1784790215; h=content-type:cc:to:from:subject:message-id:references:mime-version :in-reply-to:date:x-gm-message-state:from:to:cc:subject:date :message-id:reply-to:content-type; bh=6g1rpDOHm8pAfjUBaAbALxk3paqpS889TKdqZf1z3UQ=; b=HmRx37I9IT5/EJrToFu3wN6Q/FlMcCQos5gDt2s2z3fqgjjdU/w912+e4zZ8dNS+pU 6ALsMdtgRqnKs4NUdWlsIhB61GkE7vdn87zYStYgIM/6iAFqN9WdUCHBZM3vz8qjs3e2 PpRUnEeSieGNpNfC2L56t+1d6q+EErDMJ2Lyi/owdFJE0mCwt76/QMXAQDflmDvfEQZL z7MSYGgmYj6Wrti6i4x2CH6/gNyPw/ie96EfG16Phtodqsc2F+wWM23CDiLYfDcQ6Izx nhHo2aD0Ktmfy8djdwgMfOn9tlqzaODvQPZKMa+jtsu3EdHTuhec42Y1SuhfL+7z/OWr /2gQ== X-Forwarded-Encrypted: i=1; AHgh+Roq+Y23jo8mLziKtu5GwgZzpqSI3IcuoBXGBJ/V7Tl4cd2FHQ9pdERIVyU9uRTgRkULBN/XXy+ZG/ZGlhI=@vger.kernel.org X-Gm-Message-State: AOJu0Yx2EYCqD2fPgA0QXmVe/20w1iDSgtZRjZY1Vh7HuC9nH1jSmnIy RQ1517pcPhx4EFLU5wi1ZvQiAm5/ojxtN68kc+HlVeqsR479Gh56WP9XwBRe+Gj2Et7vzNGR4BQ 4Ad3ae43WPA== X-Received: from dlbpv1.prod.google.com ([2002:a05:7023:901:b0:138:62a:280c]) (user=irogers job=prod-delivery.src-stubby-dispatcher) by 2002:a05:6a21:495:b0:3c0:9c1b:d0b5 with SMTP id adf61e73a8af0-3c1109f44ebmr22649357637.64.1784185415273; Thu, 16 Jul 2026 00:03:35 -0700 (PDT) Date: Thu, 16 Jul 2026 00:03:01 -0700 In-Reply-To: <20260716070303.507066-1-irogers@google.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: Mime-Version: 1.0 References: <20260716043223.388233-1-irogers@google.com> <20260716070303.507066-1-irogers@google.com> X-Mailer: git-send-email 2.55.0.141.g00534a21ce-goog Message-ID: <20260716070303.507066-13-irogers@google.com> Subject: [PATCH v4 12/14] perf stat: Extend JSON output linter to test advanced PMU and metric-only checks From: Ian Rogers To: irogers@google.com, acme@kernel.org, ctshao@google.com, namhyung@kernel.org 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 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" This patch extends tools/perf/tests/shell/stat+json_output.sh to run the advanced hardware PMU, topology-aware aggregation, and metric-only JSON checks a second time under the --new print flag: - check_per_cache_instance - check_per_cluster - check_metric_only This guarantees that JSON outputs produced by the decoupled streaming printing callbacks are verified and structurally valid under advanced topology-aware modes and metric-only JSON row-column layouts. Signed-off-by: Ian Rogers Assisted-by: Antigravity:gemini-3.5-flash Acked-by: Chun-Tse Shao --- tools/perf/tests/shell/stat+json_output.sh | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tools/perf/tests/shell/stat+json_output.sh b/tools/perf/tests/= shell/stat+json_output.sh index 8168b861ade7..d18cac5dcff1 100755 --- a/tools/perf/tests/shell/stat+json_output.sh +++ b/tools/perf/tests/shell/stat+json_output.sh @@ -249,7 +249,10 @@ then check_per_core check_per_die check_per_socket + check_per_cache_instance + check_per_cluster fi +check_metric_only =20 cleanup exit 0 --=20 2.55.0.141.g00534a21ce-goog From nobody Sat Jul 25 16:18:33 2026 Received: from mail-pj1-f74.google.com (mail-pj1-f74.google.com [209.85.216.74]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 4BA3939021D for ; Thu, 16 Jul 2026 07:03:38 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.216.74 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1784185419; cv=none; b=qt0+zkY7Il8YGBrczueWI6CGCRhaFrrRqo3F5yAU1+RdzY4BqmI+xxA6IL1DsViua5FW2NUTA+QjSzT1qs0HnsrXUq4qAxy54zXE0bZQzisooqosMlmEq53QIWmJcP7GVz2Su/gScevg45YRU7jvEW15+9H4xXLlTGpEbvjk2Js= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1784185419; c=relaxed/simple; bh=eNpfZh1dpIFAekfalGaQk+/eINW92GCphOzZiYJOV5s=; h=Date:In-Reply-To:Mime-Version:References:Message-ID:Subject:From: To:Cc:Content-Type; b=bBcejmFILwpg2J95tmnK7PC9gp7z1qrrgZda9kxzC2vNifsLsyWZ1OmFGeEtPg/DmlNvv8IucrraHmKMELnvl+Xj7hf3JGqg4WhyEW3Hs2yCKOp5W9/k3ZmIszt4CsO3cs1a9zpD5qXw9rsJfJQrMJ4gzuQSsz79+kNyFpYatTQ= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=google.com; spf=pass smtp.mailfrom=flex--irogers.bounces.google.com; dkim=pass (2048-bit key) header.d=google.com header.i=@google.com header.b=QfbUcIRJ; arc=none smtp.client-ip=209.85.216.74 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=google.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=flex--irogers.bounces.google.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=google.com header.i=@google.com header.b="QfbUcIRJ" Received: by mail-pj1-f74.google.com with SMTP id 98e67ed59e1d1-3811279d51aso5336745a91.1 for ; Thu, 16 Jul 2026 00:03:38 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=20251104; t=1784185418; x=1784790218; darn=vger.kernel.org; h=content-type:cc:to:from:subject:message-id:references:mime-version :in-reply-to:date:from:to:cc:subject:date:message-id:reply-to :content-type; bh=iU1fZww4Tcrag6lxNEeuKCJy8Y+7g+3ojiLQofYJ2MA=; b=QfbUcIRJvzKx3jExKpsWyL52G0SD9EV5x0j1sB4cVi5tuUqpw635AdGgT+jh7i3QdV xSfjA8ckUUP+rlR4AuEJCx7OKHRol3BhPgR8PL0Ck1EJADcmEtWLBTxGGTKFNLNCcOpl xE5DMAXBJI1W4orvh/MamGa2f7JCEbNm8MJ9riteLoZs7P1io2UdTFCUWTPgF6OvETBD Xe4HyOcbYgbeK38xMZ91cukICz853rNie8vdxaHSOsM7CnbhaAwKHJ/+HNxDi/mfLwFM IvuhinxNL6Lp6zChB7V3ljaqRKIGHPsaO+HA+YMuPhX+cC0v+E6UqPlgb/NntVhU27o7 8wHQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1784185418; x=1784790218; h=content-type:cc:to:from:subject:message-id:references:mime-version :in-reply-to:date:x-gm-message-state:from:to:cc:subject:date :message-id:reply-to:content-type; bh=iU1fZww4Tcrag6lxNEeuKCJy8Y+7g+3ojiLQofYJ2MA=; b=JqjMcgKriUOJhUbLx6RBZIFbj251Jh19LxZ6/iZrszaIWnLVesbcVDAcS1OmjTbHQl V34ZAUujVPK6YY6TQnJu61DV3bP4x9/6/LJpNYKO8+XtHpKAl3k6++WBGvHt98tQH76w ApTXEet2DFfv8CZbxM5XfxTF/T0CUVcCCbMBogul7AxXzhhNU4MFCFHMpOuVKbA1dzDv 4c2HwXak7CmHUnuRwk7llHiK4xRu3Ajix8NHa2Co20lf9iGoH+TF6LSXPtzjzKlItnW8 WQCnNCXVEkEq6oHMFR7qw8HH/vi0nQgwEovTqrGhWea6UO9dM7m2Qezz3byle/0X2wlH IUzA== X-Forwarded-Encrypted: i=1; AHgh+RqwhWLUfSvgHekqKBIMpTh9xYK72AzjAiQhVyzBctdkgOSLmntlT8xsvvNympM/fYWHKcSWkDuqEKDPDKM=@vger.kernel.org X-Gm-Message-State: AOJu0YyVh8z1MkvRBDq+rQ0rtQVpkOfeSzpVQRYqXr8klBEHYz44YmRT zoyCb5UFk1iS1rori5YT3OWw1eFKTvlUff45+jWTQ2cTlLhVvbH3/5HmITE+3gWwBa+BihB2/eZ lLCQarhB+4w== X-Received: from dyee27.prod.google.com ([2002:a05:693c:629b:b0:30c:83f3:7370]) (user=irogers job=prod-delivery.src-stubby-dispatcher) by 2002:a17:90b:38c8:b0:38d:dbc7:5178 with SMTP id 98e67ed59e1d1-38ddbc75254mr17482013a91.22.1784185417275; Thu, 16 Jul 2026 00:03:37 -0700 (PDT) Date: Thu, 16 Jul 2026 00:03:02 -0700 In-Reply-To: <20260716070303.507066-1-irogers@google.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: Mime-Version: 1.0 References: <20260716043223.388233-1-irogers@google.com> <20260716070303.507066-1-irogers@google.com> X-Mailer: git-send-email 2.55.0.141.g00534a21ce-goog Message-ID: <20260716070303.507066-14-irogers@google.com> Subject: [PATCH v4 13/14] perf stat: Add --new support to PMU metrics Python validator From: Ian Rogers To: irogers@google.com, acme@kernel.org, ctshao@google.com, namhyung@kernel.org 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 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" This patch extends the performance metrics value Python validation script tools/perf/tests/shell/lib/perf_metric_validation.py to support a new command-line argument `-new`: parser.add_argument("-new", help=3D"Use new printing API (--new)", ...) When set, the Validator class appends the `--new` option flag to its internally spawned `perf stat` commands: command =3D [tool, 'stat'] if self.new_print: command.append('--new') This enables validating Intel PMU metric mathematical values generated specifically by the decoupled, streaming JSON printing callbacks. Signed-off-by: Ian Rogers Assisted-by: Antigravity:gemini-3.5-flash Acked-by: Chun-Tse Shao --- tools/perf/tests/shell/lib/perf_metric_validation.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/tools/perf/tests/shell/lib/perf_metric_validation.py b/tools/p= erf/tests/shell/lib/perf_metric_validation.py index 3d52f94f22b9..b8397234eebc 100644 --- a/tools/perf/tests/shell/lib/perf_metric_validation.py +++ b/tools/perf/tests/shell/lib/perf_metric_validation.py @@ -36,7 +36,7 @@ class TestError: =20 class Validator: def __init__(self, rulefname, reportfname=3D'', t=3D5, debug=3DFalse, = datafname=3D'', fullrulefname=3D'', - workload=3D'true', metrics=3D'', cputype=3D'cpu'): + workload=3D'true', metrics=3D'', cputype=3D'cpu', new_pri= nt=3DFalse): self.rulefname =3D rulefname self.reportfname =3D reportfname self.rules =3D None @@ -68,6 +68,7 @@ class Validator: self.datafname =3D datafname self.debug =3D debug self.fullrulefname =3D fullrulefname + self.new_print =3D new_print =20 def __set_metrics(self, metrics=3D''): if metrics !=3D '': @@ -379,7 +380,10 @@ class Validator: =20 def _run_perf(self, metric, workload: str): tool =3D 'perf' - command =3D [tool, 'stat', '--cputype', self.cputype, '-j', '-M', = f"{metric}", "-a"] + command =3D [tool, 'stat'] + if self.new_print: + command.append('--new') + command.extend(['--cputype', self.cputype, '-j', '-M', f"{metric}"= , "-a"]) wl =3D workload.split() command.extend(wl) print(" ".join(command)) @@ -587,6 +591,8 @@ def main() -> None: parser.add_argument("-m", help=3D"Metric list to validate", default=3D= "") parser.add_argument("-cputype", help=3D"Only test metrics for the give= n CPU/PMU type", default=3D"cpu") + parser.add_argument("-new", help=3D"Use new printing API (--new)", + action=3D"store_true", default=3DFalse) args =3D parser.parse_args() outpath =3D Path(args.output_dir) reportf =3D Path.joinpath(outpath, 'perf_report.json') @@ -595,7 +601,7 @@ def main() -> None: =20 validator =3D Validator(args.rule, reportf, debug=3Dargs.debug, datafname=3Ddatafile, fullrulefname=3Dfullrule, = workload=3Dargs.wl, - metrics=3Dargs.m, cputype=3Dargs.cputype) + metrics=3Dargs.m, cputype=3Dargs.cputype, new_pr= int=3Dargs.new) ret =3D validator.test() =20 return ret --=20 2.55.0.141.g00534a21ce-goog From nobody Sat Jul 25 16:18:33 2026 Received: from mail-pg1-f201.google.com (mail-pg1-f201.google.com [209.85.215.201]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id D1FD73909A6 for ; Thu, 16 Jul 2026 07:03:39 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.215.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1784185421; cv=none; b=lheY2TGOdnOMVvvqU0kkCso18Ee5E21GlEKW/nKs8BCZRuQB5DdVJmXV9sArOxFicFx1zTITK4RMVU0fFJjD7RgW/IoXhMa0AGIJR3JTi6GIuEtvP+RH6rL0bdlPRVZAura73BunwkFn7nutYGxOwN4obpeQ71pIbFDayq2kajA= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1784185421; c=relaxed/simple; bh=Iy70ix2G6UJ2waMDQZdP0H/xHnMOXw4T4raHxlZze4E=; h=Date:In-Reply-To:Mime-Version:References:Message-ID:Subject:From: To:Cc:Content-Type; b=bK4hJLmGkM7k+c2p79k5CEJwBbndsRrimZ3PHMfoBzjuNAhdQJsmblCEVmu3q4zLR5ZjWFDpUeere0/xMCRDlGw/gJZ4XJupPB+oUjA1oLLLDxEtqb86RowBif49M8nP/y4Dm2Shfiz8nEuJk+0t5sr14wjKtd6TQzN8J1t5v9o= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=google.com; spf=pass smtp.mailfrom=flex--irogers.bounces.google.com; dkim=pass (2048-bit key) header.d=google.com header.i=@google.com header.b=YzBNpDag; arc=none smtp.client-ip=209.85.215.201 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=google.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=flex--irogers.bounces.google.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=google.com header.i=@google.com header.b="YzBNpDag" Received: by mail-pg1-f201.google.com with SMTP id 41be03b00d2f7-c8894570b58so2330616a12.3 for ; Thu, 16 Jul 2026 00:03:39 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=20251104; t=1784185419; x=1784790219; darn=vger.kernel.org; h=content-type:cc:to:from:subject:message-id:references:mime-version :in-reply-to:date:from:to:cc:subject:date:message-id:reply-to :content-type; bh=y/A4YjzcgmgX8KmC+PM43mOtsoEDH1gz6SlFM8oolbM=; b=YzBNpDagf0EvcCJUdzxxnvXEYnY/k3JihbfTq6zkseXKderlM3xfq4AQRkZQSi+X9D /gLlauToU6Fv08eiQX9IsRMPGx69zmq3ov7H+kgjAuKDjdVOO2nYUo/LwzJr6WsKMnto wFO1NI+UJ+nmIChFYMc6ldKFqflcB/t8XnL2LcA46UBhXtD0joOnDBmtJNF/zTDV8/dD RZLKx2vy7ax2oKCipT898JXL8wsFqBvThN8fQl663bpm3Af3wZA4WEdRMktFgRdSAoQ4 NibRMvoqMVZ5RljAbTAhTUoecDo4lZbfHCoFtpLiRl5t+un2CljuLPBiO6iu4Zs8MzXw cmIA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1784185419; x=1784790219; h=content-type:cc:to:from:subject:message-id:references:mime-version :in-reply-to:date:x-gm-message-state:from:to:cc:subject:date :message-id:reply-to:content-type; bh=y/A4YjzcgmgX8KmC+PM43mOtsoEDH1gz6SlFM8oolbM=; b=krVuQ3W7baszBX9VOnnG0DxPMtc1tWsB+yeYEUs+8hHB/Ae7KiP4qdho0e5jxadhvU RuGbW0J4wlh3ktOofroWIcYRH2bHa1yFQw63Z2CQJQBtkbLkkXiqlaYcRRHJ9blTU+wF qYJDUlUrNbkOLjYlW+EfBRfPrmL5DsMNfeZVzCvYIPLSnSqKHRnVYQC+hZaH7VYrc/lR KOYS9tvrGw8v3ktIecuGqM5SJLitf0HhH3SQpWZQpfr4nf1MzKUKuVH185P2XYmCdabt UBfBx1gfribdowAhvYjoBA1B1fdRY93RyALw9bJQGbxtoZPXcpTgj6FJ/qXOJJJn4jpp OPiw== X-Forwarded-Encrypted: i=1; AHgh+RqMgS3lN0gDOnP9EYYrHXL7H5qAXF2DfX0MYQyPxTY0L837pVUURHZ/aDXePPwu/Ytou19DMWi757DpuJo=@vger.kernel.org X-Gm-Message-State: AOJu0Yz3AqV6moSPrM6QNgz+4Mrh2WuvMWtmX75tArmESjVYyA2/t0Ml 15mZvej+UjQ3ImUkqwU+Xzv3chJ1gEjmqvA3nQs/GOvtjU2z60EtYXmYbh0U++FJAPdDEROnFuU bhoozWJRX/w== X-Received: from dlbek7.prod.google.com ([2002:a05:7022:c87:b0:13b:83ad:69bc]) (user=irogers job=prod-delivery.src-stubby-dispatcher) by 2002:a05:6a21:d82:b0:3b3:2703:110 with SMTP id adf61e73a8af0-3c110776c30mr24087912637.4.1784185418932; Thu, 16 Jul 2026 00:03:38 -0700 (PDT) Date: Thu, 16 Jul 2026 00:03:03 -0700 In-Reply-To: <20260716070303.507066-1-irogers@google.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: Mime-Version: 1.0 References: <20260716043223.388233-1-irogers@google.com> <20260716070303.507066-1-irogers@google.com> X-Mailer: git-send-email 2.55.0.141.g00534a21ce-goog Message-ID: <20260716070303.507066-15-irogers@google.com> Subject: [PATCH v4 14/14] perf stat: Extend PMU metrics value linter to validate --new outputs From: Ian Rogers To: irogers@google.com, acme@kernel.org, ctshao@google.com, namhyung@kernel.org 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 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" This patch updates tools/perf/tests/shell/stat_metrics_values.sh to run the metrics value validation linter twice: once for the legacy path, and once under the --new print flag using the newly introduced python validator `-new` argument: $PYTHON $pythonvalidator -rule $rulefile -output_dir $tmpdir ... -new This mathematically guarantees that calculated Intel PMU metric values staged inside the decoupled streaming JSON callbacks are parsed and validated cleanly against the formal rules engine. Signed-off-by: Ian Rogers Assisted-by: Antigravity:gemini-3.5-flash Acked-by: Chun-Tse Shao --- tools/perf/tests/shell/stat_metrics_values.sh | 22 ++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/tools/perf/tests/shell/stat_metrics_values.sh b/tools/perf/tes= ts/shell/stat_metrics_values.sh index 86c5c7f70933..3f545e88b69f 100755 --- a/tools/perf/tests/shell/stat_metrics_values.sh +++ b/tools/perf/tests/shell/stat_metrics_values.sh @@ -26,13 +26,29 @@ echo "Output will be stored in: $tmpdir" for cputype in /sys/bus/event_source/devices/cpu_*; do cputype=3D$(basename "$cputype") echo "Testing metrics for: $cputype" - $PYTHON $pythonvalidator -rule $rulefile -output_dir $tmpdir -wl "${workl= oad}" \ + mkdir -p "$tmpdir/legacy" + $PYTHON $pythonvalidator -rule $rulefile -output_dir "$tmpdir/legacy" -wl= "${workload}" \ -cputype "${cputype}" ret=3D$? - rm -rf $tmpdir if [ $ret -ne 0 ]; then - echo "Metric validation return with errors. Please check metrics reporte= d with errors." + echo "Metric validation return with errors. " \ + "Please check metrics reported with errors in: " \ + "$tmpdir/legacy" + exit $ret + fi + + echo "Testing metrics for: $cputype (New API)" + mkdir -p "$tmpdir/new" + $PYTHON $pythonvalidator -rule $rulefile -output_dir "$tmpdir/new" -wl "$= {workload}" \ + -cputype "${cputype}" -new + ret=3D$? + if [ $ret -ne 0 ]; then + echo "Metric validation return with errors (New API). " \ + "Please check metrics reported with errors in: " \ + "$tmpdir/new" + exit $ret fi done +rm -rf "$tmpdir" exit $ret =20 --=20 2.55.0.141.g00534a21ce-goog