From: bot+bpf-ci@kernel.org
To: mykyta.yatsenko5@gmail.com,bpf@vger.kernel.org,ast@kernel.org,andrii@kernel.org,daniel@iogearbox.net,kernel-team@meta.com,eddyz87@gmail.com,memxor@gmail.com,qmo@kernel.org,linux-perf-users@vger.kernel.org,acme@kernel.org,namhyung@kernel.org
Cc: yatsenko@meta.com,ast@kernel.org,andrii@kernel.org,daniel@iogearbox.net,martin.lau@kernel.org,eddyz87@gmail.com,yonghong.song@linux.dev,mason@kernel.org,ihor.solodrai@linux.dev
Subject: Re: [PATCH bpf-next v3 3/3] bpftool: Scale counters and report cycles per run
Date: Tue, 8 Sep 2026 16:21:36 +0000 (UTC) [thread overview]
Message-ID: <cc417e4dba25ded6b33afcdfa4cd4a69de3e9c8ab7ca351c131ae92bc7430d86@mail.kernel.org> (raw)
In-Reply-To: <20260908-bpftool_cyles_per_run-v3-3-60e86f325c35@meta.com>
[-- Attachment #1: Type: text/plain, Size: 7699 bytes --]
> diff --git a/tools/bpf/bpftool/prog.c b/tools/bpf/bpftool/prog.c
> index 8c2f9255b36d..9e126f3823ad 100644
> --- a/tools/bpf/bpftool/prog.c
> +++ b/tools/bpf/bpftool/prog.c
[ ... ]
> @@ -2182,9 +2199,39 @@ static int profile_parse_metrics(int argc, char **argv)
> return selected_cnt;
> }
>
> +/*
> + * Filter out CPUs that have any selected metric not scheduled for them. This makes sure all
> + * metrics are using the same CPU set, as a result ratio metrics are consistent.
> + */
> +static void profile_filter_cpus(__u32 num_cpu,
> + struct bpf_perf_event_value vals[MAX_NUM_PROFILE_METRICS][num_cpu],
> + __u64 *counts)
> +{
> + __u32 m, cpu, key = 0;
> +
> + for (m = 0; m < ARRAY_SIZE(metrics); m++) {
> + if (!metrics[m].selected)
> + continue;
> +
> + for (cpu = 0; cpu < num_cpu; cpu++) {
> + /*
> + * CPU has hits, but this metric never scheduled, set counts[cpu] to 0
> + * so other metrics ignore this CPU too
> + */
> + if (counts[cpu] && !vals[key][cpu].running) {
> + p_info("%s not scheduled on CPU %u; excluding %llu runs from all metrics",
> + metrics[m].name, cpu, counts[cpu]);
> + counts[cpu] = 0;
> + }
> + }
> + key++;
> + }
> +}
[ ... ]
> @@ -2194,38 +2241,61 @@ static void profile_read_values(struct profiler_bpf *obj)
> count_map_fd = bpf_map__fd(obj->maps.counts);
> if (reading_map_fd < 0 || count_map_fd < 0) {
> p_err("failed to get fd for map");
> - return;
> + return min(reading_map_fd, count_map_fd);
> }
>
> err = bpf_map_lookup_elem(count_map_fd, &key, counts);
> if (err) {
> p_err("failed to read count_map: %s", strerror(errno));
> - return;
> + return err;
> + }
> +
> + for (m = 0; m < ARRAY_SIZE(metrics); m++) {
> + if (!metrics[m].selected)
> + continue;
> +
> + err = bpf_map_lookup_elem(reading_map_fd, &key, values[key]);
> + if (err) {
> + p_err("failed to read reading_map: %s", strerror(errno));
> + return err;
> + }
> + key++;
> }
>
> profile_total_count = 0;
> for (cpu = 0; cpu < num_cpu; cpu++)
> profile_total_count += counts[cpu];
>
> + profile_filter_cpus(num_cpu, values, counts);
> +
> + profile_valid_count = 0;
> + for (cpu = 0; cpu < num_cpu; cpu++)
> + profile_valid_count += counts[cpu];
> +
> + key = 0;
> for (m = 0; m < ARRAY_SIZE(metrics); m++) {
> - struct bpf_perf_event_value values[num_cpu];
> + double scale;
>
> if (!metrics[m].selected)
> continue;
>
> - err = bpf_map_lookup_elem(reading_map_fd, &key, values);
> - if (err) {
> - p_err("failed to read reading_map: %s",
> - strerror(errno));
> - return;
> - }
> for (cpu = 0; cpu < num_cpu; cpu++) {
> - metrics[m].val.counter += values[cpu].counter;
> - metrics[m].val.enabled += values[cpu].enabled;
> - metrics[m].val.running += values[cpu].running;
> + /* Skip CPUs with no runs or with an unscheduled metric. */
> + if (!counts[cpu])
> + continue;
> +
> + val = &values[key][cpu];
> +
> + metrics[m].val.enabled += val->enabled;
> + metrics[m].val.running += val->running;
> + metrics[m].val.counter += val->counter;
> + /* Scale counter values to account for perf event multiplexing. */
> + scale = (double)val->enabled / val->running;
> + metrics[m].scaled_val += val->counter * scale;
> }
> key++;
> }
> + return 0;
> }
Does this correctly handle the case where one metric is never scheduled
on any CPU?
The CPU-set filter in profile_filter_cpus() zeroes counts[cpu] for every
CPU where any selected metric has running == 0:
if (counts[cpu] && !vals[key][cpu].running) {
p_info("%s not scheduled on CPU %u; ...");
counts[cpu] = 0;
}
The aggregation loop then skips those CPUs for all metrics:
for (cpu = 0; cpu < num_cpu; cpu++) {
if (!counts[cpu])
continue;
...
metrics[m].val.counter += val->counter;
...
metrics[m].scaled_val += val->counter * scale;
}
If a selected metric is never scheduled on any CPU (the scenario this
series targets - commit 44f919dd1fbc describes profiling iTLB misses on
a long-running program with a full PMU), then every CPU with counts[cpu]
!= 0 gets zeroed.
For example, `bpftool prog profile <prog> cycles itlb_misses`:
* profile_valid_count becomes 0
* cycles' val.counter and scaled_val stay 0 even though the cycles
event was measured correctly on every CPU
* plain output prints "run_cnt 51397" followed by "0 cycles" and
"0 itlb_misses" with no ratio column
Before this patch the same run reported the real cycles count and 0 for
itlb_misses, so the healthy metric's data is lost.
In JSON mode there is no diagnostic at all. The p_info() call in
profile_filter_cpus() returns immediately when json_output is set, so
a consumer sees run_cnt=51397 with value=0, value_scaled=0, enabled=0,
running=0, and only run_cnt_valid=0 as a hint.
The commit message scopes the change to ratios ("Use the same CPU set
for derived ratios"), and the .rst says "all metric values exclude that
CPU so that ratios use a consistent CPU set" but neither documents that
a single unscheduled metric collapses the whole report to zeros, and
JSON gives no warning.
Would it make sense to exclude only the affected metric, or keep the raw
per-metric aggregation intact and restrict only the ratio denominators?
> @@ -2250,24 +2322,34 @@ static void profile_print_readings_json(void)
>
> static void profile_print_readings_plain(void)
> {
> - __u32 m;
> + __u32 i;
>
> printf("\n%18llu %-20s\n", profile_total_count, "run_cnt");
> - for (m = 0; m < ARRAY_SIZE(metrics); m++) {
> - struct bpf_perf_event_value *val = &metrics[m].val;
> + for (i = 0; i < ARRAY_SIZE(metrics); i++) {
> + struct profile_metric *m = &metrics[i];
> + struct bpf_perf_event_value *val = &m->val;
> int r;
> + __u64 ratio;
>
> - if (!metrics[m].selected)
> + if (!m->selected)
> continue;
> - printf("%18llu %-20s", val->counter, metrics[m].name);
> + printf("%18llu %-20s", m->scaled_val, m->name);
>
> - r = metrics[m].ratio_metric - 1;
> - if (r >= 0 && metrics[r].selected &&
> - metrics[r].val.counter > 0) {
> + r = m->ratio_metric;
> + switch (r) {
> + case METRIC_RUN_CNT:
> + ratio = profile_valid_count;
> + break;
> + case METRIC_NONE:
> + ratio = 0;
> + break;
> + default:
> + ratio = metrics[r].scaled_val;
> + }
> + if (ratio) {
> printf("# %8.2f %-30s",
> - val->counter * metrics[m].ratio_mul /
> - metrics[r].val.counter,
> - metrics[m].ratio_desc);
> + m->scaled_val * m->ratio_mul / ratio,
> + m->ratio_desc);
> } else {
> printf("%-41s", "");
> }
Plain output prints profile_total_count as "run_cnt" but the new "cycles
per run" column divides by profile_valid_count, and profile_valid_count
is never printed in plain mode (it is only exposed as the JSON
run_cnt_valid field).
Whenever profile_filter_cpus() excludes at least one CPU the two differ,
so the displayed numbers cannot be reconciled. The user sees run_cnt and
a scaled cycles value, and "# <x> cycles per run" where
x != cycles/run_cnt, with no way to see the denominator that was
actually used.
The only signal is the p_info() lines on stderr, which are easy to miss
when stdout is redirected and are suppressed entirely under --json.
Would it make sense to print the valid run count in plain mode (as JSON
now does) or label the ratio with the CPU set it was computed over?
---
AI reviewed your patch. Please fix the bug or email reply why it's not a bug.
See: https://github.com/kernel-patches/vmtest/blob/master/ci/claude/README.md
CI run summary: https://github.com/kernel-patches/bpf/actions/runs/34246064212
next prev parent reply other threads:[~2026-09-08 16:21 UTC|newest]
Thread overview: 16+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-09-08 14:27 [PATCH bpf-next v3 0/3] bpftool: Improve perf counter reporting Mykyta Yatsenko
2026-09-08 14:27 ` [PATCH bpf-next v3 1/3] bpftool: Track perf counter snapshot state Mykyta Yatsenko
2026-09-08 14:35 ` sashiko-bot
2026-09-08 22:08 ` Quentin Monnet
2026-09-08 23:56 ` Andrii Nakryiko
2026-09-09 9:19 ` Mykyta Yatsenko
2026-09-08 14:27 ` [PATCH bpf-next v3 2/3] perf bpf_counter: Track valid BPF counter snapshots Mykyta Yatsenko
2026-09-08 14:39 ` sashiko-bot
2026-09-08 14:27 ` [PATCH bpf-next v3 3/3] bpftool: Scale counters and report cycles per run Mykyta Yatsenko
2026-09-08 14:42 ` sashiko-bot
2026-09-08 16:21 ` bot+bpf-ci [this message]
2026-09-08 17:37 ` Mykyta Yatsenko
2026-09-08 22:08 ` Quentin Monnet
2026-09-09 0:02 ` Andrii Nakryiko
2026-09-09 9:49 ` Mykyta Yatsenko
2026-09-08 18:01 ` [PATCH bpf-next v3 0/3] bpftool: Improve perf counter reporting Ihor Solodrai
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=cc417e4dba25ded6b33afcdfa4cd4a69de3e9c8ab7ca351c131ae92bc7430d86@mail.kernel.org \
--to=bot+bpf-ci@kernel.org \
--cc=acme@kernel.org \
--cc=andrii@kernel.org \
--cc=ast@kernel.org \
--cc=bpf@vger.kernel.org \
--cc=daniel@iogearbox.net \
--cc=eddyz87@gmail.com \
--cc=ihor.solodrai@linux.dev \
--cc=kernel-team@meta.com \
--cc=linux-perf-users@vger.kernel.org \
--cc=martin.lau@kernel.org \
--cc=mason@kernel.org \
--cc=memxor@gmail.com \
--cc=mykyta.yatsenko5@gmail.com \
--cc=namhyung@kernel.org \
--cc=qmo@kernel.org \
--cc=yatsenko@meta.com \
--cc=yonghong.song@linux.dev \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox