From nobody Sun May 24 20:33:35 2026 Received: from mail-dy1-f201.google.com (mail-dy1-f201.google.com [74.125.82.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 F3898390221 for ; Fri, 22 May 2026 22:34:05 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=74.125.82.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1779489249; cv=none; b=p6q4WgcGAwu2USuypWIy8SRLceWP1gg71LoJBvnm9C5uIfdBLxdEDbMSPJAzcXLTjyey1ntSkybWKX4x4hXUdIIZ0UPePR4kSW2UNO0L6nWV9BM+taMai0V9hdsU7+EJuKGEkPSHpvvnxDCxBSnaZnfvzZKcVsfjwb+rK8/PSAo= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1779489249; c=relaxed/simple; bh=SEJ9V3QppsouKxljAzsgxaJf3kDFUO+ktFj1QQ+A4MI=; h=Date:In-Reply-To:Mime-Version:References:Message-ID:Subject:From: To:Content-Type; b=E3B539wPGMGKKb2AGhj/rdb3ScDez0SLPFxmtF4dfhFUOT533d8Qn0DMo4aaGqMOBZOVPb0FVtpG11Z/wcU5pgz3fPdB4c6APZICB3UUjS+Rb8df5ooSwdssvwW0Peq570EQHGD/iMVy+tpFUgkSWi4HL8gJwHH+3yLTktZaK4Y= 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=d1M3t6GN; arc=none smtp.client-ip=74.125.82.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="d1M3t6GN" Received: by mail-dy1-f201.google.com with SMTP id 5a478bee46e88-2ee1da7a13fso9393691eec.1 for ; Fri, 22 May 2026 15:34:05 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=20251104; t=1779489245; x=1780094045; darn=vger.kernel.org; h=to:from:subject:message-id:references:mime-version:in-reply-to:date :from:to:cc:subject:date:message-id:reply-to; bh=EG6sQaWiEq8xk/VtLUy/bDKisSgvoM/2FB6X6AJLzSM=; b=d1M3t6GNGUKHAjFmjS5DXuloB9ZOkHjAFMkq+WE+2av3PK9ebDqr9ySxvDKvI2/koT 2jwOcOaxFpAOnIawFmUXXhK0bpz0vJv8i6yig/raBybklch+qmUvwiplOvf15rZJmLZA rqJ1joHs+cuRlVt4X9KGnLcedpSDdKEwGO7S+XqI+hjEu4YCa+UQzwpa10jZbCXXt3Po vPcOyWvgXWIDq6vm3iw8WqRf5/eS+GXQ5P6tUnYUyTrPfSF4sXJzrvsSQgur5I3sEbFe Lys+4TiyAdVlj+ydGxLEJYTsiiksJgaJzF09aXufqXvBMal7aLjqnniLBi1Oye8cZLoI VXDQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1779489245; x=1780094045; h=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; bh=EG6sQaWiEq8xk/VtLUy/bDKisSgvoM/2FB6X6AJLzSM=; b=mBYOPZad6UaqgcJ35Vu822XncVwd4mRU8fYqlKxtL7HgrL7LEPexjECLrBdjKZ7Fz+ iqixi8Xi4zjc0GlXtl6+9OVvPnPibJpTbLcD7TeNvcKKtwnSlCerzxIHmgAB5qZeyUj7 7kLFm4tN0hMNFpPAUrxqxPC/vz6XAnZkJiRtmbHnYqWaIm4mnZLZCHBAh5QpaS+H/wXC XjtBeJkhfQHo25Y03lqlEYnxhu0peu/3j5LAkcTEAMw8CBjxMW7gtdeNspLqby/oFxx3 PerDfxKaDnbKZAGnNYvbanz2Q3orFkzReV3bKYNXawLcnR14s6OZV4XHIwMROy92+FWT ayPA== X-Forwarded-Encrypted: i=1; AFNElJ9fEuSkPC89Se2VtZFhX8TTD7s/hq8TWoIg3xX1h4eXDeP4+HQS3V0dTcnXpxq6PoJ8DVxSvFAFvTT8HFY=@vger.kernel.org X-Gm-Message-State: AOJu0YzE/BUuQfE9iRb/Ag0m47UDLyBPc/kZADjS6AgzFBfl55/XmC0h bakB5ac71PjQLvyKDj6Wl2Vyjijpc27lld4VKqaY1n5QVElf4UsiMGfQfDCdNODFK5rqx6Hki0N 3c6Sn9GHa1g== X-Received: from dybux5.prod.google.com ([2002:a05:7301:5005:b0:2f2:3f95:c586]) (user=irogers job=prod-delivery.src-stubby-dispatcher) by 2002:a05:7301:9c93:b0:2ed:e14:e956 with SMTP id 5a478bee46e88-304490e124amr2842702eec.32.1779489244889; Fri, 22 May 2026 15:34:04 -0700 (PDT) Date: Fri, 22 May 2026 15:33:29 -0700 In-Reply-To: <20260522223342.2393553-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: <20260522223342.2393553-1-irogers@google.com> X-Mailer: git-send-email 2.54.0.794.g4f17f83d09-goog Message-ID: <20260522223342.2393553-2-irogers@google.com> Subject: [RFC PATCH v1 01/14] perf stat: Introduce core generic print traversal engine and header stubs From: Ian Rogers To: Peter Zijlstra , Ingo Molnar , Arnaldo Carvalho de Melo , Namhyung Kim , Jiri Olsa , Ian Rogers , Adrian Hunter , James Clark , linux-kernel@vger.kernel.org, linux-perf-users@vger.kernel.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/Buil= d, without affecting the legacy print path. Signed-off-by: Ian Rogers TAG=3Dagy CONV=3D7c33d6a0-70a2-454a-82f7-4de2101a5b9c --- tools/perf/builtin-stat.c | 261 ++++++++-------- tools/perf/util/Build | 4 + 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 | 487 ++++++++++++++++++++++++++++++ tools/perf/util/stat-print.h | 129 ++++++++ 7 files changed, 791 insertions(+), 129 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 99d7db372b48..ef8f5da99c64 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; @@ -1091,7 +1094,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; @@ -2455,155 +2461,152 @@ int cmd_stat(int argc, const char **argv) bool affinity =3D true, affinity_set =3D false; struct option stat_options[] =3D { OPT_BOOLEAN('T', "transaction", &transaction_run, - "hardware transaction statistics"), + "hardware transaction statistics"), OPT_CALLBACK('e', "event", &parse_events_option_args, "event", - "event selector. use 'perf list' to list available events", - parse_events_option), - OPT_CALLBACK(0, "filter", &evsel_list, "filter", - "event filter", parse_filter), + "event selector. use 'perf list' to list available events", + parse_events_option), + OPT_CALLBACK(0, "filter", &evsel_list, "filter", "event filter", parse_f= ilter), OPT_BOOLEAN('i', "no-inherit", &stat_config.no_inherit, - "child tasks do not inherit counters"), - OPT_STRING('p', "pid", &target.pid, "pid", - "stat events on existing process id"), - OPT_STRING('t', "tid", &target.tid, "tid", - "stat events on existing thread id"), + "child tasks do not inherit counters"), + OPT_STRING('p', "pid", &target.pid, "pid", "stat events on existing proc= ess id"), + OPT_STRING('t', "tid", &target.tid, "tid", "stat events on existing thre= ad id"), #ifdef HAVE_BPF_SKEL OPT_STRING('b', "bpf-prog", &target.bpf_str, "bpf-prog-id", - "stat events on existing bpf program id"), - OPT_BOOLEAN(0, "bpf-counters", &target.use_bpf, - "use bpf program to count events"), + "stat events on existing bpf program id"), + OPT_BOOLEAN(0, "bpf-counters", &target.use_bpf, "use bpf program to coun= t events"), OPT_STRING(0, "bpf-attr-map", &target.attr_map, "attr-map-path", - "path to perf_event_attr map"), + "path to perf_event_attr map"), #endif OPT_BOOLEAN('a', "all-cpus", &target.system_wide, - "system-wide collection from all CPUs"), + "system-wide collection from all CPUs"), OPT_BOOLEAN(0, "scale", &stat_config.scale, - "Use --no-scale to disable counter scaling for multiplexing"), + "Use --no-scale to disable counter scaling for multiplexing"), OPT_INCR('v', "verbose", &verbose, - "be more verbose (show counter open errors, etc)"), + "be more verbose (show counter open errors, etc)"), OPT_INTEGER('r', "repeat", &stat_config.run_count, - "repeat command and print average + stddev (max: 100, forever: 0)"), + "repeat command and print average + stddev (max: 100, forever: 0)"), OPT_BOOLEAN(0, "table", &stat_config.walltime_run_table, - "display details about each run (only with -r option)"), + "display details about each run (only with -r option)"), OPT_BOOLEAN('n', "null", &stat_config.null_run, - "null run - dont start any counters"), - OPT_INCR('d', "detailed", &detailed_run, - "detailed run - start a lot of events"), - OPT_BOOLEAN('S', "sync", &sync_run, - "call sync() before starting a run"), + "null run - dont start any counters"), + OPT_INCR('d', "detailed", &detailed_run, "detailed run - start a lot of = events"), + OPT_BOOLEAN('S', "sync", &sync_run, "call sync() before starting a run"), OPT_CALLBACK_NOOPT('B', "big-num", NULL, NULL, - "print large numbers with thousands\' separators", - stat__set_big_num), + "print large numbers with thousands\' separators", + stat__set_big_num), OPT_STRING('C', "cpu", &target.cpu_list, "cpu", - "list of cpus to monitor in system-wide"), + "list of cpus to monitor in system-wide"), OPT_BOOLEAN('A', "no-aggr", &opt_mode.no_aggr, - "disable aggregation across CPUs or PMUs"), + "disable aggregation across CPUs or PMUs"), OPT_BOOLEAN(0, "no-merge", &opt_mode.no_aggr, - "disable aggregation the same as -A or -no-aggr"), + "disable aggregation the same as -A or -no-aggr"), OPT_BOOLEAN(0, "hybrid-merge", &stat_config.hybrid_merge, - "Merge identical named hybrid events"), + "Merge identical named hybrid events"), OPT_STRING('x', "field-separator", &stat_config.csv_sep, "separator", - "print counts with custom separator"), + "print counts with custom separator"), OPT_BOOLEAN('j', "json-output", &stat_config.json_output, - "print counts in JSON format"), + "print counts in JSON format"), OPT_CALLBACK('G', "cgroup", &evsel_list, "name", - "monitor event in cgroup name only", parse_stat_cgroups), + "monitor event in cgroup name only", parse_stat_cgroups), OPT_STRING(0, "for-each-cgroup", &stat_config.cgroup_list, "name", - "expand events for each cgroup"), + "expand events for each cgroup"), OPT_STRING('o', "output", &output_name, "file", "output file name"), OPT_BOOLEAN(0, "append", &append_file, "append to the output file"), - OPT_INTEGER(0, "log-fd", &output_fd, - "log output to fd, instead of stderr"), + OPT_INTEGER(0, "log-fd", &output_fd, "log output to fd, instead of stder= r"), OPT_STRING(0, "pre", &pre_cmd, "command", - "command to run prior to the measured command"), + "command to run prior to the measured command"), OPT_STRING(0, "post", &post_cmd, "command", - "command to run after to the measured command"), + "command to run after to the measured command"), OPT_UINTEGER('I', "interval-print", &stat_config.interval, - "print counts at regular interval in ms " - "(overhead is possible for values <=3D 100ms)"), + "print counts at regular interval in ms " + "(overhead is possible for values <=3D 100ms)"), OPT_INTEGER(0, "interval-count", &stat_config.times, - "print counts for fixed number of times"), + "print counts for fixed number of times"), OPT_BOOLEAN(0, "interval-clear", &stat_config.interval_clear, - "clear screen in between new interval"), - OPT_UINTEGER(0, "timeout", &stat_config.timeout, + "clear screen in between new interval"), + OPT_UINTEGER( + 0, "timeout", &stat_config.timeout, "stop workload and print counts after a timeout period in ms (>=3D 10ms= )"), OPT_BOOLEAN(0, "per-socket", &opt_mode.socket, - "aggregate counts per processor socket"), + "aggregate counts per processor socket"), OPT_BOOLEAN(0, "per-die", &opt_mode.die, "aggregate counts per processor= die"), OPT_BOOLEAN(0, "per-cluster", &opt_mode.cluster, - "aggregate counts per processor cluster"), - OPT_CALLBACK_OPTARG(0, "per-cache", &opt_mode.cache, &stat_config.aggr_l= evel, - "cache level", "aggregate count at this cache level (Default: LLC)", - parse_cache_level), + "aggregate counts per processor cluster"), + OPT_CALLBACK_OPTARG( + 0, "per-cache", &opt_mode.cache, &stat_config.aggr_level, "cache level", + "aggregate count at this cache level (Default: LLC)", parse_cache_level= ), OPT_BOOLEAN(0, "per-core", &opt_mode.core, - "aggregate counts per physical processor core"), + "aggregate counts per physical processor core"), OPT_BOOLEAN(0, "per-thread", &opt_mode.thread, "aggregate counts per thr= ead"), OPT_BOOLEAN(0, "per-node", &opt_mode.node, "aggregate counts per numa no= de"), - OPT_INTEGER('D', "delay", &target.initial_delay, + OPT_INTEGER( + 'D', "delay", &target.initial_delay, "ms to wait before starting measurement after program start (-1: start = with events disabled)"), OPT_CALLBACK_NOOPT(0, "metric-only", &stat_config.metric_only, NULL, - "Only print computed metrics. No raw values", enable_metric_only), + "Only print computed metrics. No raw values", + enable_metric_only), OPT_BOOLEAN(0, "metric-no-group", &stat_config.metric_no_group, - "don't group metric events, impacts multiplexing"), + "don't group metric events, impacts multiplexing"), OPT_BOOLEAN(0, "metric-no-merge", &stat_config.metric_no_merge, - "don't try to share events between metrics in a group"), + "don't try to share events between metrics in a group"), OPT_BOOLEAN(0, "metric-no-threshold", &stat_config.metric_no_threshold, - "disable adding events for the metric threshold calculation"), - OPT_BOOLEAN(0, "topdown", &topdown_run, - "measure top-down statistics"), + "disable adding events for the metric threshold calculation"), + OPT_BOOLEAN(0, "topdown", &topdown_run, "measure top-down statistics"), #ifdef HAVE_ARCH_X86_64_SUPPORT OPT_BOOLEAN(0, "record-tpebs", &tpebs_recording, - "enable recording for tpebs when retire_latency required"), + "enable recording for tpebs when retire_latency required"), OPT_CALLBACK(0, "tpebs-mode", &tpebs_mode, "tpebs-mode", - "Mode of TPEBS recording: mean, min or max", - parse_tpebs_mode), + "Mode of TPEBS recording: mean, min or max", parse_tpebs_mode), #endif OPT_UINTEGER(0, "td-level", &stat_config.topdown_level, - "Set the metrics level for the top-down statistics (0: max level)"), - OPT_BOOLEAN(0, "smi-cost", &smi_cost, - "measure SMI cost"), + "Set the metrics level for the top-down statistics (0: max level)"= ), + OPT_BOOLEAN(0, "smi-cost", &smi_cost, "measure SMI cost"), OPT_CALLBACK('M', "metrics", &evsel_list, "metric/metric group list", - "monitor specified metrics or metric groups (separated by ,)", - append_metric_groups), + "monitor specified metrics or metric groups (separated by ,)", + append_metric_groups), OPT_BOOLEAN_FLAG(0, "all-kernel", &stat_config.all_kernel, - "Configure all used events to run in kernel space.", - PARSE_OPT_EXCLUSIVE), + "Configure all used events to run in kernel space.", + PARSE_OPT_EXCLUSIVE), OPT_BOOLEAN_FLAG(0, "all-user", &stat_config.all_user, - "Configure all used events to run in user space.", - PARSE_OPT_EXCLUSIVE), + "Configure all used events to run in user space.", + PARSE_OPT_EXCLUSIVE), OPT_BOOLEAN(0, "percore-show-thread", &stat_config.percore_show_thread, - "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, "summary", &stat_config.summary, - "print summary for interval mode"), + "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 inter= val mode"), OPT_BOOLEAN(0, "no-csv-summary", &stat_config.no_csv_summary, - "don't print 'summary' for CSV summary output"), + "don't print 'summary' for CSV summary output"), OPT_BOOLEAN(0, "quiet", &quiet, - "don't print any output, messages or warnings (useful with record)"), + "don't print any output, messages or warnings (useful with record)"= ), OPT_BOOLEAN_SET(0, "affinity", &affinity, &affinity_set, - "enable (default) or disable affinity optimizations to reduce IPIs"), + "enable (default) or disable affinity optimizations to reduce IPIs"), OPT_CALLBACK(0, "cputype", &evsel_list, "hybrid cpu type", - "Only enable events on applying cpu with this type " - "for hybrid platform (e.g. core or atom)", - parse_cputype), - OPT_CALLBACK(0, "pmu-filter", &evsel_list, "pmu", + "Only enable events on applying cpu with this type " + "for hybrid platform (e.g. core or atom)", + parse_cputype), + OPT_CALLBACK( + 0, "pmu-filter", &evsel_list, "pmu", "Only enable events on applying pmu with specified " "for multiple pmus with same type(e.g. hisi_sicl2_cpa0 or hisi_sicl0_cp= a0)", parse_pmu_filter), #ifdef HAVE_LIBPFM OPT_CALLBACK(0, "pfm-events", &evsel_list, "event", - "libpfm4 event selector. use 'perf list' to list available events", - parse_libpfm_events_option), + "libpfm4 event selector. use 'perf list' to list available events", + parse_libpfm_events_option), #endif - OPT_CALLBACK(0, "control", &stat_config, "fd:ctl-fd[,ack-fd] or fifo:ctl= -fifo[,ack-fifo]", + OPT_CALLBACK( + 0, "control", &stat_config, + "fd:ctl-fd[,ack-fd] or fifo:ctl-fifo[,ack-fifo]", "Listen on ctl-fd descriptor for command to control measurement ('enabl= e': enable events, 'disable': disable events).\n" "\t\t\t Optionally send control command completion ('ack\\n') to ack-f= d descriptor.\n" "\t\t\t Alternatively, ctl-fifo / ack-fifo will be opened and used as = ctl-fd / ack-fd.", parse_control_option), OPT_CALLBACK_OPTARG(0, "iostat", &evsel_list, &stat_config, "default", - "measure I/O performance metrics provided by arch/platform", - iostat_parse), + "measure I/O performance metrics provided by arch/platform", + iostat_parse), OPT_END() }; const char * const stat_usage[] =3D { diff --git a/tools/perf/util/Build b/tools/perf/util/Build index 4bbc78b1f741..b03099e820d4 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-print-csv.c b/tools/perf/util/stat-print-= csv.c new file mode 100644 index 000000000000..e9d1e7c30c90 --- /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..72df7a94095d --- /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..83987e97c889 --- /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..3a634396c7c6 --- /dev/null +++ b/tools/perf/util/stat-print.c @@ -0,0 +1,487 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#include "stat-print.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) { + cpu_aggr_map__for_each_idx(aggr_idx, config->aggr_map) { + 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) { + 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(const struct perf_stat_config *confi= g, + 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; + + 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; + + 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) >=3D 0) { + 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 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", + }; + for (size_t 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(const struct perf_stat_config *conf= ig, + 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) { + if (!config->metric_only && + (!evsel->default_metricgroup || evsel->default_show_events)) { + if (!is_basic_shadow_metric(mexp->metric_name)) + continue; + } + + ret =3D calculate_and_print_metric(config, cb, outer_ctx, mexp, evsel, a= ggr_idx); + if (ret) + return ret; + } + return 0; +} + +/** + * perf_stat_print_metrics - Entry point for metric calculation & printing. + * + * Returns 0 on success, or a negative error code on failure. + */ +static int perf_stat_print_metrics(const struct perf_stat_config *config, + const struct perf_stat_print_callbacks *cb, void *outer_ctx, + struct evsel *evsel, int aggr_idx) +{ + if (config->iostat_run) { + /* IOSTAT metrics not supported yet in new API */ + return 0; + } + + return perf_stat_print_metricgroup(config, cb, outer_ctx, evsel, aggr_idx= ); +} + +int perf_stat__print_cb(struct evlist *evlist, const struct perf_stat_conf= ig *config, + const struct target *target __maybe_unused, + const struct timespec *ts __maybe_unused, int argc __maybe_unused, + const char **argv __maybe_unused, + const struct perf_stat_print_callbacks *cb, void *ctx) +{ + struct evsel *counter; + int aggr_idx; + int ret =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) { + if (evsel__is_hybrid(counter)) { + if (config->hybrid_merge && + counter->first_wildcard_match !=3D NULL) + continue; + } else { + if (counter->first_wildcard_match !=3D NULL) + 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); + 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) { + if (evsel__is_hybrid(counter)) { + if (config->hybrid_merge && + counter->first_wildcard_match !=3D NULL) + continue; + } else { + if (counter->first_wildcard_match !=3D NULL) + 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); + 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, const struct perf_stat_config = *config, + const struct target *target, const struct timespec *ts, int argc, + const char **argv) +{ + if (config->csv_output) { + return perf_stat__print_csv(evlist, config, target, ts, argc, argv); + } else if (config->json_output) { + return perf_stat__print_json(evlist, config, target, ts, argc, argv); + } else { + return perf_stat__print_std(evlist, config, target, ts, argc, argv); + } +} diff --git a/tools/perf/util/stat-print.h b/tools/perf/util/stat-print.h new file mode 100644 index 000000000000..0ce83b51eccd --- /dev/null +++ b/tools/perf/util/stat-print.h @@ -0,0 +1,129 @@ +/* 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 "" + +struct evlist; +struct perf_stat_config; +struct target; +struct timespec; +struct evsel; +struct aggr_cpu_id; + +/** + * 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, const struct perf_stat_config *config); + + /** + * print_end - Called after all events and metrics have been traversed. + * Executes the actual formatting and printing of the buffered tree. + * @ctx: Opaque context pointer. + * @config: Perf stat configuration. + */ + int (*print_end)(void *ctx, const struct perf_stat_config *config); + + /** + * print_event - Called to buffer an event (counter) value. + * @ctx: Opaque context pointer. + * @config: Perf stat configuration. + * @evsel: The event selector being printed (mutable for lazy 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, const struct perf_stat_config *config, stru= ct evsel *evsel, + int aggr_idx, u64 val, u64 ena, u64 run, double stdev_pct); + + /** + * print_metric - Called to buffer a metric value associated with an 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, const struct perf_stat_config *config, str= uct 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, const struct perf_stat_conf= ig *config, + 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, const struct perf_stat_config = *config, + const struct target *target, const struct timespec *ts, int argc, + const char **argv); + +/* + * Format-specific entry points, implemented in their respective files. + * All return 0 on success, or a negative error code on failure. + */ + +int perf_stat__print_std(struct evlist *evlist, const struct perf_stat_con= fig *config, + const struct target *target, const struct timespec *ts, int argc, + const char **argv); + +int perf_stat__print_csv(struct evlist *evlist, const struct perf_stat_con= fig *config, + const struct target *target, const struct timespec *ts, int argc, + const char **argv); + +int perf_stat__print_json(struct evlist *evlist, const struct perf_stat_co= nfig *config, + const struct target *target, const struct timespec *ts, int argc, + const char **argv); + +#endif /* __PERF_STAT_PRINT_H */ --=20 2.54.0.794.g4f17f83d09-goog From nobody Sun May 24 20:33:35 2026 Received: from mail-dy1-f201.google.com (mail-dy1-f201.google.com [74.125.82.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 D491438E8C9 for ; Fri, 22 May 2026 22:34:07 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=74.125.82.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1779489250; cv=none; b=lO0rN2kvOt0KAlkHySlzv7vvLqZH6VXLq0Bb6HGOkSxs6vlzcx1gnYI0RZQTVidpG7NlRvNQOtRrGmg3gD5TS5SbCvs1jcLx/aRFH9RY73DbKVigmNyh7jUj/zgAPZXA0oNBy3/i8YhnIpLrt+Ja8dxr/FJ32Y4TX5+PyN9+lS8= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1779489250; c=relaxed/simple; bh=7jEfVydiNd0BLHuEvAPUwiTsiqidCEHH7O04ZUgl3Dk=; h=Date:In-Reply-To:Mime-Version:References:Message-ID:Subject:From: To:Content-Type; b=HwlofYR+MvNK/W+fZD35Ky6f3a0YOsEJU83/ujqEgC1RKPuPH6Bb+z3cJl1MpelqFXQcIfF+TIO5Nr41nZ1gP7xMc+wQrPDvFXWihU9HWd4xK7b4CrQk5HJbm18sMosVeTNutYeRsBCG3ykwrROYMNqnQzec1cs3N+dumAVLj9o= 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=mSMnJ6Xb; arc=none smtp.client-ip=74.125.82.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="mSMnJ6Xb" Received: by mail-dy1-f201.google.com with SMTP id 5a478bee46e88-30457ff05b8so946303eec.0 for ; Fri, 22 May 2026 15:34:07 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=20251104; t=1779489247; x=1780094047; darn=vger.kernel.org; h=to:from:subject:message-id:references:mime-version:in-reply-to:date :from:to:cc:subject:date:message-id:reply-to; bh=dMIVgcABLtJN9wboli4jeVu+VRnrVXCSDVoPE8cpN5I=; b=mSMnJ6Xbv0olSAWpYtnHMD33QPKRTLoLOoWiT9XDiVZqOT+vllvHayN5VyyMcKvsaD l9oBqSIaofQX79h4C20cgj69XrXne+ozleP8BR/uy+VU+9GrSfNCNqnPvN6JSm8XKsrw FUPYdpVY6paKyUMS6oVRWOKJo08G2b2YBn020dDCv/QZ8XlQHJoWbq5tUQNzDTnvgnGO prdg0FEUwvoFWoMQNSRKHLShioz3Uwvi8SmN46qu3zOIHzHTRbf4+ArmcvksCFrQ0YHr 5V3mEj2wlgYle02pbmhKxN7dDr8ypR1xpqFP7sRb76f1BGUPEkQokOQw5J8F0CM20lb3 2QMg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1779489247; x=1780094047; h=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; bh=dMIVgcABLtJN9wboli4jeVu+VRnrVXCSDVoPE8cpN5I=; b=OYLuhzd6V1ZqgpTQ5pS7pF/bbRIPSuSSC1XqVWHVIbOcmN+35uE8pinVWycscV77dN EpWAwLAz2KUezJeUtmvL+gOjh4Z6zc51ewMWFt5KQI1D4hZ42DwEB8WijltQhLVsLd1B YjeER5MM7ZPjpojK8hSEccnW5WiClh2V+s+EqtBYNc/WYAAz2uJq/s+U5UiomW9me4Jl sTQu9KK+iq2swtw/mSiYLLz7EBlRGG2nBvkHSQWCEUhZPr+yl6INOBQKHJGtaZdjYiQr qN75zJ2GmNeX/TczEzvL/IjJb4Y5VbPGAofM5vNHgbKGBAI3K6c4T8eRAAqZvOk3BjBd oCYQ== X-Forwarded-Encrypted: i=1; AFNElJ+q2eW3S6ThU8iS6AuDMnLSQOaUHZMAGFeH71Zs1No6CHkWX4r89ivwtaCQGYpQHsmtMHcXNqiXWAoB7dk=@vger.kernel.org X-Gm-Message-State: AOJu0YzLw2ddnSIaHKutVh/JGnaIuJhxrwQhUqeKk78gbiIBFDFl+EZB WzDu2SfYY4rlVUUZgPLDcqjdTpQvZ1OnUNy2ibY9uK0IGg/0d9xIP4BGd70Relp9ouPJ/eU2oEE mOvElt3ZEkg== X-Received: from dlj15.prod.google.com ([2002:a05:7022:50f:b0:12c:34da:e06e]) (user=irogers job=prod-delivery.src-stubby-dispatcher) by 2002:a05:7022:6093:b0:12c:6e85:505c with SMTP id a92af1059eb24-1365f70f672mr2057630c88.4.1779489246785; Fri, 22 May 2026 15:34:06 -0700 (PDT) Date: Fri, 22 May 2026 15:33:30 -0700 In-Reply-To: <20260522223342.2393553-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: <20260522223342.2393553-1-irogers@google.com> X-Mailer: git-send-email 2.54.0.794.g4f17f83d09-goog Message-ID: <20260522223342.2393553-3-irogers@google.com> Subject: [RFC PATCH v1 02/14] perf stat: Implement standard console (STD) formatting callbacks From: Ian Rogers To: Peter Zijlstra , Ingo Molnar , Arnaldo Carvalho de Melo , Namhyung Kim , Jiri Olsa , Ian Rogers , Adrian Hunter , James Clark , linux-kernel@vger.kernel.org, linux-perf-users@vger.kernel.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_metr= ic` DOM nodes to buffer traversal streams, and fully encapsulates DOM state initialization and queue cleanups inside std_print_start() and std_print_en= d(). Utilizes the newly centralized unified aggregation helpers to resolve CPU a= nd thread prefixes cleanly, and incorporates full interval-mode timestamp printing support across all rows. Signed-off-by: Ian Rogers TAG=3Dagy CONV=3D7c33d6a0-70a2-454a-82f7-4de2101a5b9c --- tools/perf/util/stat-print-std.c | 740 ++++++++++++++++++++++++++++++- 1 file changed, 733 insertions(+), 7 deletions(-) diff --git a/tools/perf/util/stat-print-std.c b/tools/perf/util/stat-print-= std.c index 83987e97c889..0aa4e2669bba 100644 --- a/tools/perf/util/stat-print-std.c +++ b/tools/perf/util/stat-print-std.c @@ -1,13 +1,739 @@ /* SPDX-License-Identifier: GPL-2.0 */ -#include "stat-print.h" +#include +#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 "stat-print.h" +#include "stat.h" +#include "target.h" +#include "thread_map.h" +#include "tool_pmu.h" + +#define COUNTS_LEN 18 +#define EVNAME_LEN 32 +#define COMM_LEN 16 +#define PID_LEN 7 +#define MGROUP_LEN 50 +#define METRIC_LEN 38 + +static const int aggr_header_lens[] =3D { + [AGGR_CORE] =3D 18, [AGGR_CACHE] =3D 22, [AGGR_CLUSTER] =3D 20, + [AGGR_DIE] =3D 12, [AGGR_SOCKET] =3D 6, [AGGR_NODE] =3D 6, + [AGGR_NONE] =3D 6, [AGGR_THREAD] =3D 16, [AGGR_GLOBAL] =3D 0, +}; + +/** + * struct queued_metric - In-memory record of a buffered metric. + * @list: Linked list node for queueing. + * @name: The display name of the metric. + * @unit: The metric's unit (e.g., "%", "GHz", or NULL). + * @val: The calculated ratio/metric value. + * @thresh: Threshold classification for color coding. + * @aggr_idx: Aggregation index in evsel stats. + */ +struct queued_metric { + struct list_head list; + char *name; + char *unit; + double val; + enum metric_threshold_classify thresh; + int aggr_idx; +}; + +/** + * struct queued_event - In-memory record of a buffered counter event. + * @list: Linked list node for queueing. + * @evsel: The associated performance event selector. + * @name: The uniquely formatted/resolved event name. + * @val: Raw aggregated counter value. + * @ena: Enabled time for multiplexing percentage. + * @run: Running time for multiplexing percentage. + * @stdev_pct: Standard deviation percentage across repeated runs. + * @aggr_idx: Aggregation index. + * @is_metricgroup: Whether this represents a unified metricgroup header. + * @metrics_list: Linked list head containing nested queued_metric structu= res. + */ +struct queued_event { + struct list_head list; + struct evsel *evsel; + char *name; + u64 val, ena, run; + double stdev_pct; + int aggr_idx; + bool is_metricgroup; + struct list_head metrics_list; +}; + +/** + * struct std_print_state - Print state context for Standard console 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; +}; + +/** + * 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 ", 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 should_skip_zero_counter(const struct perf_stat_config *config= , struct evsel *counter, + int aggr_idx) +{ + struct perf_cpu cpu; + unsigned int idx; + struct aggr_cpu_id id; + + if (verbose =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 (!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((struct perf_stat_conf= ig *)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((struct perf_stat_config *)config, cpu); + + if (aggr_cpu_id__equal(&id, &own_id)) + return false; + } + return true; +} + +/* + * Standard (STD) Output Callbacks - Normal Mode + */ + +static int std_print_start(void *ctx, const struct perf_stat_config *confi= g __maybe_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, const struct perf_stat_config *confi= g, struct evsel *evsel, + int aggr_idx, u64 val, u64 ena, u64 run, double stdev_pct) +{ + 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 && should_skip_zero_counter(config, evsel, aggr_idx)) + 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; + } + + 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, const struct perf_stat_config *conf= ig __maybe_unused, + struct evsel *evsel __maybe_unused, int aggr_idx __maybe_unused, + const char *name, const char *unit, double val, + enum metric_threshold_classify thresh) +{ + struct std_print_state *ps =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; =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) + 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); + + fprintf(output, "\'"); + if (config->run_count > 1) + fprintf(output, " (%d runs)", config->run_count); + fprintf(output, ":\n\n"); +} + +static int std_print_end(void *ctx, const struct perf_stat_config *config) +{ + struct std_print_state *ps =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); + fprintf(out, "%*s", MGROUP_LEN + config->unit_width + 2 - n, ""); + } + } 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 */ + fprintf(out, "%-*s", EVNAME_LEN, evsel__name(evsel)); + + /* If there are no metrics, print noise and multiplexing percentage */ + if (list_empty(&ev->metrics_list)) { + if (ev->stdev_pct) + fprintf(out, " ( +-%6.2f%% )", ev->stdev_pct); + if (ev->run !=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]; + 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, + ""); + } 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); + } + 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, + const 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, + const struct perf_stat_config *config __maybe_unused, + struct evsel *evsel __maybe_unused, int aggr_idx, + const char *name, const char *unit, double val, + enum metric_threshold_classify thresh) { + struct std_metric_only_print_state *ps =3D ctx; + struct queued_metric *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; + 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, const struct perf_stat_con= fig *config) +{ + struct std_metric_only_print_state *ps =3D ctx; + struct queued_metric *b, *tmp; + FILE *out =3D ps->fp; + int first_aggr =3D -1; + int current_aggr =3D -1; + const char *color; + char *str; + int mlen; + int ret =3D 0; + int err; + + if (list_empty(&ps->queued_metrics)) + return 0; + + /* Print the formatted header prefix */ + if (!config->interval) + print_header_std(config, ps->target, ps->argc, ps->argv); + + first_aggr =3D list_first_entry(&ps->queued_metrics, struct queued_metric= , list)->aggr_idx; + + /* Print headers */ + list_for_each_entry(b, &ps->queued_metrics, list) { + if (b->aggr_idx =3D=3D first_aggr) { + char *header_name; + if (b->unit && b->unit[0]) { + err =3D asprintf(&header_name, "%s %s", b->unit, b->name); + } else { + header_name =3D strdup(b->name); + err =3D header_name ? 0 : -1; + } + if (err < 0) { + ret =3D -ENOMEM; + goto cleanup; + } + fprintf(out, "%*s ", config->metric_only_len, header_name); + free(header_name); + } + } + fprintf(out, "\n\n"); + + /* Print values */ + list_for_each_entry_safe(b, tmp, &ps->queued_metrics, list) { + if (b->aggr_idx !=3D current_aggr) { + if (current_aggr !=3D -1) + fprintf(out, "\n"); + current_aggr =3D b->aggr_idx; + if (config->interval && ps->timestamp[0]) + fprintf(out, "%s", ps->timestamp); + } + color =3D metric_threshold_classify__color(b->thresh); + mlen =3D config->metric_only_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 ", mlen, str); + free(str); + + list_del(&b->list); + free(b->name); + free(b->unit); + free(b); + } + print_footer_std(config); + return 0; + +cleanup: + list_for_each_entry_safe(b, tmp, &ps->queued_metrics, list) { + list_del(&b->list); + free(b->name); + free(b->unit); + free(b); + } + return ret; +} + +static const struct perf_stat_print_callbacks std_metric_only_print_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, const struct perf_stat_con= fig *config, + 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, + }; + 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 && 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.54.0.794.g4f17f83d09-goog From nobody Sun May 24 20:33:35 2026 Received: from mail-dl1-f74.google.com (mail-dl1-f74.google.com [74.125.82.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 C1AEC30CDB6 for ; Fri, 22 May 2026 22:34:09 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=74.125.82.74 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1779489251; cv=none; b=WH+M5P58nVxYqd49sHjj7QEPOg96xt9eq/DlCm4Is+rivZfXFsvFSc4zq6EDyA6UOG8UrlRbBTSGjXPWIII2YSRAIS0tDbDCxWlvmLsmGg0jd7s2cZQcUOuSOEsysRHL0fb4TwYofpArgkK6wY9/J3F55vcVIDpFR+aOImjOIeQ= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1779489251; c=relaxed/simple; bh=mMukkELF64vPLpVVMICsa3x2PclcZaqcw9gihFqiBKg=; h=Date:In-Reply-To:Mime-Version:References:Message-ID:Subject:From: To:Content-Type; b=W6Qi2F4wguvMuxxmWdl94aN8iNPVPTizK8a4aQ7fZqVlyTlxPbD5mVHhJNdlNGgYzQIiF/lCgaStvL2yHC0Mt7FHRSrGIWvsQEH52dNJ+6bvvdPFjHs004mTgOfx+z/pkH+HAh8MIvmvzECr18mpV3OI05ADs9zZyyhUPcajcb0= 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=G/SjEtVh; arc=none smtp.client-ip=74.125.82.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="G/SjEtVh" Received: by mail-dl1-f74.google.com with SMTP id a92af1059eb24-135405e4035so37669882c88.1 for ; Fri, 22 May 2026 15:34:09 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=20251104; t=1779489249; x=1780094049; darn=vger.kernel.org; h=to:from:subject:message-id:references:mime-version:in-reply-to:date :from:to:cc:subject:date:message-id:reply-to; bh=PbZge+S6GxcXFo7SrT3BdTOqtxic4O2pzIUUOXH6H6k=; b=G/SjEtVhG3+WIZs9HsOGURl9Jz7AlpmtIJiY/kgqlLDHtiuAsb3M6tbwMP7Ajls1RW 7AmwGl80yka072XN+JCK0y7dDohRArmUrJpWowEHsVWNQrUqLaYFVA0UWUxJe+Snfpvr X8dUcfVlDBFgTLWjm0wHqy3XXDhTa8r94QB4n4Sgk7tLoxcIyTbr9VG01lCg8IcQ12Rw IeNGngeGYbBjsAFdGWOHXq2dWGJC6/fayaYxRM4BZDPdBII1DUaG9HN3+GwGlwTFiSMe RP9mJQbmXrzTs7JKTcCKTs09r+kdwc1g1qqExcxEa3v9UWiiqzDoqggWqxFUzGBR6zIV NFfA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1779489249; x=1780094049; h=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; bh=PbZge+S6GxcXFo7SrT3BdTOqtxic4O2pzIUUOXH6H6k=; b=D6Y9bSFkWrKZjAbnkgsZb8AsLRGov+bNWq8f4crpq79uB1G7EcEYzylD873KiJmhry yhuAyn8N5f3letm1dfMHX2VqUzcttOHKxWIHNya1uqwTuR1CXydDaGMHl/0NKveayHi/ 7AUKtYQSy8CY7KFz3FPjybDX93ip4H58CXV/MCyKFdl5JcWfBi1j04WEL7u2faH88n/8 DKAaCyoh5TKOsoifPR9NneNa3THrUyEW599RO2hBm0h4nOySYwlxTXT8otTw6LFEkQtI hbNN6BoUWpn1sIzqW8CW1/GW4VaAqLLgnrkQSfG/dt96h/yBOSgNUaHGhykIQ/WWlBFW V3zA== X-Forwarded-Encrypted: i=1; AFNElJ9x1pTtAhKF9n67wRGMmtcMUQCBG8/LOU5oCabM9f+2BMdOXnD4t6DcWeD9UI25UmefOs9/W0Cs1PU2GK0=@vger.kernel.org X-Gm-Message-State: AOJu0Yxonx5VO/2grP0a8/zSTxpc2ky+0gzVEA/qOT7j00kJVsuKNVsb 1Y8yct4jvK+RvNNJQHRn60Nlo3WFkBjVrEEKiO5alYgqZHh8Yj6QbfvphOqK4DA3OnWQ4kMUq5k dVKBGx1i+og== X-Received: from dlb38.prod.google.com ([2002:a05:7022:626:b0:134:a774:d28e]) (user=irogers job=prod-delivery.src-stubby-dispatcher) by 2002:a05:7022:6727:b0:134:a701:de8 with SMTP id a92af1059eb24-1365f810954mr2223740c88.5.1779489248683; Fri, 22 May 2026 15:34:08 -0700 (PDT) Date: Fri, 22 May 2026 15:33:31 -0700 In-Reply-To: <20260522223342.2393553-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: <20260522223342.2393553-1-irogers@google.com> X-Mailer: git-send-email 2.54.0.794.g4f17f83d09-goog Message-ID: <20260522223342.2393553-4-irogers@google.com> Subject: [RFC PATCH v1 03/14] perf stat: Extend STD output linter to test basic New API checks From: Ian Rogers To: Peter Zijlstra , Ingo Molnar , Arnaldo Carvalho de Melo , Namhyung Kim , Jiri Olsa , Ian Rogers , Adrian Hunter , James Clark , linux-kernel@vger.kernel.org, linux-perf-users@vger.kernel.org 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 TAG=3Dagy CONV=3D7c33d6a0-70a2-454a-82f7-4de2101a5b9c --- 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.54.0.794.g4f17f83d09-goog From nobody Sun May 24 20:33:35 2026 Received: from mail-dl1-f73.google.com (mail-dl1-f73.google.com [74.125.82.73]) (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 C452039099B for ; Fri, 22 May 2026 22:34:11 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=74.125.82.73 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1779489253; cv=none; b=LwQ/sp2pNzjtEZemHCXU1+qAwzck36h/8uEwSWhQ+cIHuMrxziu4KKtlkMZ+kpmHrdt+3ixbE9G0PCVwwM/NkHLDkjiaz48yGUMEqnxXeiEDJlGChqn6q9ElHkPpWUNdE8a8oIyztGUdWz3dEP/eNOx20lmw2BY7lmIsn5qyzeY= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1779489253; c=relaxed/simple; bh=QHZVshflFmrjjhMtKUhjVH9MQLmvIRgweRvpXHjw4uY=; h=Date:In-Reply-To:Mime-Version:References:Message-ID:Subject:From: To:Content-Type; b=hvzFQPNwxsKfUkbdlVJZcvC/x8G9yei1aXZINA5wLLqnVuWaOZqeOK1ONUTZlK60ZTPpWUpnNZmtH0D5Ibuvg3V6lIq1VR+NcLVULTYSiIL5VDnYta8tXleNsKoY6vo9J+Up+M8mS73I/QArMUiAdV2u4XhQ+8WShJH0P+WJ52E= 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=Qghj5dG3; arc=none smtp.client-ip=74.125.82.73 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="Qghj5dG3" Received: by mail-dl1-f73.google.com with SMTP id a92af1059eb24-12dece274b1so10436887c88.1 for ; Fri, 22 May 2026 15:34:11 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=20251104; t=1779489251; x=1780094051; darn=vger.kernel.org; h=to:from:subject:message-id:references:mime-version:in-reply-to:date :from:to:cc:subject:date:message-id:reply-to; bh=DqzxQW2AbIPTH9tTCQP9t1PgzS1BV2Rl09I1wSbSwmk=; b=Qghj5dG3sJaXWlK4MXcc1NiTax+P+y2OZ7L82bSb9SI7ZcCfkVWwo3e6jQRpgl9cs6 jwNSaJebqrZimuoqCr/ATO4WWpwxT/HDg60v38n+puPMFO1bFtMxOHFu1I1+vM61dYUO P/KmWJTbpgY1+nULwkRs/nVHFl87VBY//4BkedfMObxA619q122Y3vqnIMq/VdEYfcYD WwjxMutEd6LBXLalkcthHm+i3nDi0ntn9KhvNx/WAzeMooBP3z/V0wT6kjpwCj+tGvP0 FUDct3Gj6B9AG1M5rhqaMDFH4lBndVp9YaWivgug9UBinsfPElIeohuV3LRcKAfWl2RO 9ZGw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1779489251; x=1780094051; h=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; bh=DqzxQW2AbIPTH9tTCQP9t1PgzS1BV2Rl09I1wSbSwmk=; b=OQDq6K9qr1b6Y6eASu4g3pBhCafldhRiRHt4gR/JNJdnAow7GaejBNzvIoTbwKMS/Y e8IUD2jSVOG7qOK+7Jup2rv8sT0Tf7Dn/FHAf8HrT7gsGvUgCaIlafIZVxCvM3wQ5Nwb saeR3yAuBcWE7vr1v0+abpqMWTnpAuciOQXKyybLuy0QKBOp09eUUgAqtTSY/Z3PRdkh y4T2Xd+2TGv2LwoPMsI3AU/LGxD3AthUb6kBUhIzPRl9XORZk/0EjUTeVDIN119+PXBL Y28xECTltHEoElAUFUNmVyBeBbs7M1eAWK5pydmwgOG9KzfMH7mR4UV8f4yJt6QwYAs3 dbqA== X-Forwarded-Encrypted: i=1; AFNElJ91Ffv+fPrtSqUcRbNOXODAWVkPAphnvu/dcB/olQyRUg4XfaHE+7ttbEOFqaxXUapjLmo0tWyM/xQsGlA=@vger.kernel.org X-Gm-Message-State: AOJu0YxIrphU3a56uVRBndsfU/fG5KnVEal/bGsvaUxL8IsTnjj6C6/4 hzLK1uCfpVZmHUZ/FRaLxTpmUbIYaLo9MMWB6SRN7C+ciFHevJETq/gq+3j4yEg7UwkeZAmPsoi sf+jpmJTlKg== X-Received: from dlb3.prod.google.com ([2002:a05:7022:603:b0:135:e6d6:d14d]) (user=irogers job=prod-delivery.src-stubby-dispatcher) by 2002:a05:7022:258a:b0:136:4317:a163 with SMTP id a92af1059eb24-1365fa35574mr2034112c88.25.1779489250537; Fri, 22 May 2026 15:34:10 -0700 (PDT) Date: Fri, 22 May 2026 15:33:32 -0700 In-Reply-To: <20260522223342.2393553-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: <20260522223342.2393553-1-irogers@google.com> X-Mailer: git-send-email 2.54.0.794.g4f17f83d09-goog Message-ID: <20260522223342.2393553-5-irogers@google.com> Subject: [RFC PATCH v1 04/14] perf stat: Extend STD output linter to test core aggregation checks From: Ian Rogers To: Peter Zijlstra , Ingo Molnar , Arnaldo Carvalho de Melo , Namhyung Kim , Jiri Olsa , Ian Rogers , Adrian Hunter , James Clark , linux-kernel@vger.kernel.org, linux-perf-users@vger.kernel.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 TAG=3Dagy CONV=3D7c33d6a0-70a2-454a-82f7-4de2101a5b9c --- 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.54.0.794.g4f17f83d09-goog From nobody Sun May 24 20:33:35 2026 Received: from mail-dl1-f74.google.com (mail-dl1-f74.google.com [74.125.82.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 9E15E39099B for ; Fri, 22 May 2026 22:34:16 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=74.125.82.74 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1779489258; cv=none; b=HgsWlbRK9Ikoy8FDZ7SjAW3mcg2B6LHMhjanZgXNpjsbQf3MWtmrzn/LuYam1UimACGOkcW7ijaP8ba2KGFK1LtfXUdvaOtFzodt/gE8Rdue1SGdAPYTFDLk6vbSDSfwPIibkdI/H5zjbAidGhKlNHgneKaZRH1l9RCKvN0y+bA= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1779489258; c=relaxed/simple; bh=+GDPFK96LQIDUsVeVvdUUn4Jcb/d6dmTMg3sFaT7dEg=; h=Date:In-Reply-To:Mime-Version:References:Message-ID:Subject:From: To:Content-Type; b=PQubwKZepNQau9ZUVUHBTBAw9W4Ewi2Wz84L2mJQE1T+REJrNangdaO1RjvFSK8XkNe4Z3ZMgoNpMovLAgahtFQ/0vQU84k5qvCyB8+RexDJBNXJOLFTAjAIUIU37wNSyrH0Fucd95ZIL6kEPQcwKitlwesneEv11W8iPMD6FjE= 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=bD96NsS3; arc=none smtp.client-ip=74.125.82.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="bD96NsS3" Received: by mail-dl1-f74.google.com with SMTP id a92af1059eb24-135405e4035so37670206c88.1 for ; Fri, 22 May 2026 15:34:16 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=20251104; t=1779489254; x=1780094054; darn=vger.kernel.org; h=to:from:subject:message-id:references:mime-version:in-reply-to:date :from:to:cc:subject:date:message-id:reply-to; bh=sblp1Q6dJebg6sx9UNRnZOmxsbwknght1WKM1+Jo3Xg=; b=bD96NsS3wIzIsgZIY9uGUhcI9627pCDfvsD1G9hU/p5MSdWYoA8j5182HPjWV1SwDR Qx3DPb/K9ouWaT2YNjFlOf8OBIjdq+yc9QdumW4UQydtkc0hnoLTx1agXycwGoEWMSl+ iF0tDHv8ZW3rYfNZlDCL6Me2vuMBSzhVqu7CmT0lRUonkHBBZ2FN0j/kRXd5kTpsRUDK 75Ic56jx1CfVl17NhPwwH/jpB1qymOW1eP6StCjD37YHJKgyYqnAXvejj5o9uzg89D3E Ch4rzkr4Ga1TO6gnLBDPoWCdIWk53UFHO0W2Z3L8loT5cn9jtu5/GVgfO/nfetKp3UIn SD/A== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1779489254; x=1780094054; h=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; bh=sblp1Q6dJebg6sx9UNRnZOmxsbwknght1WKM1+Jo3Xg=; b=kGgC+c/QAcWDPNNuXyp1+OkREaxnsvrLgQmExYVD1pi4PX/KYP+IwIDKYVJ8WiOvst FjP67kR0O/rKw02R9RPXRcyHvFC9KvcP1wsFk+wvIi3CMcdhhXOjR64lL2cY5uzuhZ4V FuNwbF9zDdY0Wi8n+CxsO4udx/She2gu5gafS5fqh6Gr7+6RvS0O/+xuEI3K59F8vIRX KK8cjH5tJX/QNUFsqTj3EeC/Sh2yXwSxI0SqKIz0PyBHFbTWz1Sf7iCXQmCRAhgIyy6R fRf8jqS+mbWP1neCehajjUxOR2T7AYkWJdqKs6PaOozrF3sRgLU2npissdKr0GPNz6Wi VS7A== X-Forwarded-Encrypted: i=1; AFNElJ85r3QgdenT5dIbMaxnqh/QZt5WgCtE0hmZbGayPTARkrvdw+VlakG1IAXZjeQN5rIeB7cmmqvB1t4J3kk=@vger.kernel.org X-Gm-Message-State: AOJu0YzPfI5leGrdjorfconsD8yiiywZfCCh6MqW9EPizgs4kv85YajJ NlHNSYI557MY0vIp3/7FkJb5MqWAmNmQRXciTkv9zGgzvu1QADJQKuaJu9P43fWIeeZqmUMDSXC g5A4NCPpDlw== X-Received: from dlbph6.prod.google.com ([2002:a05:7022:3706:b0:133:5da7:fa13]) (user=irogers job=prod-delivery.src-stubby-dispatcher) by 2002:a05:7022:46:b0:136:8957:a66f with SMTP id a92af1059eb24-1368957ac01mr700448c88.25.1779489253644; Fri, 22 May 2026 15:34:13 -0700 (PDT) Date: Fri, 22 May 2026 15:33:33 -0700 In-Reply-To: <20260522223342.2393553-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: <20260522223342.2393553-1-irogers@google.com> X-Mailer: git-send-email 2.54.0.794.g4f17f83d09-goog Message-ID: <20260522223342.2393553-6-irogers@google.com> Subject: [RFC PATCH v1 05/14] perf stat: Extend STD output linter to test advanced PMU checks From: Ian Rogers To: Peter Zijlstra , Ingo Molnar , Arnaldo Carvalho de Melo , Namhyung Kim , Jiri Olsa , Ian Rogers , Adrian Hunter , James Clark , linux-kernel@vger.kernel.org, linux-perf-users@vger.kernel.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 TAG=3Dagy CONV=3D7c33d6a0-70a2-454a-82f7-4de2101a5b9c --- tools/perf/tests/shell/stat+std_output.sh | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tools/perf/tests/shell/stat+std_output.sh b/tools/perf/tests/s= hell/stat+std_output.sh index 8dee005a7281..e4e8e4238ae0 100755 --- a/tools/perf/tests/shell/stat+std_output.sh +++ b/tools/perf/tests/shell/stat+std_output.sh @@ -132,6 +132,11 @@ then check_per_die "STD (New API)" "$perf_cmd" check_per_socket "STD (New API)" "$perf_cmd" fi +if [ $skip_test -ne 1 ] +then + check_per_cache_instance "STD (New API)" "$perf_cmd" + check_per_cluster "STD (New API)" "$perf_cmd" +fi =20 cleanup exit 0 --=20 2.54.0.794.g4f17f83d09-goog From nobody Sun May 24 20:33:35 2026 Received: from mail-dl1-f73.google.com (mail-dl1-f73.google.com [74.125.82.73]) (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 9E0CD37E2EE for ; Fri, 22 May 2026 22:34:16 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=74.125.82.73 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1779489257; cv=none; b=t2sqMslGSDaQu1h/I3Z1zUBgiaGrfKXlpRLb2UjtPI6BqBTkfBxvfLPVILzbiea9MXeOK8FXsQ/bDS/9e6ZOqg6nuJAKC8MGm00dUg1IweSanfuSu6LwkJ/LrYobKxGSJDsrxVkxrbJ/O65xfqnt7zaWQs1HBsxeBobC3GwAcrU= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1779489257; c=relaxed/simple; bh=BI4TEobKkCnWm6fjmCNLDXLWLzD9tRE7a0S9TIuMpIY=; h=Date:In-Reply-To:Mime-Version:References:Message-ID:Subject:From: To:Content-Type; b=QAAAZqtlb8Y6DHabXHgUceREbBzd9vHy65EanA/oUD6LdST2mJWtvKudMwYEJPS8cFi0OwDesO3T726VWXa/u5tUcPgcK6coZeMtKPoFDCr/zGuEwEsJTjhzeU5swG4VJnVQafuzlhZxUFqZ/qzUZaf0M4BWedt7fQWNsz4ZX3I= 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=IMtjuhXu; arc=none smtp.client-ip=74.125.82.73 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="IMtjuhXu" Received: by mail-dl1-f73.google.com with SMTP id a92af1059eb24-135de949041so6534831c88.0 for ; Fri, 22 May 2026 15:34:16 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=20251104; t=1779489256; x=1780094056; darn=vger.kernel.org; h=to:from:subject:message-id:references:mime-version:in-reply-to:date :from:to:cc:subject:date:message-id:reply-to; bh=FbuxZ66k165gjxfcqkOHiRnswir15qirhXLxfhhwE3Q=; b=IMtjuhXus0EGHkY2YWcbrK2oGyFNpquF9u+9fqHZANa7aodFFIg86MZunmoHAdCZJi qInPZCDESCh3dDycIgKcm3w4JypWnkhuqwTdqfLD70TfE2zb+Gj/BL6JvDTojBQjAu9n KrUb5ujRGU1v/dDXv3Xi1xT2iUDXX7bI525/ACfBiYj0Bzq91Y+D2HtyRd91rMTIVcDt yAWCjqy9BZt5D3pti+WRkRzoaK8gKVUTWvCyL+fkr+Vq4c0fAKsWuGIL6WdxZHSf6nIe Jyh2qk6h62zHNilnUOLGA+BTnoOZM68jQHNaMGD7VWwm81o2SZEl+aaDZsMEFcVVQ8Ug RETw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1779489256; x=1780094056; h=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; bh=FbuxZ66k165gjxfcqkOHiRnswir15qirhXLxfhhwE3Q=; b=kzdZmWsIp1pqJqr2TPIaPx+5PzVY6NALueqMKQs0m15L9qgRQEwbYxLtOept7edt3R 7EMaL7OVQNG2Ga+wWKqyOY+wIJwsPSrv/Uo/84C5Gv6NLgMbRcBzpcRD3njhuBUfAquL o6AuoGZK0f0bdFrHvxQ6swZPeLFowQ4nMc7XTyo7zvJJjthbgRZWMFENeUk0PA2t+6Ub mOnpoGAXh2UL3EbGufn9Dtfvi94JPp84GDkucoN+BsNFpaEvO/+8n7ELlXu8Dprm+qUC j7gdhuKHbXXbEoUJlO45UBLhh3EMBzsN2AZ5RcNCJT7L5VwD1Ov0vp4p3ENErPCWGr/F uk+A== X-Forwarded-Encrypted: i=1; AFNElJ9284JZ3wsBlbfagmsmVm351+YUYr3Xu5vmhjgQTyofFA+O7KpO0vk1o4L1xLiPwBojb+NpfFTKk09+obE=@vger.kernel.org X-Gm-Message-State: AOJu0Yzes7NVD33meM/aHEGCwyK9QKG2uDh58VsY/iuPZRWx0nu8IX1u fApdUidNXfV5hMu2c/i7sQOa3Medj9tNv2rJEK/ESZo46/qmbEnbXUMwbVCTuTJts9Jq2dXN5oZ H3446aqxt/w== X-Received: from dlbsn12.prod.google.com ([2002:a05:7022:b90c:b0:135:cac1:fe63]) (user=irogers job=prod-delivery.src-stubby-dispatcher) by 2002:a05:7022:6197:b0:128:d375:f1d4 with SMTP id a92af1059eb24-1365fb403admr2032094c88.28.1779489255446; Fri, 22 May 2026 15:34:15 -0700 (PDT) Date: Fri, 22 May 2026 15:33:34 -0700 In-Reply-To: <20260522223342.2393553-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: <20260522223342.2393553-1-irogers@google.com> X-Mailer: git-send-email 2.54.0.794.g4f17f83d09-goog Message-ID: <20260522223342.2393553-7-irogers@google.com> Subject: [RFC PATCH v1 06/14] perf stat: Extend STD output linter to test metric-only checks From: Ian Rogers To: Peter Zijlstra , Ingo Molnar , Arnaldo Carvalho de Melo , Namhyung Kim , Jiri Olsa , Ian Rogers , Adrian Hunter , James Clark , linux-kernel@vger.kernel.org, linux-perf-users@vger.kernel.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 TAG=3Dagy CONV=3D7c33d6a0-70a2-454a-82f7-4de2101a5b9c --- 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 e4e8e4238ae0..b48f0ebe2921 100755 --- a/tools/perf/tests/shell/stat+std_output.sh +++ b/tools/perf/tests/shell/stat+std_output.sh @@ -137,6 +137,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.54.0.794.g4f17f83d09-goog From nobody Sun May 24 20:33:35 2026 Received: from mail-dy1-f201.google.com (mail-dy1-f201.google.com [74.125.82.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 608993914E2 for ; Fri, 22 May 2026 22:34:18 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=74.125.82.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1779489263; cv=none; b=BWByntJID7yDWGMJrjIpb77iDpXALrfzO8V6EPQ9hJnQWw87hU6v3rmu9dwXxsE0Dp7g676k5Ylbw6y2V2qdtu2dVTuG3lF7gt6/tHRGU9VsQa82lH2LfYvEZBSjBm5/TvKRnL3c9tC/lXmgLIauikIpWUnQqouz0CaZcA8iUKo= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1779489263; c=relaxed/simple; bh=vdHLjM2hRu5X2e/9xv78d8KZ4WyxhB+wg+OiIaudjgg=; h=Date:In-Reply-To:Mime-Version:References:Message-ID:Subject:From: To:Content-Type; b=NfFasUFUKlIu38wRphMP8QhRyYtnKZxmKhQ2UH3lI7ZKK/r3bB+LJXWlmwGROzVdGfaFDEDCt/fSBTTXFTxVi3rPJwN16nhxZdim5q/KJweCQDbH/da0Jsgh3DzugJq0nsO6f9wqglqKd+2yxX+zeCOX/BvBZKal1poDYBvrluM= 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=cRq4Qm/N; arc=none smtp.client-ip=74.125.82.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="cRq4Qm/N" Received: by mail-dy1-f201.google.com with SMTP id 5a478bee46e88-3041c65a30bso3500614eec.1 for ; Fri, 22 May 2026 15:34:18 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=20251104; t=1779489257; x=1780094057; darn=vger.kernel.org; h=to:from:subject:message-id:references:mime-version:in-reply-to:date :from:to:cc:subject:date:message-id:reply-to; bh=rkD+Tv38lkC2YQkzVf03ssA2wPVmHjSgIoy2cgK3co0=; b=cRq4Qm/NcsGbEWlbZIeMpWlgFAbhnU540iIOXn4y7MzyZNPmbWxOTGjPgmuM4xHzuR QhlNvoiALTZ2oNVW+30HIsy6HN3zGhMI57yOR+4qzRYZfSIylbqKb3PLcjAbC9SynS8e CNMu6SUFKYeep1AKPH4H2d1VmTFKpqYsxJORd/Cm8f2WI9elJAlQNlN2pk2h5eKvHKNN ZBP5X9ipTpFuNfiSKRnnhF5YQwPp4gTzY9gW+s4PcCEPMg83XEYyPBIOXaNMNnJQQdfp DLoxXN/EArdzdbVE7+oKxTJUjXPkmFt1fo/4AcOZeG1lt2cyDIUQglulYlSqkgur9LLt zEuQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1779489257; x=1780094057; h=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; bh=rkD+Tv38lkC2YQkzVf03ssA2wPVmHjSgIoy2cgK3co0=; b=HfCa+UzXbl9A/Q9IF/j8c8r8RYD0y8U4XJ9G3ijq+irtXcXhMvLzhxv/bRZQOJ0v7B U2t+Jy6LK/h9XlSJ+UO8Ycj3eQ0bJTaWUfwNOSU9pOM6NkYWSh5XON/Z6fB34PTqpbzz 8nbJDEJpt4IoBrfkF3QS4sO5Tza9KjdwvHzkZ55z44GiQWbMsqGe6bnKhkqMT+MOn+h/ i2l9tPRCzkckJFkZ0COq61QOt9+bRXaihwef6oJ+KN8MdbK9rtTgeo0clAoHn4GlwIcB 1DI/NFFm5rzMVQ/mwo8+uKPkG19sqPcg8PCbxNlwiy3emljBNs4W82foNH9d7Z7oosj3 Sw1g== X-Forwarded-Encrypted: i=1; AFNElJ87GLPvruugfTHgodpcynrOGmyyTJRYzkluqDGXQcfYMVsRBm3VH8XPuDUSA5HCR9Y6spnu/way8JfX8HI=@vger.kernel.org X-Gm-Message-State: AOJu0Yxr460VTjgtMJ6aX+P0CGg+IylfIdov5Gmf/yR1PY+MaqbFgBYf Iz3NcDeu5nShP3YAeBzp5Vq8A6dd1nOzQXctmxD1c6/GZPEUfjciI3RsA1oZur4e/G+t7/o1sAM uYCuY81b5fA== X-Received: from dycnr9-n1.prod.google.com ([2002:a05:7300:e9c9:10b0:2ee:2c3a:8aaf]) (user=irogers job=prod-delivery.src-stubby-dispatcher) by 2002:a05:7300:df0c:b0:2ea:5057:a331 with SMTP id 5a478bee46e88-3044918484emr2787603eec.21.1779489257289; Fri, 22 May 2026 15:34:17 -0700 (PDT) Date: Fri, 22 May 2026 15:33:35 -0700 In-Reply-To: <20260522223342.2393553-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: <20260522223342.2393553-1-irogers@google.com> X-Mailer: git-send-email 2.54.0.794.g4f17f83d09-goog Message-ID: <20260522223342.2393553-8-irogers@google.com> Subject: [RFC PATCH v1 07/14] perf stat: Implement CSV formatting callbacks From: Ian Rogers To: Peter Zijlstra , Ingo Molnar , Arnaldo Carvalho de Melo , Namhyung Kim , Jiri Olsa , Ian Rogers , Adrian Hunter , James Clark , linux-kernel@vger.kernel.org, linux-perf-users@vger.kernel.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 li= sts 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 TAG=3Dagy CONV=3D7c33d6a0-70a2-454a-82f7-4de2101a5b9c --- tools/perf/util/stat-print-csv.c | 530 ++++++++++++++++++++++++++++++- 1 file changed, 522 insertions(+), 8 deletions(-) diff --git a/tools/perf/util/stat-print-csv.c b/tools/perf/util/stat-print-= csv.c index e9d1e7c30c90..3bac8592152a 100644 --- a/tools/perf/util/stat-print-csv.c +++ b/tools/perf/util/stat-print-csv.c @@ -1,13 +1,527 @@ -/* SPDX-License-Identifier: GPL-2.0 */ -#include "stat-print.h" +// SPDX-License-Identifier: GPL-2.0 +#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; + 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"). + * @val: Raw aggregated counter value. + * @ena: Enabled time for multiplexing percentage. + * @run: Running time for multiplexing percentage. + * @scale: Event scale factor. + * @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; + u64 val, ena, run; + double scale; + 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: + fprintf(output, "%s-%d%s", + perf_thread_map__comm(evsel->core.threads, id.thread_idx), + perf_thread_map__pid(evsel->core.threads, id.thread_idx), 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, const struct perf_stat_config *confi= g __maybe_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, const struct perf_stat_config *confi= g __maybe_unused, + struct evsel *evsel, int aggr_idx, u64 val, u64 ena, u64 run, + double stdev_pct __maybe_unused) +{ + struct csv_print_state *ps =3D ctx; + struct queued_event *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; + } + + ev->evsel =3D evsel; + ev->val =3D val; + ev->ena =3D ena; + ev->run =3D run; + ev->scale =3D evsel->scale; + 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, const struct perf_stat_config *conf= ig __maybe_unused, + struct evsel *evsel __maybe_unused, int aggr_idx __maybe_unused, + const char *name, const char *unit, double val, + enum metric_threshold_classify thresh __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->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; + list_add_tail(&b->list, &ps->current_event->metrics_list); + + return 0; +} + +static int csv_print_end(void *ctx, const 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; =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) + print_aggr_id_csv(config, output, evsel, id, aggr_nr); + } + + 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); + } + + if (ev->unit) + fprintf(output, "%s%s", ev->unit, sep); + else + fprintf(output, "%s", sep); + + fprintf(output, "%s", ev->name); + + 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_percen= t); + + /* Print metrics */ + 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); + } + /* Subsequent metrics have exactly 4 padding separators */ + 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); + } + 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, + const 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, + const struct perf_stat_config *config __maybe_unused, + struct evsel *evsel __maybe_unused, int aggr_idx, + const char *name, const char *unit, double val, + enum metric_threshold_classify thresh __maybe_unused) +{ + struct csv_metric_only_print_state *ps =3D ctx; + struct queued_metric *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->aggr_idx =3D aggr_idx; + list_add_tail(&b->list, &ps->queued_metrics); + + return 0; +} + +static int csv_metric_only_print_end(void *ctx, const struct perf_stat_con= fig *config) { + 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; + int first_aggr =3D -1; + int current_aggr =3D -1; + int ret =3D 0; + int err; + + if (list_empty(&ps->queued_metrics)) + return 0; + + first_aggr =3D list_first_entry(&ps->queued_metrics, struct queued_metric= , list)->aggr_idx; + + /* Print interval timestamp header if configured */ + if (config->interval && ps->timestamp[0]) + fprintf(output, "%s", ps->timestamp); + + /* Print aggregation prefix header in CSV metric-only mode */ + if (config->aggr_map && first_aggr >=3D 0) { + struct aggr_cpu_id id =3D config->aggr_map->map[first_aggr]; + struct evsel *mock_evsel =3D + list_first_entry(&ps->evlist->core.entries, struct evsel, core.node); + int aggr_nr =3D 0; + + if (mock_evsel->stats && mock_evsel->stats->aggr) + aggr_nr =3D mock_evsel->stats->aggr[first_aggr].nr; + + print_aggr_id_csv(config, output, mock_evsel, id, aggr_nr); + } + + /* 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); + } else { + header_name =3D strdup(b->name); + err =3D header_name ? 0 : -1; + } + if (err < 0) { + ret =3D -ENOMEM; + goto cleanup; + } + fprintf(output, "%s%s", header_name, sep); + free(header_name); + } + } + fprintf(output, "\n"); + + /* Print values */ + list_for_each_entry_safe(b, tmp, &ps->queued_metrics, list) { + if (b->aggr_idx !=3D current_aggr) { + if (current_aggr !=3D -1) + fprintf(output, "\n"); + current_aggr =3D b->aggr_idx; + if (config->interval && ps->timestamp[0]) + fprintf(output, "%s", ps->timestamp); + if (config->aggr_map && current_aggr >=3D 0) { + struct aggr_cpu_id id =3D config->aggr_map->map[current_aggr]; + struct evsel *mock_evsel =3D list_first_entry( + &ps->evlist->core.entries, struct evsel, core.node); + int aggr_nr =3D 0; + + if (mock_evsel->stats && mock_evsel->stats->aggr) + aggr_nr =3D mock_evsel->stats->aggr[current_aggr].nr; + + print_aggr_id_csv(config, output, mock_evsel, id, aggr_nr); + } + } + fprintf(output, "%.1f%s", b->val, sep); + + list_del(&b->list); + free(b->name); + free(b->unit); + free(b); + } + fprintf(output, "\n"); 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 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, const struct perf_stat_con= fig *config, + 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.54.0.794.g4f17f83d09-goog From nobody Sun May 24 20:33:35 2026 Received: from mail-dl1-f73.google.com (mail-dl1-f73.google.com [74.125.82.73]) (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 35EBE2DAFDF for ; Fri, 22 May 2026 22:34:20 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=74.125.82.73 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1779489263; cv=none; b=MnMdIZkLK/slEGEL4IdXEzbkdgTje5TaBS990G6NFkQa3jsqDdgdBmFNQmcLdmc/V7LbRKFdV0JSxFwZAZ5CgB3hJ06SXKsi+nIbJB5eCaFFyMOT+0GaH7ZGEzIL3I9/yx4XD+bSZ6qXMoudRiMJl8Ky/q0xKve5DOxxQTIH7Jc= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1779489263; c=relaxed/simple; bh=piD1lSGIvhfh6IOI08spPTHARGMJ8GTeKxUslcwb7DQ=; h=Date:In-Reply-To:Mime-Version:References:Message-ID:Subject:From: To:Content-Type; b=XesVnRj0116MdhAMOzGnvNFEi9DTtozEZbonHIHOjFSWpRpoVODBwF0whxXY/gRuvRQoRC1+xeG8yeO8IlioUzMBGuyopbnQu6qfydXx4D9XYt7hPBYkdFRW1nlb1U+X5zuhcRUhDA1gv/5qhTw4RC3QpHDFeBdDUXWL3yk5ARE= 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=SUvIAcaM; arc=none smtp.client-ip=74.125.82.73 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="SUvIAcaM" Received: by mail-dl1-f73.google.com with SMTP id a92af1059eb24-12dece274b1so10436983c88.1 for ; Fri, 22 May 2026 15:34:20 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=20251104; t=1779489259; x=1780094059; darn=vger.kernel.org; h=to:from:subject:message-id:references:mime-version:in-reply-to:date :from:to:cc:subject:date:message-id:reply-to; bh=WwCtL+MLETGslqK6F91RVIGBg95O8W6fS1FbAA07JO4=; b=SUvIAcaMG8AgwYVlqiM3kbPBKg+Hm77VO3yGY+euOUIIwTC6LP3ALr408DAptrdZXq I5Omu2FPzUDVG5/pC9f10Zri0sY0Ih6l1eECjHRH7vJ0GiSHuoykkehgvZHJHF6FBhhx J/FuKjFBcSpnmJG28Pv7D1E81Js8neMD9wQZbd4FmsK91Dy+4DMSn/23zNPRGn2mJnAt lxADuONk2lkDQphLqi8yoNgKiSAMXvKORABag6uzktGPW7mmmQLrcKDc+R3ocqe4LYYk d6aMSUipZpFtZR/AIV4sef0/8W6csXICJXTugLScRwUt8H1gVWDnsbO5BZqeS0s8btQo INYg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1779489259; x=1780094059; h=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; bh=WwCtL+MLETGslqK6F91RVIGBg95O8W6fS1FbAA07JO4=; b=WAzV3RYFKxuu1RO1JSk/EYnWCPYnYY7RorC9HH25w/y8ZwFEsraUqEUW0NTENka+6c TnKmYw+1Uq/OV5ks4g2D0D0Ggd4TFEOuyZAP65ZYP7zF+Bi2NRZOCicHK144N7Hvvjmr U07zr0Sp1AFTRjeIfNDE0Omwai/tJORc989RLs29g1ssM4r37JEfJws7aOGnSHI7JZrL ZKLaw5OxaSEnMKNT/Ow77OnfrkkbyVvM89uUX/7SKOSQhayT5bvScagY016xGaTqc/Dy 3QEzMAqQkJrAy22SrEZtZ7lo7zBZo0vg7LHQ/nSQ3c87Oaf/SMDnu1xACw1g1LFYnG1m hJhA== X-Forwarded-Encrypted: i=1; AFNElJ9aiQHBt8q156A3iooWD5mnlPT500GzTcarldjrPaeVe0LnLOB0DSju89+h3ZMfhAYcaQpUtJFUTdkYU1w=@vger.kernel.org X-Gm-Message-State: AOJu0YxmvyzTV52wi5YMhbA89tkEx681V6ZaZrpGZmqtv7vW3dt5dAVC D5SOwl+MUhIZz5PR0PL9F6tiVYsuk7b8PSVKl+gPH46daQUXmtlOBa6m/zpjOeUCCU0BBde+Gzx 8j6yRpiuuVQ== X-Received: from dlbup7.prod.google.com ([2002:a05:7022:fd07:b0:135:bc52:ad87]) (user=irogers job=prod-delivery.src-stubby-dispatcher) by 2002:a05:7022:31a:b0:130:ca3d:f963 with SMTP id a92af1059eb24-1365fc637cemr1839341c88.41.1779489259176; Fri, 22 May 2026 15:34:19 -0700 (PDT) Date: Fri, 22 May 2026 15:33:36 -0700 In-Reply-To: <20260522223342.2393553-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: <20260522223342.2393553-1-irogers@google.com> X-Mailer: git-send-email 2.54.0.794.g4f17f83d09-goog Message-ID: <20260522223342.2393553-9-irogers@google.com> Subject: [RFC PATCH v1 08/14] perf stat: Extend CSV output linter to test core aggregation checks From: Ian Rogers To: Peter Zijlstra , Ingo Molnar , Arnaldo Carvalho de Melo , Namhyung Kim , Jiri Olsa , Ian Rogers , Adrian Hunter , James Clark , linux-kernel@vger.kernel.org, linux-perf-users@vger.kernel.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 TAG=3Dagy CONV=3D7c33d6a0-70a2-454a-82f7-4de2101a5b9c --- 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.54.0.794.g4f17f83d09-goog From nobody Sun May 24 20:33:35 2026 Received: from mail-dl1-f74.google.com (mail-dl1-f74.google.com [74.125.82.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 429163911A1 for ; Fri, 22 May 2026 22:34:22 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=74.125.82.74 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1779489265; cv=none; b=fFgTc1d1XZMt0Mxtl49Yo/vQ0/5mTyZXwRAQ8Z/MqdjKGl5H+HKb4QH9I2KOONsfWCWIVO977D3Pc74w0uTdZhENQCXA0lEO6hKAEvI8dCAE6LbGFTi6EB9ANYf8QhBaruywvF0hqaMPsNWwj3BfjePyu5RXNEyDThWIFcot/dY= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1779489265; c=relaxed/simple; bh=FEVyrHDBlet1P9BzFLFGACvY7k3jpDNi+mhxyn+8vK0=; h=Date:In-Reply-To:Mime-Version:References:Message-ID:Subject:From: To:Content-Type; b=DsC4UfUbZCz20XNQ5QOHRyvKzeqz9949Od2p3NSp9ydEnF7brc1Z51cWTzy9U8AWMAOryyjor8FLg9fDA5wP/Q3XZxugUSUfgQquJQRrXlmcBP7ZO2gTSgo66HU/Q5omFp4C7JUuSDfhataZu2ELa99O66mn8XC8OVuz6+6+wWQ= 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=cgDCqcDj; arc=none smtp.client-ip=74.125.82.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="cgDCqcDj" Received: by mail-dl1-f74.google.com with SMTP id a92af1059eb24-1329791f18fso13060739c88.1 for ; Fri, 22 May 2026 15:34:22 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=20251104; t=1779489261; x=1780094061; darn=vger.kernel.org; h=to:from:subject:message-id:references:mime-version:in-reply-to:date :from:to:cc:subject:date:message-id:reply-to; bh=xAFihfRLWRFUO8em9E3yweTug9bGII+pn0wa8seQfq4=; b=cgDCqcDj+XO4+KWTzvOX6sZ/2eJ4SzABteLElUxbWAGn1vjzMGEtfrU2s4C2rq2uIP chNqrUoayddIhW2XUEpDhwaGQOWBiCcnuAvm6muVACP8ubaH+35bW7AaQ2yBNOcpBYry bkCbzgA098iLPHCse7sbzRg1O6uVhzfjETz4E3z0lyDg3GfJ6wEHzlEc3xZCi3HMxwVf T6ACGga3ek2vSbtn7VBdM9Dr6U/nr74kM9Ti5LzeVHjJgnGOG4WDhbfkogKAUv3AkWU1 JHeHAhGfdvoOloslB3vt50S1UYO94GU498jK3/UlAdwY2usVSjg0LgZbUpRq9gX4C+XS NGIA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1779489261; x=1780094061; h=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; bh=xAFihfRLWRFUO8em9E3yweTug9bGII+pn0wa8seQfq4=; b=hw8CzGwqTGwZ8pQpMXLGKmUGU8rG7AGHkAudreo0EuodxEgOvk/0oUzH6/GdCB6BQd 1dsHaXGZGAFFDeFomsiji6pqwwqe8L6vMsGMPXQzDwj2+v7zV0IJWFltrxCpm4P9IJ6A 6B/KaY9llAgz8+t//gmK0XeOkKODpgT9i80elNU9kEJjpIThO8lZM3IJoQVWeLpKoQIA /rCqUQfXaUMOYFHpkDoy/cBgguJ192DzDXOTUzF3LdCfYZtCaQaBLcSkWsorNL67sI+P R4tGYVKLf3dK5CyngZL6kvPC4yDkd1BQQ4sz67rOKALjGnpm/JIwvwgGu/JHokRTOx3C +nxQ== X-Forwarded-Encrypted: i=1; AFNElJ/XuU3x+TRl6eZSZv5cSvNS0L6zWXATLDhRiQPuZOkrM3TNza+/l8GouS3JYG0M2kudS2f4i2269i7k/Xg=@vger.kernel.org X-Gm-Message-State: AOJu0YzuM5nFB3erePDqpdmDBtLF4FwrlBUk2BM9yfT9NNL1kfCYa+BP Ibu9rzKg+8BQ1AI2qvtIXqmf2XKAQe3Vu5t9+eKM1wy3rm9lHL/Ge+t0NKtgKJ4ZSDXXH8yYrHn K0Kf4m0gO0w== X-Received: from dlx18.prod.google.com ([2002:a05:7022:92:b0:135:637f:2609]) (user=irogers job=prod-delivery.src-stubby-dispatcher) by 2002:a05:7022:b86:b0:132:956b:3c6 with SMTP id a92af1059eb24-1365fc692damr1782939c88.33.1779489261156; Fri, 22 May 2026 15:34:21 -0700 (PDT) Date: Fri, 22 May 2026 15:33:37 -0700 In-Reply-To: <20260522223342.2393553-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: <20260522223342.2393553-1-irogers@google.com> X-Mailer: git-send-email 2.54.0.794.g4f17f83d09-goog Message-ID: <20260522223342.2393553-10-irogers@google.com> Subject: [RFC PATCH v1 09/14] perf stat: Extend CSV output linter to test advanced PMU and metric-only checks From: Ian Rogers To: Peter Zijlstra , Ingo Molnar , Arnaldo Carvalho de Melo , Namhyung Kim , Jiri Olsa , Ian Rogers , Adrian Hunter , James Clark , linux-kernel@vger.kernel.org, linux-perf-users@vger.kernel.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 callbac= ks are verified and column-valid under advanced aggregation modes and metric-o= nly JSON row-column layouts. Signed-off-by: Ian Rogers TAG=3Dagy CONV=3D7c33d6a0-70a2-454a-82f7-4de2101a5b9c --- tools/perf/tests/shell/stat+csv_output.sh | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tools/perf/tests/shell/stat+csv_output.sh b/tools/perf/tests/s= hell/stat+csv_output.sh index e4153a03d716..78e3c0bd8f82 100755 --- a/tools/perf/tests/shell/stat+csv_output.sh +++ b/tools/perf/tests/shell/stat+csv_output.sh @@ -103,6 +103,12 @@ then check_per_die "CSV (New API)" "$perf_cmd" check_per_socket "CSV (New API)" "$perf_cmd" fi +if [ $skip_test -ne 1 ] +then + 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.54.0.794.g4f17f83d09-goog From nobody Sun May 24 20:33:35 2026 Received: from mail-dy1-f202.google.com (mail-dy1-f202.google.com [74.125.82.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 6C1ED3911C3 for ; Fri, 22 May 2026 22:34:24 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=74.125.82.202 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1779489267; cv=none; b=KzOc0I7o7tTUTls46pdvsuLteqKuTqSYH96R98t3G/pftx0DBORJddY8HPQO24lLIfbUUdENZdvTuFWzk5kdhBX4cT1XcnLZgy7jPr0OGRzdSKsaduCzujKduSLJjzDZWF9Hokm0+PFJsZa2iiQ7UUKGObsre0KnKtK+hHPFANA= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1779489267; c=relaxed/simple; bh=9W2AuQy7TR2xWuUOlVR4VtUCzqNQ2HyXmGF5IoMv0p4=; h=Date:In-Reply-To:Mime-Version:References:Message-ID:Subject:From: To:Content-Type; b=aIBNQcqW4brhwo9XK5QJ8Iqi+k1EAlBbBick2t6F4Jkl5ctbEduBygDVS9xZT5Ck0pLRp9cOyFbS27/CJl9N6/YGUevARer2MKQirMO0N+++5AVZiIlC9cAsgc0SXHGvcrl4h31KALhYVFrccctuCDbLJ7KEADQd1lrejjbDAHA= 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=hesEtrSD; arc=none smtp.client-ip=74.125.82.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="hesEtrSD" Received: by mail-dy1-f202.google.com with SMTP id 5a478bee46e88-2f2d983d109so4384523eec.0 for ; Fri, 22 May 2026 15:34:24 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=20251104; t=1779489263; x=1780094063; darn=vger.kernel.org; h=to:from:subject:message-id:references:mime-version:in-reply-to:date :from:to:cc:subject:date:message-id:reply-to; bh=7fKGuOZoyLbgVPD1fllZNkCVmfe6juErH4XnZWzewrE=; b=hesEtrSDPy3mGDdEc7m+LdErf4PW2dFbQD1ecPONXwLD7sjtWRq8kxlrRyOO4Xe30p lhYpyULb7DnBUtKMRuM4Yw6IJRWm3v/HHWAEn3IJuWQkqSHpV1RLe25q/lf64kAV01tQ mP4CnBkDVLdkgZJU1+E6Dg3QpPf4KzxqWH/ytI9+g2Jts+MRgsmoc7RxEvv6iFqDSLAA d5H56P6Viaxdq4Er6vToYdVYiYjd7K9FEibcxMlb40tBrDwnttWYoSd1yMf/MUgLusK1 OzprtPQEmg8Eft3AnT6CFjhiUShLBULO8F2CD5VNEyQSYaSME/idVcP+QQDBw+VqCDsL BBLQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1779489263; x=1780094063; h=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; bh=7fKGuOZoyLbgVPD1fllZNkCVmfe6juErH4XnZWzewrE=; b=g62/8ejY1aUK8WoBWUnd7XZtq9ba3m7yYUAq6VgzLs0Z3PC7Unqktj6O/CykeQMGEc e9w+NNljCT7XukYQ82ZbTkXF5YV9sjVh/HlrazKrXp5S2sSdOGrqwGL+Tr1+v7wcJHhN AF6eGCy6/xQ3QH2gEN7ZGtn/zOqVgmJUz4YyE7kXI4ek/tnO06+b7J9knHcyhLGo5OBD iHGi+ImZFtKYz0A3kNfyPobK1hN0fiHmcnzZhWZzhfnH38kANLMTWVSxrNBD0i9qAhZs s5tPFK9ZUGC0ZLZQ85IGfL5FoIvuDR2+26Ijk1782dBhO+YumD03kuC8yuj0B/SCEV4M p7Jg== X-Forwarded-Encrypted: i=1; AFNElJ/WtB2QiKatG1QY3iONsDiXSIHvEgzaWSihRf/iwGAIq9fNy1QPn1HILsYTEj8guf7OmFeA8kqOtNc+tb4=@vger.kernel.org X-Gm-Message-State: AOJu0YzHGjUPyIawhDKfJDGCQG92YNjKS5GXPCkADTKaCmejxxMgHkqe nHIYS6lWDL9qetpR+Th+3MuYHYUE2cjKAC5QhYfeqWxiDN1aGdcZSvaRpqFi7A8PUtMjQZNFmjQ L/j17jRnNrw== X-Received: from dyeo18.prod.google.com ([2002:a05:7300:5352:b0:2fd:3927:94f]) (user=irogers job=prod-delivery.src-stubby-dispatcher) by 2002:a05:7301:2288:b0:304:18be:55a5 with SMTP id 5a478bee46e88-3044914f732mr2774520eec.22.1779489263164; Fri, 22 May 2026 15:34:23 -0700 (PDT) Date: Fri, 22 May 2026 15:33:38 -0700 In-Reply-To: <20260522223342.2393553-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: <20260522223342.2393553-1-irogers@google.com> X-Mailer: git-send-email 2.54.0.794.g4f17f83d09-goog Message-ID: <20260522223342.2393553-11-irogers@google.com> Subject: [RFC PATCH v1 10/14] perf stat: Implement streaming JSON formatting callbacks From: Ian Rogers To: Peter Zijlstra , Ingo Molnar , Arnaldo Carvalho de Melo , Namhyung Kim , Jiri Olsa , Ian Rogers , Adrian Hunter , James Clark , linux-kernel@vger.kernel.org, linux-perf-users@vger.kernel.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 engi= ne 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 TAG=3Dagy CONV=3D7c33d6a0-70a2-454a-82f7-4de2101a5b9c --- tools/perf/util/stat-print-json.c | 341 +++++++++++++++++++++++++++++- 1 file changed, 333 insertions(+), 8 deletions(-) diff --git a/tools/perf/util/stat-print-json.c b/tools/perf/util/stat-print= -json.c index 72df7a94095d..da33b241c961 100644 --- a/tools/perf/util/stat-print-json.c +++ b/tools/perf/util/stat-print-json.c @@ -1,13 +1,338 @@ -/* SPDX-License-Identifier: GPL-2.0 */ -#include "stat-print.h" +// SPDX-License-Identifier: GPL-2.0 +#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; +}; =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) +/** + * 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, + const struct perf_stat_config *config __maybe_unused) +{ + return 0; +} + +static int json_print_event(void *ctx, const struct perf_stat_config *conf= ig, struct evsel *evsel, + int aggr_idx, u64 val, u64 ena, u64 run, + double stdev_pct __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, const struct perf_stat_config *con= fig, struct evsel *evsel, + int aggr_idx, const char *name, const char *unit __maybe_unused, + 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) { + run =3D ps_evsel->aggr[aggr_idx].counts.run; + ena =3D ps_evsel->aggr[aggr_idx].counts.ena; + } + + 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 (name && name[0]) + fprintf(output, ", \"metric-unit\" : \"%s\"", name); + 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, + const 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, + const struct perf_stat_config *config __maybe_unused) +{ + struct json_metric_only_print_state *ps =3D ctx; + + ps->last_aggr_idx =3D -1; + ps->first_in_group =3D true; + return 0; +} + +static int json_metric_only_print_metric(void *ctx, + const struct perf_stat_config *config __maybe_unused, + struct evsel *evsel __maybe_unused, int aggr_idx, + const char *name, const char *unit, double val, + enum metric_threshold_classify thresh __maybe_unused) +{ + struct json_metric_only_print_state *ps =3D ctx; + FILE *output =3D ps->fp; + char *header_name; + int err; + + + if (aggr_idx !=3D ps->last_aggr_idx) { + if (ps->last_aggr_idx !=3D -1) + fprintf(output, "}\n"); + fprintf(output, "{"); + if (config->interval && ps->timestamp[0]) + fprintf(output, "%s", ps->timestamp); + if (config->aggr_map && aggr_idx >=3D 0) { + struct aggr_cpu_id id =3D config->aggr_map->map[aggr_idx]; + struct evsel *mock_evsel =3D list_first_entry(&ps->evlist->core.entries, + struct evsel, core.node); + int aggr_nr =3D 0; + + if (mock_evsel->stats && mock_evsel->stats->aggr) + aggr_nr =3D mock_evsel->stats->aggr[aggr_idx].nr; + + print_aggr_id_json(config, output, mock_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]) { + err =3D asprintf(&header_name, "%s %s", unit, name); + } else { + header_name =3D strdup(name); + err =3D header_name ? 0 : -1; + } + if (err < 0) + return -ENOMEM; + + fprintf(output, "\"%s\" : \"%.1f\"", header_name, val); + free(header_name); + return 0; +} + +static int json_metric_only_print_end(void *ctx, + const 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 -1) + 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, const struct perf_stat_co= nfig *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.54.0.794.g4f17f83d09-goog From nobody Sun May 24 20:33:35 2026 Received: from mail-dy1-f202.google.com (mail-dy1-f202.google.com [74.125.82.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 DAAA238E8C9 for ; Fri, 22 May 2026 22:34:26 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=74.125.82.202 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1779489272; cv=none; b=Fb25WVNSECOiGMaFlE8Vy4gRZKizWm0BNtcsbY6cz3huh3bHq3JeT1Ah6bGukK1nr3oddx1fw8W4yx2GiAAp1TIwhB5JrDKOYPsHBtusEiUxyOl0RjlXzRT6+XotDforC3CsrHqaxPC9C3nbYe84JmRiycbnE1bY19xatXzQGHw= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1779489272; c=relaxed/simple; bh=QFMynWcJjkS5MnPj61sKETHhVCs6vtQC2XYtUOE/oe8=; h=Date:In-Reply-To:Mime-Version:References:Message-ID:Subject:From: To:Content-Type; b=WAwg8vsqx/k4dQx7oQXHjCgIFwZMir21/eOnqEy/jzy7vkMC/yzzplckfIRA971jd9KMNNNdkekmT/G0xFKaj0b8ea+M1WOJlARJ5I4AdRpsYcLrouqKgGKuC7ePhQxi0axJ0s73HZctBpIfZJJq9/tpTtxISkqwoqiUU1aSXAg= 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=miE5ZThn; arc=none smtp.client-ip=74.125.82.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="miE5ZThn" Received: by mail-dy1-f202.google.com with SMTP id 5a478bee46e88-304627c66ddso741289eec.0 for ; Fri, 22 May 2026 15:34:26 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=20251104; t=1779489266; x=1780094066; darn=vger.kernel.org; h=to:from:subject:message-id:references:mime-version:in-reply-to:date :from:to:cc:subject:date:message-id:reply-to; bh=QhHSiOmkLS8o0pebuQEPSAqgSPXeTQRZXi8kWVXxgY0=; b=miE5ZThn2r7YdzDorYfp47YNxTTwcUOc3Rb72DtOVZN/VSDpj2x97oLjjt0kQ53nEa cteDRqMB6g9w+WQ3VxJgFoB4kid76tIdYz8NLFzT0H+KCbRksRcXla166Up5Q8F8Tn23 2IafirabzdAdZ3ZPlD1uw/zLO6HQsd+tQV9tTMEuH8GEr5Oy/D3XNOB6eW2vVZrHkCob itgr/4pmCdAumrx6D3b8GTUHk72Pd7b3DAyrr4UqkWIB3Dj18AzSEG4i0Jli/Zi8kmkr wZQNip9Xc/sfg4uf5tj2+xMEp0UbOZxtN1l3Zw7Tb8aUSClAsRWR9vwcinEtO/i1Dhmw Ib7w== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1779489266; x=1780094066; h=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; bh=QhHSiOmkLS8o0pebuQEPSAqgSPXeTQRZXi8kWVXxgY0=; b=qztsHg/EGoN3faC1T8DsydwHevpO+o9O4sWsxCwci5jfDJ+HOgH8qooHn+Bfp2WaCi 4NY72/3ApAwBn/g9E66Wfz7v0ayDDWTM1Mx3wdjSlcNjhsm8hoXst7omwH7J0+CCwfCV 95uNhEyU3szlI7/xNbRM1ZMD7KrCvMBPjiWPyAtHDj4NIL+lY/tzr8vYNJHXRqh8WQyb uqDicR6PE+e2j1ap+5DAUx5SCX0xEmMJhvoutWGN+TqP/Vwhs09fIKE5hYqbyV3w0VgM HAnPJuJcwtTISJ06SORUhbNUH+1Wl2B5Nx+axB4N4U/wB8R6pbVaXVskg8MOGazB6v4K e6Mg== X-Forwarded-Encrypted: i=1; AFNElJ8NLVf4FLCwM68rV8y/MuCKrntdnSHMAX6LfAXAbciCn/IDY1CSzR/D4BX+nfCddA4khtx7EaWNllxCmRk=@vger.kernel.org X-Gm-Message-State: AOJu0YyYDdX1YXv2L55Ug0wRi79+rwKCUnoKMF3+IM1YCDTShBAplYex mAwxXH/+IAYNQNbBsnq+IPA5sZHwo8jCQkC3V6WBGtNhUGL32slahADTazVgWK7TbQahBrOtgKE yeLn/V8K+nA== X-Received: from dybow8.prod.google.com ([2002:a05:7300:ec08:b0:2dc:7d1e:d0e7]) (user=irogers job=prod-delivery.src-stubby-dispatcher) by 2002:a05:693c:2c92:b0:2c7:3a7:c7a7 with SMTP id 5a478bee46e88-30449193190mr2615481eec.25.1779489265617; Fri, 22 May 2026 15:34:25 -0700 (PDT) Date: Fri, 22 May 2026 15:33:39 -0700 In-Reply-To: <20260522223342.2393553-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: <20260522223342.2393553-1-irogers@google.com> X-Mailer: git-send-email 2.54.0.794.g4f17f83d09-goog Message-ID: <20260522223342.2393553-12-irogers@google.com> Subject: [RFC PATCH v1 11/14] perf stat: Extend JSON output linter to test core aggregation checks From: Ian Rogers To: Peter Zijlstra , Ingo Molnar , Arnaldo Carvalho de Melo , Namhyung Kim , Jiri Olsa , Ian Rogers , Adrian Hunter , James Clark , linux-kernel@vger.kernel.org, linux-perf-users@vger.kernel.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-allocatio= n, and streaming print callbacks are formally validated across standard and in= terval modes. Signed-off-by: Ian Rogers TAG=3Dagy CONV=3D7c33d6a0-70a2-454a-82f7-4de2101a5b9c --- tools/perf/tests/shell/stat+json_output.sh | 43 +++++++++++++++------- 1 file changed, 30 insertions(+), 13 deletions(-) diff --git a/tools/perf/tests/shell/stat+json_output.sh b/tools/perf/tests/= shell/stat+json_output.sh index 85d1ad7186c6..d35098aae093 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() { @@ -36,7 +37,7 @@ function ParanoidAndNotRoot() check_no_args() { echo -n "Checking json output: no args " - perf stat -j -o "${stat_output}" true + perf stat -j $perf_new_opt -o "${stat_output}" true $PYTHON $pythonchecker --no-args --file "${stat_output}" echo "[Success]" } @@ -49,7 +50,7 @@ check_system_wide() 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]" } @@ -62,7 +63,7 @@ check_system_wide_no_aggr() 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]" } @@ -70,7 +71,7 @@ check_system_wide_no_aggr() check_interval() { echo -n "Checking json output: interval " - perf stat -j -I 1000 -o "${stat_output}" true + perf stat -j $perf_new_opt -I 1000 -o "${stat_output}" true $PYTHON $pythonchecker --interval --file "${stat_output}" echo "[Success]" } @@ -79,7 +80,7 @@ check_interval() check_event() { echo -n "Checking json output: event " - perf stat -j -e cpu-clock -o "${stat_output}" true + perf stat -j $perf_new_opt -e cpu-clock -o "${stat_output}" true $PYTHON $pythonchecker --event --file "${stat_output}" echo "[Success]" } @@ -92,7 +93,7 @@ check_per_core() 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]" } @@ -105,7 +106,7 @@ check_per_thread() 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]" } @@ -118,7 +119,7 @@ check_per_cache_instance() 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 @@ -130,7 +131,7 @@ check_per_cluster() 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 @@ -142,7 +143,7 @@ check_per_die() 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]" } @@ -155,7 +156,7 @@ check_per_node() 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]" } @@ -168,7 +169,7 @@ check_per_socket() 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]" } @@ -181,7 +182,7 @@ check_metric_only() 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]" } @@ -232,5 +233,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 +# Run New API JSON basic and standard aggregation checks +perf_new_opt=3D"--new" +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.54.0.794.g4f17f83d09-goog From nobody Sun May 24 20:33:35 2026 Received: from mail-dl1-f74.google.com (mail-dl1-f74.google.com [74.125.82.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 C034039185C for ; Fri, 22 May 2026 22:34:28 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=74.125.82.74 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1779489274; cv=none; b=VB2WEOeFtWpoEHuZMtZxlMsSaLwAIS56M/e0+8J8SxMzI5ZXAlYHTLfzPCA5VFL1Cuzfk0AexcmR9PKsS6XjCZxGLNb0FlcVc0TTO1zG/O7FQvlJtPkkVwm3oKIzb/yD9X3zKchPNeBdIiX2fJ+/+SY/+DwiRfqbvuuTA1w9yCc= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1779489274; c=relaxed/simple; bh=3S4dxZh/zV71iVJjK0YAfP3cyGCMxAVA2lJp1eqBEGM=; h=Date:In-Reply-To:Mime-Version:References:Message-ID:Subject:From: To:Content-Type; b=mm9vE7TOOI5Di+qUrA6ETJauf+t5SllqO4rHacJkDYulOEfKKsUfGBw3Lw0SXQwWO9GYT0H8Nn3DwRWGDQuGvUKc7e2SCG+JhEio11YP/Kdkp/E8ae41kXmN20vFJ/C1/ou+A22gVEE7dpPPMn6CYyiJi4nyYKQdY7XdhwJeZAs= 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=PVhvp0cQ; arc=none smtp.client-ip=74.125.82.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="PVhvp0cQ" Received: by mail-dl1-f74.google.com with SMTP id a92af1059eb24-135f2aa4742so6193248c88.1 for ; Fri, 22 May 2026 15:34:28 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=20251104; t=1779489268; x=1780094068; darn=vger.kernel.org; h=to:from:subject:message-id:references:mime-version:in-reply-to:date :from:to:cc:subject:date:message-id:reply-to; bh=l2kO28M+YZNuE0ghx64qUE2dfp0eRAhsXViTCXkqCe0=; b=PVhvp0cQb9ixvVOQNPBNPgx9i4O6zW1bSw+epvv4TT979IaZXsVJk+gMLzP+6hYxs4 9p4H30uEo6PUjtiFek7v4vkNTmXw9WmVpm24zWlHQaZGMlXQWD/SvvB4544pAFGstTQI MYmWGEW3bdiUfbeWSLjLy/gCWz/8r5Zvm44ITJJxbunCE/Zf70kUjka1WfwotHVmeuyP mMw24OSmai8qIGFWEmkL4R5u/iEbtkD6ErDUxZgVtf+8WEh6kBJab/tddapqPnToEc4a QTa7/TvGlwgv7hxg18VUUhpjGhVT3Nt6dR7A++KPm8CXlF6b61cSrjXCT5R6WPxon6Me Ml0Q== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1779489268; x=1780094068; h=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; bh=l2kO28M+YZNuE0ghx64qUE2dfp0eRAhsXViTCXkqCe0=; b=N7MPOFi7cwY68Y0cLjl//f9X8YI1zEByBGhX0TDkcI1eeeXUPii7WwEU1UIxEBIT0H 5BHHCD8eCFIXvzlK4xv9+UkewCOTKxZZUkbN3BV8JtznzgHvE23ZJ0QmNLYTcL+1cteR 5SI4dfUW8S/pYBQO/L4wnzD3jvVu8Bk86qhSZlLbI7WAWDGyv/TeIPoAdvYpxeKoy7iy CWuHs8sAA/q3QVpPNPgifaZrkdQotEib+Cbty3jDsy5zwXtrLgaMCMtij5GZPflQQgRm 3RWynHqi7Ej9Ph4Tnn3xUfJ6Yv5WMlcrHYKiHXhwG73v4qRp9iQKdeBiwFyooae/kEGR H8zQ== X-Forwarded-Encrypted: i=1; AFNElJ+Kq9DK9Ywbcgmobp5SqO1iHbUW/90V5G5xOOlperNVwE6ifSDshtd/QffhTO52iAVQLE7f7q2hfCkq2RU=@vger.kernel.org X-Gm-Message-State: AOJu0YzqxQdPs+pnxNjOYJ9usuBpYnqPi2iykY91gRSTBi3RHXLvqft4 VNydUWWPAx3s026+mLTEZMKDn6xL8O8owgnquko+tVPCCvukRT6b9Cs/pmR6VV+d52Sa7877TpR 2sRev9h4Qzg== X-Received: from dlbef14.prod.google.com ([2002:a05:7022:f4e:b0:135:cc8b:4336]) (user=irogers job=prod-delivery.src-stubby-dispatcher) by 2002:a05:7022:ef01:b0:12d:d972:b969 with SMTP id a92af1059eb24-1365fa45b6dmr1897947c88.24.1779489267423; Fri, 22 May 2026 15:34:27 -0700 (PDT) Date: Fri, 22 May 2026 15:33:40 -0700 In-Reply-To: <20260522223342.2393553-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: <20260522223342.2393553-1-irogers@google.com> X-Mailer: git-send-email 2.54.0.794.g4f17f83d09-goog Message-ID: <20260522223342.2393553-13-irogers@google.com> Subject: [RFC PATCH v1 12/14] perf stat: Extend JSON output linter to test advanced PMU and metric-only checks From: Ian Rogers To: Peter Zijlstra , Ingo Molnar , Arnaldo Carvalho de Melo , Namhyung Kim , Jiri Olsa , Ian Rogers , Adrian Hunter , James Clark , linux-kernel@vger.kernel.org, linux-perf-users@vger.kernel.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 print= ing callbacks are verified and structurally valid under advanced topology-aware modes and= metric-only JSON row-column layouts. Signed-off-by: Ian Rogers TAG=3Dagy CONV=3D7c33d6a0-70a2-454a-82f7-4de2101a5b9c --- tools/perf/tests/shell/stat+json_output.sh | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tools/perf/tests/shell/stat+json_output.sh b/tools/perf/tests/= shell/stat+json_output.sh index d35098aae093..3d513de0e6e7 100755 --- a/tools/perf/tests/shell/stat+json_output.sh +++ b/tools/perf/tests/shell/stat+json_output.sh @@ -248,6 +248,12 @@ then check_per_die check_per_socket fi +if [ $skip_test -ne 1 ] +then + check_per_cache_instance + check_per_cluster +fi +check_metric_only =20 cleanup exit 0 --=20 2.54.0.794.g4f17f83d09-goog From nobody Sun May 24 20:33:35 2026 Received: from mail-dy1-f201.google.com (mail-dy1-f201.google.com [74.125.82.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 866E937DAA8 for ; Fri, 22 May 2026 22:34:32 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=74.125.82.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1779489274; cv=none; b=ZZ48QdKfGN2jkbQFk3hYNZfTrj3bh681TnnN9bCI3KZTe0nFcru+npzFqwjZQ9Sb+kJFfeXQ8Kl0g47u52UFE436sBJNcDyglWRbuh3YZDVgkXveyZB6WSuGOaCLUVdOqgoG3A04bDD82FCQaUUrk+9rXMnPPoQUALo9jcfPgo0= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1779489274; c=relaxed/simple; bh=82oRk4SsWFkLEO66xqJJ3QJWj63Uj/yIgFDzEE7VIJY=; h=Date:In-Reply-To:Mime-Version:References:Message-ID:Subject:From: To:Content-Type; b=lik9U3Vt2z90eYB4xjNtw5DAO0k7jgtRUTd3xIbR1wEODk1c6dIHCJwJBAxk92VHCkou5bl8mQeChDKKGTVrSAwiy0K5XugC96UfpjAWBG27J30CMkNpd8vSKQJ04DjbDtZJwC+X1l8ftJUo3t3hycitsb6oOQWYgNAV0zNDlbY= 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=f/nrjuOI; arc=none smtp.client-ip=74.125.82.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="f/nrjuOI" Received: by mail-dy1-f201.google.com with SMTP id 5a478bee46e88-2ee1da7a13fso9393912eec.1 for ; Fri, 22 May 2026 15:34:32 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=20251104; t=1779489270; x=1780094070; darn=vger.kernel.org; h=to:from:subject:message-id:references:mime-version:in-reply-to:date :from:to:cc:subject:date:message-id:reply-to; bh=3CcYRk/hjgf1eQJiKdM92hfT80JcCdgJogs5qRtn8pU=; b=f/nrjuOIqiO2pGp8LxXeYM/NZXTxmg2m/hpD1wW8F/UhWjZm7KxW1lgDdeia8/117I dOMyI4nNsmDQUOlLHKqdufW2Y7cewkOBYhgEM88sTzzNCzV40EdlzHPKncZUBB6W7EwS VCRTG5niOfCdlMXDyMruTrMayWamSjviz9s1hOtlEGta8A4uKgyplE9HNLYQN/f+/AaQ a2041m4osClcf8lcrv20sirtxGjrp8wy2lmCr6jVWa+tZkcxI4RUO7Grv+DclLupPfPF 9l2a74/kOIHZSDppW5wFnLEsPa0k4RRTB1nsGbN6fLQmC9T7Jjc4sLI5PgN4hdfZAiMK Qp3g== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1779489270; x=1780094070; h=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; bh=3CcYRk/hjgf1eQJiKdM92hfT80JcCdgJogs5qRtn8pU=; b=G2FcIEFLNE+cSYKkbsGxYPYWZT0dyDovAWR4jP4jRQBuVhEvfGoILI+IutySZw67Lh XMArXfnCIRdfPIqYHmdGdUgl0vYP6DOvUjBLPZkGmVSk2uzpR81mXq8LhZ5fWlNllap3 hQMSk9SVye/E7RoeizqUkSPofODoCEp0NMqAMKltPyL1FZnLTJkS7EyAUMEefm4zx6YW UlFmWVm08nqJZSkMfpQUpxxWX2mUMArdAWhQJFThMqyHl/llqAHruL/DbriTlZcKgZQ+ PwZWIjbcQi8iXmr7IiQaiKL9AoJ+RAlYaR8XivqYKf7mbSsKlY9CwcR0kQEiGyVfjE+E WhXQ== X-Forwarded-Encrypted: i=1; AFNElJ8PI2aV3FCcg2pGMG+sIrXEGibXUnasP9CUm5L6crxIM301qaiupD1edhKHamZx4huSggmXY9MPsVB/oco=@vger.kernel.org X-Gm-Message-State: AOJu0YwHQaagoJwyA76cbpveITPMbdpqW3IPSYeqlQ3PsbfayfkPDIMk Z7kJ8F7E5leXlbcsTAItp3AcYAqkiF3zzbeOT+b/n1Vz8OqaJZWHw5mRqVYb+2PqXpnh5i9qv6b H0tuLcEcdWA== X-Received: from dylq22.prod.google.com ([2002:a05:7300:5996:b0:2de:ed01:5510]) (user=irogers job=prod-delivery.src-stubby-dispatcher) by 2002:a05:7300:b104:b0:2da:1874:f3bd with SMTP id 5a478bee46e88-30449051bd3mr3000405eec.16.1779489269488; Fri, 22 May 2026 15:34:29 -0700 (PDT) Date: Fri, 22 May 2026 15:33:41 -0700 In-Reply-To: <20260522223342.2393553-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: <20260522223342.2393553-1-irogers@google.com> X-Mailer: git-send-email 2.54.0.794.g4f17f83d09-goog Message-ID: <20260522223342.2393553-14-irogers@google.com> Subject: [RFC PATCH v1 13/14] perf stat: Add --new support to PMU metrics Python validator From: Ian Rogers To: Peter Zijlstra , Ingo Molnar , Arnaldo Carvalho de Melo , Namhyung Kim , Jiri Olsa , Ian Rogers , Adrian Hunter , James Clark , linux-kernel@vger.kernel.org, linux-perf-users@vger.kernel.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 intern= ally 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 TAG=3Dagy CONV=3D7c33d6a0-70a2-454a-82f7-4de2101a5b9c --- 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 dea8ef1977bf..f69cf0e0de57 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)) @@ -584,6 +588,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') @@ -592,7 +598,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.54.0.794.g4f17f83d09-goog From nobody Sun May 24 20:33:35 2026 Received: from mail-dy1-f201.google.com (mail-dy1-f201.google.com [74.125.82.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 7686C391827 for ; Fri, 22 May 2026 22:34:32 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=74.125.82.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1779489273; cv=none; b=eq18+tFiQ6wJGqxTityaksiyWFCLNTCXGTmtpPkzZKncmH+uP+VLeWiO1BEycePKCzRCHQJO1ZBEEUYdN3U5cRiwpquYYqVZcZH8QjOZf0ph4+HShvZ1qz10Y2zHMzhPzbR0g/27DMY70CsktfOPTX9/YRjpb3TrrLV5vZi4bBs= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1779489273; c=relaxed/simple; bh=tvhSffwPee9SSUcUYA6HQ3/aeT7HT6qFfUHo3kEyz70=; h=Date:In-Reply-To:Mime-Version:References:Message-ID:Subject:From: To:Content-Type; b=Y6d1RsVcvPPcaXzMXf7e+dR6uNcLNHSiQaLc4RwYZmsSzPqdtrxRU8S3nCH3+iSG9hQLA59t8RePMaLnzkMs2+N0dvJnmaYyQTDRoBHyXNKe+FoPIdGo4j0Ivd5dhOCE/0IIqYxNALim056/wzHauTBE2pwWPNokgUumgz5gPGg= 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=MinXX8f6; arc=none smtp.client-ip=74.125.82.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="MinXX8f6" Received: by mail-dy1-f201.google.com with SMTP id 5a478bee46e88-2f3ec2e8d07so4736907eec.1 for ; Fri, 22 May 2026 15:34:32 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=20251104; t=1779489272; x=1780094072; darn=vger.kernel.org; h=to:from:subject:message-id:references:mime-version:in-reply-to:date :from:to:cc:subject:date:message-id:reply-to; bh=lVqEIj3RzCuef4mURYGhNpGwQ7/i8dQl4B1awkm/B+0=; b=MinXX8f6V2E4i4XE2tofw8CXWyJBCLY8SKWUJfKhMGiLkFt6ZO6ff1cvvfaA2Dmw4g U625x42SW5eJPM+8vEkINZ1TnOXnWxlsClDcpNQblMboWjSvNMbMiCl55jUQAAqJJTHU CBCpmj5gwoqzEbtbgM+41+2aYZWaIO4gjfumbplUFdlz9NGYfuoizaTAcihbh8IMal+b KJylRREyrZyDi7wipBWZ0Pldywd6ck40NgvUX0+1vPtCLAOy0WxflsNFQUXOHXK6w6Hr GSB9YQWS5UxQ69Jzh4/O+lTTXLVyWirXrJUec41ySdfTWntuYREFt11pzqc65Tps24Qm nEIg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1779489272; x=1780094072; h=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; bh=lVqEIj3RzCuef4mURYGhNpGwQ7/i8dQl4B1awkm/B+0=; b=g+AmeyM7GlF+DFkbg8wySfNovCPh2KQJGMqBHRD2msrpEsWSmpQFrreLFhoLxSoopw gAX07hLbNyNCoIafxNleH7kPWf4pf5XgJOEbQKbj0NtmG1JQ6xy34PL2/k3kvr0vcZuq 93jKSo/VrUEB68+fWdFdDULiInp0WA8AjP5u/+hfoNpT3nl6BtL1qvVHokybvkVrOVdW I6pdTmsmk7HBYxrFslUfyvhyINakQSMgmctiygJfe97P9UEnc+tg03HZI44dMAxwEoIa fSjnhwWvS0TuLTHFxqKIshj/Cmtz+UY5aNF6vflSCrE6igYtm3+vxYer/6opx/jUaD6b +Ymw== X-Forwarded-Encrypted: i=1; AFNElJ/7CEZYKGDprhKOO/tNjHYAQLe/u1quJGO5k3UR1W9LWgJ6pCEzkHApgFmlpzilar4+9Oplc+tx1uROUBY=@vger.kernel.org X-Gm-Message-State: AOJu0YwhWm+gCCRpA+4IgmyfoubRvV1bvBhVyPwH9IGQcSHtCiYaW5Hv 8zVT3DHdEoZX2FLQAxvErkuP2l15P6dBF9AO5Iev/whSsa66kdv+mRxp8+PqQR/T+uToKI6OI/h yjBHrLkFdNQ== X-Received: from dycnn6.prod.google.com ([2002:a05:7301:1406:b0:303:9118:b1ef]) (user=irogers job=prod-delivery.src-stubby-dispatcher) by 2002:a05:7300:6920:b0:2c5:ed1b:ca9 with SMTP id 5a478bee46e88-30448ffc876mr2782355eec.1.1779489271486; Fri, 22 May 2026 15:34:31 -0700 (PDT) Date: Fri, 22 May 2026 15:33:42 -0700 In-Reply-To: <20260522223342.2393553-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: <20260522223342.2393553-1-irogers@google.com> X-Mailer: git-send-email 2.54.0.794.g4f17f83d09-goog Message-ID: <20260522223342.2393553-15-irogers@google.com> Subject: [RFC PATCH v1 14/14] perf stat: Extend PMU metrics value linter to validate --new outputs From: Ian Rogers To: Peter Zijlstra , Ingo Molnar , Arnaldo Carvalho de Melo , Namhyung Kim , Jiri Olsa , Ian Rogers , Adrian Hunter , James Clark , linux-kernel@vger.kernel.org, linux-perf-users@vger.kernel.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 TAG=3Dagy CONV=3D7c33d6a0-70a2-454a-82f7-4de2101a5b9c --- tools/perf/tests/shell/stat_metrics_values.sh | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/tools/perf/tests/shell/stat_metrics_values.sh b/tools/perf/tes= ts/shell/stat_metrics_values.sh index 30566f0b5427..02c0bcf6e078 100755 --- a/tools/perf/tests/shell/stat_metrics_values.sh +++ b/tools/perf/tests/shell/stat_metrics_values.sh @@ -22,9 +22,20 @@ for cputype in /sys/bus/event_source/devices/cpu_*; do $PYTHON $pythonvalidator -rule $rulefile -output_dir $tmpdir -wl "${workl= oad}" \ -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." + rm -rf $tmpdir + exit $ret + fi + + echo "Testing metrics for: $cputype (New API)" + $PYTHON $pythonvalidator -rule $rulefile -output_dir $tmpdir -wl "${workl= oad}" \ + -cputype "${cputype}" -new + ret=3D$? + rm -rf $tmpdir + if [ $ret -ne 0 ]; then + echo "Metric validation return with errors (New API). Please check metri= cs reported with errors." + exit $ret fi done exit $ret --=20 2.54.0.794.g4f17f83d09-goog