From nobody Mon Feb 9 06:25:20 2026 Received: from mail-pl1-f202.google.com (mail-pl1-f202.google.com [209.85.214.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 706FA33D6D1 for ; Tue, 11 Nov 2025 21:22:30 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.214.202 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1762896152; cv=none; b=mE3XHRab2tLKEr59G64rb7XlzOxHPjE2y7wIJ1Ag6FQrOux5RFS95CvXI75FZYc0xIYTDJbNGLv6HNAqZVCm72YBkGVobBzSZwvzWWJVrFleHSKKCUjExKxCY/tI2f0+gUc8p13bSx+YYlTL3V9vH5oO5J+f5uVUHunf0X/Zh0k= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1762896152; c=relaxed/simple; bh=4Lmv39Va4NvbsUZn/zTwSbp3LkNrEW1qJrXP79OCxBo=; h=Date:In-Reply-To:Mime-Version:References:Message-ID:Subject:From: To:Content-Type; b=Njm6wnpezLMZhc9dp7QUQaVLikNwMdmg59RaYPNgEu+Hx1Gr0pR+0r/r34jUqI00sLV0JvaymHxKXoogjHW9wEltQL4YfbNAlzaVhKGKUO7RtCBjgJ0Ajv3n3I/laCxwHmz9oYOHVFT3nNSrMueHNRTf7ZkxQSu4g1WkL97EtCw= 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=Zkf8ik0/; arc=none smtp.client-ip=209.85.214.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="Zkf8ik0/" Received: by mail-pl1-f202.google.com with SMTP id d9443c01a7336-297fbfb4e53so2547085ad.1 for ; Tue, 11 Nov 2025 13:22:30 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=20230601; t=1762896150; x=1763500950; 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=l+qF7PeptfMQaB3OweMIMtu9vJFd3D62+znQvkE7Y4c=; b=Zkf8ik0/VHzP4kNffxNtLWy3pf+XTrkYk5X2CBe3olwTx5G/0/KUfNgecp/wu7Q9am FzRQ3UJ1vZEIT6+n4qIBConBkvzDRhINO7zXciiPMKgdtAs2CraiSpljvdfaKlRpeyhA 4JjaW7WI+BDr69yriYW++0e5YSYbEhUGM4KGd1PPlfDz7w13/m3sniOhnYa7QERcvGYg G6yLUYextzdjCWM74QMsSMOQR7Y4gJWe2EzaGR9uh4K2FDK4+Ts5j9601XPeVBmlCGIg s1sfdFSyRsP9wGt7pDHl/dtpUU5kdrK4MFQdVIYbXISkswh7mx9NBxq5kOjG0S+SjC0O Owwg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1762896150; x=1763500950; 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=l+qF7PeptfMQaB3OweMIMtu9vJFd3D62+znQvkE7Y4c=; b=dYKcqT/gFjQLDbzYkdVongJMuGvn90PtcZgGBuE14QOXQgsPZpmrHOH+EoP4edyb99 B+IJ6UPIivzZgmgDUrVFaqq/OMGDIuMCSdpAoxO+qgRzX3iy/A6Kp5WvhNs4kddwxcDw RPe/ktrJJjYoFmmW1RkWLEKiGw8APsgqGteqQqK/+UBX2/J660ppfk3ugVI4/Q7Cm2ij o39sOUk5/C8agf2H/8NVp09lh4JtrXtMefhjBXUQ+QMVQlEMyqgehFGkLW++qGdSR/k3 y8bItxdG1OU7b/ovxu5wh4gVt7F3NBprvwsaeIfDomk7aZcrcINmaNwY33dmALfVO2Ti BxHA== X-Forwarded-Encrypted: i=1; AJvYcCU9FRn0tT6AnSk19h8FUto2I0n9qOFhxEMVxri0mfP7zeBbcvBXAzPsRA3wM/kS9Emd5o4FgYVkiCy9Ask=@vger.kernel.org X-Gm-Message-State: AOJu0YzK3EMspcwPglSGJwIOpYD/N1mM3rYE+ZkMvpHP5L4MgkH2kFPx o5bBS5mYg14juxxKulQmUxel5SA1Fv7t2Qu1z35zc1w561NxXi7nkqdUdb5+xh1zNlBwX3nVc4q 0fPjqP+XuPw== X-Google-Smtp-Source: AGHT+IEBwCIFvRacNqGgepf6wED8NzxorxFcVpa5aXsGuJIeR9T2jmamrWwKpK2dYte+Qd3JW9PMOqC4P4KQ X-Received: from dlbtp3.prod.google.com ([2002:a05:7022:3b83:b0:119:49ca:6b95]) (user=irogers job=prod-delivery.src-stubby-dispatcher) by 2002:a17:902:da4b:b0:272:c95c:866 with SMTP id d9443c01a7336-2984ed46fb4mr8418845ad.20.1762896149537; Tue, 11 Nov 2025 13:22:29 -0800 (PST) Date: Tue, 11 Nov 2025 13:21:54 -0800 In-Reply-To: <20251111212206.631711-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: <20251111212206.631711-1-irogers@google.com> X-Mailer: git-send-email 2.51.2.1041.gc1ab5b90ca-goog Message-ID: <20251111212206.631711-7-irogers@google.com> Subject: [PATCH v4 06/18] perf script: Change metric format to use json metrics From: Ian Rogers To: Peter Zijlstra , Ingo Molnar , Arnaldo Carvalho de Melo , Namhyung Kim , Alexander Shishkin , Jiri Olsa , Ian Rogers , Adrian Hunter , James Clark , Xu Yang , Chun-Tse Shao , Thomas Richter , Sumanth Korikkar , Collin Funk , Thomas Falcon , Howard Chu , Dapeng Mi , Levi Yun , Yang Li , linux-kernel@vger.kernel.org, linux-perf-users@vger.kernel.org, Andi Kleen , Weilin Wang Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" The metric format option isn't properly supported. This change improves that by making the sample events update the counts of an evsel, where the shadow metric code expects to read the values. To support printing metrics, metrics need to be found. This is done on the first attempt to print a metric. Every metric is parsed and then the evsels in the metric's evlist compared to those in perf script using the perf_event_attr type and config. If the metric matches then it is added for printing. As an event in the perf script's evlist may have >1 metric id, or different leader for aggregation, the first metric matched will be displayed in those cases. An example use is: ``` $ perf record -a -e '{instructions,cpu-cycles}:S' -a -- sleep 1 $ perf script -F period,metric ... 867817 metric: 0.30 insn per cycle 125394 metric: 0.04 insn per cycle 313516 metric: 0.11 insn per cycle metric: 1.00 insn per cycle ``` Signed-off-by: Ian Rogers --- tools/perf/builtin-script.c | 252 ++++++++++++++++++++++++++++++++---- 1 file changed, 230 insertions(+), 22 deletions(-) diff --git a/tools/perf/builtin-script.c b/tools/perf/builtin-script.c index d813adbf9889..8bab5b264f61 100644 --- a/tools/perf/builtin-script.c +++ b/tools/perf/builtin-script.c @@ -33,6 +33,7 @@ #include "util/path.h" #include "util/event.h" #include "util/mem-info.h" +#include "util/metricgroup.h" #include "ui/ui.h" #include "print_binary.h" #include "print_insn.h" @@ -341,9 +342,6 @@ struct evsel_script { char *filename; FILE *fp; u64 samples; - /* For metric output */ - u64 val; - int gnum; }; =20 static inline struct evsel_script *evsel_script(struct evsel *evsel) @@ -2132,13 +2130,161 @@ static void script_new_line(struct perf_stat_confi= g *config __maybe_unused, fputs("\tmetric: ", mctx->fp); } =20 -static void perf_sample__fprint_metric(struct perf_script *script, - struct thread *thread, +struct script_find_metrics_args { + struct evlist *evlist; + bool system_wide; +}; + +static struct evsel *map_metric_evsel_to_script_evsel(struct evlist *scrip= t_evlist, + struct evsel *metric_evsel) +{ + struct evsel *script_evsel; + + evlist__for_each_entry(script_evlist, script_evsel) { + /* Skip if perf_event_attr differ. */ + if (metric_evsel->core.attr.type !=3D script_evsel->core.attr.type) + continue; + if (metric_evsel->core.attr.config !=3D script_evsel->core.attr.config) + continue; + /* Skip if the script event has a metric_id that doesn't match. */ + if (script_evsel->metric_id && + strcmp(evsel__metric_id(metric_evsel), evsel__metric_id(script_evsel= ))) { + pr_debug("Skipping matching evsel due to differing metric ids '%s' vs '= %s'\n", + evsel__metric_id(metric_evsel), evsel__metric_id(script_evsel)); + continue; + } + return script_evsel; + } + return NULL; +} + +static int script_find_metrics(const struct pmu_metric *pm, + const struct pmu_metrics_table *table __maybe_unused, + void *data) +{ + struct script_find_metrics_args *args =3D data; + struct evlist *script_evlist =3D args->evlist; + struct evlist *metric_evlist =3D evlist__new(); + struct evsel *metric_evsel; + int ret =3D metricgroup__parse_groups(metric_evlist, + /*pmu=3D*/"all", + pm->metric_name, + /*metric_no_group=3D*/false, + /*metric_no_merge=3D*/false, + /*metric_no_threshold=3D*/true, + /*user_requested_cpu_list=3D*/NULL, + args->system_wide, + /*hardware_aware_grouping=3D*/false); + + if (ret) { + /* Metric parsing failed but continue the search. */ + goto out; + } + + /* + * Check the script_evlist has an entry for each metric_evlist entry. If + * the script evsel was already set up avoid changing data that may + * break it. + */ + evlist__for_each_entry(metric_evlist, metric_evsel) { + struct evsel *script_evsel =3D + map_metric_evsel_to_script_evsel(script_evlist, metric_evsel); + struct evsel *new_metric_leader; + + if (!script_evsel) { + pr_debug("Skipping metric '%s' as evsel '%s' / '%s' is missing\n", + pm->metric_name, evsel__name(metric_evsel), + evsel__metric_id(metric_evsel)); + goto out; + } + + if (script_evsel->metric_leader =3D=3D NULL) + continue; + + if (metric_evsel->metric_leader =3D=3D metric_evsel) { + new_metric_leader =3D script_evsel; + } else { + new_metric_leader =3D + map_metric_evsel_to_script_evsel(script_evlist, + metric_evsel->metric_leader); + } + /* Mismatching evsel leaders. */ + if (script_evsel->metric_leader !=3D new_metric_leader) { + pr_debug("Skipping metric '%s' due to mismatching evsel metric leaders = '%s' vs '%s'\n", + pm->metric_name, evsel__metric_id(metric_evsel), + evsel__metric_id(script_evsel)); + goto out; + } + } + /* + * Metric events match those in the script evlist, copy metric evsel + * data into the script evlist. + */ + evlist__for_each_entry(metric_evlist, metric_evsel) { + struct evsel *script_evsel =3D + map_metric_evsel_to_script_evsel(script_evlist, metric_evsel); + struct metric_event *metric_me =3D metricgroup__lookup(&metric_evlist->m= etric_events, + metric_evsel, + /*create=3D*/false); + + if (script_evsel->metric_id =3D=3D NULL) { + script_evsel->metric_id =3D metric_evsel->metric_id; + metric_evsel->metric_id =3D NULL; + } + + if (script_evsel->metric_leader =3D=3D NULL) { + if (metric_evsel->metric_leader =3D=3D metric_evsel) { + script_evsel->metric_leader =3D script_evsel; + } else { + script_evsel->metric_leader =3D + map_metric_evsel_to_script_evsel(script_evlist, + metric_evsel->metric_leader); + } + } + + if (metric_me) { + struct metric_expr *expr; + struct metric_event *script_me =3D + metricgroup__lookup(&script_evlist->metric_events, + script_evsel, + /*create=3D*/true); + + if (!script_me) { + /* + * As the metric_expr is created, the only + * failure is a lack of memory. + */ + goto out; + } + list_splice_init(&metric_me->head, &script_me->head); + list_for_each_entry(expr, &script_me->head, nd) { + for (int i =3D 0; expr->metric_events[i]; i++) { + expr->metric_events[i] =3D + map_metric_evsel_to_script_evsel(script_evlist, + expr->metric_events[i]); + } + } + } + } + pr_debug("Found metric '%s' whose evsels match those of in the perf data\= n", + pm->metric_name); + evlist__delete(metric_evlist); +out: + return 0; +} + +static struct aggr_cpu_id script_aggr_cpu_id_get(struct perf_stat_config *= config __maybe_unused, + struct perf_cpu cpu) +{ + return aggr_cpu_id__global(cpu, /*data=3D*/NULL); +} + +static void perf_sample__fprint_metric(struct thread *thread, struct evsel *evsel, struct perf_sample *sample, FILE *fp) { - struct evsel *leader =3D evsel__leader(evsel); + static bool init_metrics; struct perf_stat_output_ctx ctx =3D { .print_metric =3D script_print_metric, .new_line =3D script_new_line, @@ -2150,23 +2296,85 @@ static void perf_sample__fprint_metric(struct perf_= script *script, }, .force_header =3D false, }; - struct evsel *ev2; - u64 val; + struct perf_counts_values *count, *old_count; + int cpu_map_idx, thread_map_idx, aggr_idx; + struct evsel *pos; + + if (!init_metrics) { + /* One time initialization of stat_config and metric data. */ + struct script_find_metrics_args args =3D { + .evlist =3D evsel->evlist, + .system_wide =3D perf_thread_map__pid(evsel->core.threads, /*idx=3D*/0)= =3D=3D -1, + + }; + if (!stat_config.output) + stat_config.output =3D stdout; + + if (!stat_config.aggr_map) { + /* TODO: currently only global aggregation is supported. */ + assert(stat_config.aggr_mode =3D=3D AGGR_GLOBAL); + stat_config.aggr_get_id =3D script_aggr_cpu_id_get; + stat_config.aggr_map =3D + cpu_aggr_map__new(evsel->evlist->core.user_requested_cpus, + aggr_cpu_id__global, /*data=3D*/NULL, + /*needs_sort=3D*/false); + } + + metricgroup__for_each_metric(pmu_metrics_table__find(), script_find_metr= ics, &args); + init_metrics =3D true; + } + + if (!evsel->stats) { + if (evlist__alloc_stats(&stat_config, evsel->evlist, /*alloc_raw=3D*/tru= e) < 0) + return; + } + if (!evsel->stats->aggr) { + if (evlist__alloc_aggr_stats(evsel->evlist, stat_config.aggr_map->nr) < = 0) + return; + } =20 - if (!evsel->stats) - evlist__alloc_stats(&stat_config, script->session->evlist, /*alloc_raw= =3D*/false); - if (evsel_script(leader)->gnum++ =3D=3D 0) - perf_stat__reset_shadow_stats(); - val =3D sample->period * evsel->scale; - evsel_script(evsel)->val =3D val; - if (evsel_script(leader)->gnum =3D=3D leader->core.nr_members) { - for_each_group_member (ev2, leader) { - perf_stat__print_shadow_stats(&stat_config, ev2, - evsel_script(ev2)->val, - sample->cpu, - &ctx); + /* Update the evsel's count using the sample's data. */ + cpu_map_idx =3D perf_cpu_map__idx(evsel->core.cpus, (struct perf_cpu){sam= ple->cpu}); + if (cpu_map_idx < 0) { + /* Missing CPU, check for any CPU. */ + if (perf_cpu_map__cpu(evsel->core.cpus, /*idx=3D*/0).cpu =3D=3D -1 || + sample->cpu =3D=3D (u32)-1) { + /* Place the counts in the which ever CPU is first in the map. */ + cpu_map_idx =3D 0; + } else { + pr_info("Missing CPU map entry for CPU %d\n", sample->cpu); + return; + } + } + thread_map_idx =3D perf_thread_map__idx(evsel->core.threads, sample->tid); + if (thread_map_idx < 0) { + /* Missing thread, check for any thread. */ + if (perf_thread_map__pid(evsel->core.threads, /*idx=3D*/0) =3D=3D -1 || + sample->tid =3D=3D (u32)-1) { + /* Place the counts in the which ever thread is first in the map. */ + thread_map_idx =3D 0; + } else { + pr_info("Missing thread map entry for thread %d\n", sample->tid); + return; + } + } + count =3D perf_counts(evsel->counts, cpu_map_idx, thread_map_idx); + old_count =3D perf_counts(evsel->prev_raw_counts, cpu_map_idx, thread_map= _idx); + count->val =3D old_count->val + sample->period; + count->run =3D old_count->run + 1; + count->ena =3D old_count->ena + 1; + + /* Update the aggregated stats. */ + perf_stat_process_counter(&stat_config, evsel); + + /* Display all metrics. */ + evlist__for_each_entry(evsel->evlist, pos) { + cpu_aggr_map__for_each_idx(aggr_idx, stat_config.aggr_map) { + perf_stat__print_shadow_stats(&stat_config, pos, + count->val, + aggr_idx, + &ctx); } - evsel_script(leader)->gnum =3D 0; } } =20 @@ -2348,7 +2556,7 @@ static void process_event(struct perf_script *script, } =20 if (PRINT_FIELD(METRIC)) - perf_sample__fprint_metric(script, thread, evsel, sample, fp); + perf_sample__fprint_metric(thread, evsel, sample, fp); =20 if (verbose > 0) fflush(fp); --=20 2.51.2.1041.gc1ab5b90ca-goog