* [PATCH bpf-next] bpftool: Print average cycles per program run in profiler
@ 2026-08-27 15:29 Mykyta Yatsenko
2026-08-27 16:22 ` bot+bpf-ci
` (2 more replies)
0 siblings, 3 replies; 7+ messages in thread
From: Mykyta Yatsenko @ 2026-08-27 15:29 UTC (permalink / raw)
To: bpf, ast, andrii, daniel, kernel-team, eddyz87, memxor, qmo
Cc: Mykyta Yatsenko
From: Mykyta Yatsenko <yatsenko@meta.com>
Total cycle counts are difficult to compare across workloads with
different run counts. Report cycles per run to expose the per-invocation
cost directly.
Example:
sudo ./bpftool prog profile name mprog duration 15 cycles instructions
423256 run_cnt
947413975 cycles # 2238.39 cycles per run
333965846 instructions # 0.35 insns per cycle
Signed-off-by: Mykyta Yatsenko <yatsenko@meta.com>
---
tools/bpf/bpftool/Documentation/bpftool-prog.rst | 6 ++++--
tools/bpf/bpftool/prog.c | 18 +++++++++++-------
2 files changed, 15 insertions(+), 9 deletions(-)
diff --git a/tools/bpf/bpftool/Documentation/bpftool-prog.rst b/tools/bpf/bpftool/Documentation/bpftool-prog.rst
index 90fa2a48cc26..2280dc4492c0 100644
--- a/tools/bpf/bpftool/Documentation/bpftool-prog.rst
+++ b/tools/bpf/bpftool/Documentation/bpftool-prog.rst
@@ -217,7 +217,9 @@ bpftool prog run *PROG* data_in *FILE* [data_out *FILE* [data_size_out *L*]] [ct
bpftool prog profile *PROG* [duration *DURATION*] *METRICs*
Profile *METRICs* for bpf program *PROG* for *DURATION* seconds or until
user hits <Ctrl+C>. *DURATION* is optional. If *DURATION* is not specified,
- the profiling will run up to **UINT_MAX** seconds.
+ the profiling will run up to **UINT_MAX** seconds. When **cycles** is
+ selected, plain output also reports the average number of cycles per
+ program run.
bpftool prog help
Print short help message.
@@ -360,7 +362,7 @@ EXAMPLES
::
51397 run_cnt
- 40176203 cycles (83.05%)
+ 40176203 cycles # 781.68 cycles per run (83.05%)
42518139 instructions # 1.06 insns per cycle (83.39%)
123 llc_misses # 2.89 LLC misses per million insns (83.15%)
diff --git a/tools/bpf/bpftool/prog.c b/tools/bpf/bpftool/prog.c
index a9f730d407a9..8935508f955b 100644
--- a/tools/bpf/bpftool/prog.c
+++ b/tools/bpf/bpftool/prog.c
@@ -2069,9 +2069,9 @@ struct profile_metric {
bool selected;
/* calculate ratios like instructions per cycle */
- const int ratio_metric; /* 0 for N/A, 1 for index 0 (cycles) */
+ const int ratio_metric; /* 0 for run_cnt, 1 for index 0 (cycles) */
const char *ratio_desc;
- const float ratio_mul;
+ const double ratio_mul;
} metrics[] = {
{
.name = "cycles",
@@ -2080,6 +2080,9 @@ struct profile_metric {
.config = PERF_COUNT_HW_CPU_CYCLES,
.exclude_user = 1,
},
+ .ratio_metric = 0,
+ .ratio_desc = "cycles per run",
+ .ratio_mul = 1.0,
},
{
.name = "instructions",
@@ -2256,17 +2259,18 @@ static void profile_print_readings_plain(void)
for (m = 0; m < ARRAY_SIZE(metrics); m++) {
struct bpf_perf_event_value *val = &metrics[m].val;
int r;
+ __u64 ratio;
if (!metrics[m].selected)
continue;
printf("%18llu %-20s", val->counter, metrics[m].name);
- r = metrics[m].ratio_metric - 1;
- if (r >= 0 && metrics[r].selected &&
- metrics[r].val.counter > 0) {
+ r = metrics[m].ratio_metric;
+ /* r == 0 is a special case for run_cnt */
+ ratio = r ? metrics[r - 1].val.counter : profile_total_count;
+ if (metrics[m].ratio_desc && ratio) {
printf("# %8.2f %-30s",
- val->counter * metrics[m].ratio_mul /
- metrics[r].val.counter,
+ val->counter * metrics[m].ratio_mul / ratio,
metrics[m].ratio_desc);
} else {
printf("%-41s", "");
---
base-commit: 23ff631b3b8b1452dfe933ee21f96321a9c5e209
change-id: 20260827-bpftool_cyles_per_run-93169fcb30b7
Best regards,
--
Mykyta Yatsenko <yatsenko@meta.com>
^ permalink raw reply related [flat|nested] 7+ messages in thread
* Re: [PATCH bpf-next] bpftool: Print average cycles per program run in profiler
2026-08-27 15:29 [PATCH bpf-next] bpftool: Print average cycles per program run in profiler Mykyta Yatsenko
@ 2026-08-27 16:22 ` bot+bpf-ci
2026-08-28 0:30 ` Andrii Nakryiko
2026-08-28 18:46 ` Suchit Karunakaran
2 siblings, 0 replies; 7+ messages in thread
From: bot+bpf-ci @ 2026-08-27 16:22 UTC (permalink / raw)
To: mykyta.yatsenko5, bpf, ast, andrii, daniel, kernel-team, eddyz87,
memxor, qmo
Cc: yatsenko, ast, andrii, daniel, martin.lau, eddyz87, yonghong.song,
clm, ihor.solodrai
[-- Attachment #1: Type: text/plain, Size: 3006 bytes --]
> diff --git a/tools/bpf/bpftool/Documentation/bpftool-prog.rst b/tools/bpf/bpftool/Documentation/bpftool-prog.rst
> index 90fa2a48cc26a..2280dc4492c06 100644
> --- a/tools/bpf/bpftool/Documentation/bpftool-prog.rst
> +++ b/tools/bpf/bpftool/Documentation/bpftool-prog.rst
[ ... ]
> diff --git a/tools/bpf/bpftool/prog.c b/tools/bpf/bpftool/prog.c
> index a9f730d407a92..8935508f955b1 100644
> --- a/tools/bpf/bpftool/prog.c
> +++ b/tools/bpf/bpftool/prog.c
> @@ -2069,9 +2069,9 @@ struct profile_metric {
> bool selected;
>
> /* calculate ratios like instructions per cycle */
> - const int ratio_metric; /* 0 for N/A, 1 for index 0 (cycles) */
> + const int ratio_metric; /* 0 for run_cnt, 1 for index 0 (cycles) */
> const char *ratio_desc;
> - const float ratio_mul;
> + const double ratio_mul;
> } metrics[] = {
This isn't a bug, but could the comment also mention that a NULL
ratio_desc means no ratio is printed? The l1d_loads entry now depends on
that rather than on ratio_metric == 0.
> {
> .name = "cycles",
> @@ -2080,6 +2080,9 @@ struct profile_metric {
> .config = PERF_COUNT_HW_CPU_CYCLES,
> .exclude_user = 1,
> },
> + .ratio_metric = 0,
> + .ratio_desc = "cycles per run",
> + .ratio_mul = 1.0,
> },
[ ... ]
> @@ -2256,17 +2259,18 @@ static void profile_print_readings_plain(void)
> for (m = 0; m < ARRAY_SIZE(metrics); m++) {
> struct bpf_perf_event_value *val = &metrics[m].val;
> int r;
> + __u64 ratio;
>
> if (!metrics[m].selected)
> continue;
> printf("%18llu %-20s", val->counter, metrics[m].name);
>
> - r = metrics[m].ratio_metric - 1;
> - if (r >= 0 && metrics[r].selected &&
> - metrics[r].val.counter > 0) {
> + r = metrics[m].ratio_metric;
> + /* r == 0 is a special case for run_cnt */
> + ratio = r ? metrics[r - 1].val.counter : profile_total_count;
> + if (metrics[m].ratio_desc && ratio) {
> printf("# %8.2f %-30s",
> - val->counter * metrics[m].ratio_mul /
> - metrics[r].val.counter,
> + val->counter * metrics[m].ratio_mul / ratio,
> metrics[m].ratio_desc);
This isn't a bug, but would a name like 'divisor' or 'denom' read more
naturally here? The variable holds the denominator rather than the
computed ratio, which makes the expression 'val->counter * ... / ratio'
read as if dividing a ratio by a ratio.
The commit message explains the new cycles-per-run output but not the
encoding change that makes it work. This isn't a bug, but would it be
worth a sentence noting that ratio_metric == 0 now means run_cnt (and
that ratio_desc is what gates printing)? Currently readers have to derive
the new encoding from the diff.
The unrelated widening of ratio_mul from float to double is also
unmentioned in the changelog.
---
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/33089436017
^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [PATCH bpf-next] bpftool: Print average cycles per program run in profiler
2026-08-27 15:29 [PATCH bpf-next] bpftool: Print average cycles per program run in profiler Mykyta Yatsenko
2026-08-27 16:22 ` bot+bpf-ci
@ 2026-08-28 0:30 ` Andrii Nakryiko
2026-08-28 11:00 ` Mykyta Yatsenko
2026-08-28 18:46 ` Suchit Karunakaran
2 siblings, 1 reply; 7+ messages in thread
From: Andrii Nakryiko @ 2026-08-28 0:30 UTC (permalink / raw)
To: Mykyta Yatsenko
Cc: bpf, ast, andrii, daniel, kernel-team, eddyz87, memxor, qmo,
Mykyta Yatsenko
On Thu, Aug 27, 2026 at 8:29 AM Mykyta Yatsenko
<mykyta.yatsenko5@gmail.com> wrote:
>
> From: Mykyta Yatsenko <yatsenko@meta.com>
>
> Total cycle counts are difficult to compare across workloads with
> different run counts. Report cycles per run to expose the per-invocation
> cost directly.
>
> Example:
> sudo ./bpftool prog profile name mprog duration 15 cycles instructions
>
> 423256 run_cnt
> 947413975 cycles # 2238.39 cycles per run
> 333965846 instructions # 0.35 insns per cycle
>
> Signed-off-by: Mykyta Yatsenko <yatsenko@meta.com>
> ---
> tools/bpf/bpftool/Documentation/bpftool-prog.rst | 6 ++++--
> tools/bpf/bpftool/prog.c | 18 +++++++++++-------
> 2 files changed, 15 insertions(+), 9 deletions(-)
>
> diff --git a/tools/bpf/bpftool/Documentation/bpftool-prog.rst b/tools/bpf/bpftool/Documentation/bpftool-prog.rst
> index 90fa2a48cc26..2280dc4492c0 100644
> --- a/tools/bpf/bpftool/Documentation/bpftool-prog.rst
> +++ b/tools/bpf/bpftool/Documentation/bpftool-prog.rst
> @@ -217,7 +217,9 @@ bpftool prog run *PROG* data_in *FILE* [data_out *FILE* [data_size_out *L*]] [ct
> bpftool prog profile *PROG* [duration *DURATION*] *METRICs*
> Profile *METRICs* for bpf program *PROG* for *DURATION* seconds or until
> user hits <Ctrl+C>. *DURATION* is optional. If *DURATION* is not specified,
> - the profiling will run up to **UINT_MAX** seconds.
> + the profiling will run up to **UINT_MAX** seconds. When **cycles** is
> + selected, plain output also reports the average number of cycles per
> + program run.
>
> bpftool prog help
> Print short help message.
> @@ -360,7 +362,7 @@ EXAMPLES
> ::
>
> 51397 run_cnt
> - 40176203 cycles (83.05%)
> + 40176203 cycles # 781.68 cycles per run (83.05%)
> 42518139 instructions # 1.06 insns per cycle (83.39%)
> 123 llc_misses # 2.89 LLC misses per million insns (83.15%)
>
> diff --git a/tools/bpf/bpftool/prog.c b/tools/bpf/bpftool/prog.c
> index a9f730d407a9..8935508f955b 100644
> --- a/tools/bpf/bpftool/prog.c
> +++ b/tools/bpf/bpftool/prog.c
> @@ -2069,9 +2069,9 @@ struct profile_metric {
> bool selected;
>
> /* calculate ratios like instructions per cycle */
> - const int ratio_metric; /* 0 for N/A, 1 for index 0 (cycles) */
> + const int ratio_metric; /* 0 for run_cnt, 1 for index 0 (cycles) */
> const char *ratio_desc;
> - const float ratio_mul;
> + const double ratio_mul;
> } metrics[] = {
> {
> .name = "cycles",
> @@ -2080,6 +2080,9 @@ struct profile_metric {
> .config = PERF_COUNT_HW_CPU_CYCLES,
> .exclude_user = 1,
> },
> + .ratio_metric = 0,
> + .ratio_desc = "cycles per run",
> + .ratio_mul = 1.0,
> },
> {
> .name = "instructions",
> @@ -2256,17 +2259,18 @@ static void profile_print_readings_plain(void)
> for (m = 0; m < ARRAY_SIZE(metrics); m++) {
> struct bpf_perf_event_value *val = &metrics[m].val;
> int r;
> + __u64 ratio;
>
> if (!metrics[m].selected)
> continue;
> printf("%18llu %-20s", val->counter, metrics[m].name);
>
> - r = metrics[m].ratio_metric - 1;
> - if (r >= 0 && metrics[r].selected &&
> - metrics[r].val.counter > 0) {
> + r = metrics[m].ratio_metric;
> + /* r == 0 is a special case for run_cnt */
> + ratio = r ? metrics[r - 1].val.counter : profile_total_count;
> + if (metrics[m].ratio_desc && ratio) {
this ratio_desc-based thing looks suspect. We used to check .selected,
why did you change this?
And tbh, this whole ratio_metric would be much better done with enum,
where you can have -1 as "NO_METRIC", -2 as "RUN_CNT", 0 - cycles, 1 -
instructions, and so on.
then in definition of metrics array you can use explicit
[METRIC_CYCLES] = { .name = "cycles", ... },
[METRIC_INSNS] = { .name = "instructions", ..., .ratio_metric = METRIC_CYCLES }
makes everything consistent, explicit, easier to follow, wdyt?
pw-bot: cr
> printf("# %8.2f %-30s",
> - val->counter * metrics[m].ratio_mul /
> - metrics[r].val.counter,
> + val->counter * metrics[m].ratio_mul / ratio,
> metrics[m].ratio_desc);
> } else {
> printf("%-41s", "");
>
> ---
> base-commit: 23ff631b3b8b1452dfe933ee21f96321a9c5e209
> change-id: 20260827-bpftool_cyles_per_run-93169fcb30b7
>
> Best regards,
> --
> Mykyta Yatsenko <yatsenko@meta.com>
>
^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [PATCH bpf-next] bpftool: Print average cycles per program run in profiler
2026-08-28 0:30 ` Andrii Nakryiko
@ 2026-08-28 11:00 ` Mykyta Yatsenko
2026-08-28 16:33 ` Andrii Nakryiko
0 siblings, 1 reply; 7+ messages in thread
From: Mykyta Yatsenko @ 2026-08-28 11:00 UTC (permalink / raw)
To: Andrii Nakryiko
Cc: bpf, ast, andrii, daniel, kernel-team, eddyz87, memxor, qmo,
Mykyta Yatsenko
On 8/28/26 1:30 AM, Andrii Nakryiko wrote:
> On Thu, Aug 27, 2026 at 8:29 AM Mykyta Yatsenko
> <mykyta.yatsenko5@gmail.com> wrote:
>>
>> From: Mykyta Yatsenko <yatsenko@meta.com>
>>
>> Total cycle counts are difficult to compare across workloads with
>> different run counts. Report cycles per run to expose the per-invocation
>> cost directly.
>>
>> Example:
>> sudo ./bpftool prog profile name mprog duration 15 cycles instructions
>>
>> 423256 run_cnt
>> 947413975 cycles # 2238.39 cycles per run
>> 333965846 instructions # 0.35 insns per cycle
>>
>> Signed-off-by: Mykyta Yatsenko <yatsenko@meta.com>
>> ---
>> tools/bpf/bpftool/Documentation/bpftool-prog.rst | 6 ++++--
>> tools/bpf/bpftool/prog.c | 18 +++++++++++-------
>> 2 files changed, 15 insertions(+), 9 deletions(-)
>>
>> diff --git a/tools/bpf/bpftool/Documentation/bpftool-prog.rst b/tools/bpf/bpftool/Documentation/bpftool-prog.rst
>> index 90fa2a48cc26..2280dc4492c0 100644
>> --- a/tools/bpf/bpftool/Documentation/bpftool-prog.rst
>> +++ b/tools/bpf/bpftool/Documentation/bpftool-prog.rst
>> @@ -217,7 +217,9 @@ bpftool prog run *PROG* data_in *FILE* [data_out *FILE* [data_size_out *L*]] [ct
>> bpftool prog profile *PROG* [duration *DURATION*] *METRICs*
>> Profile *METRICs* for bpf program *PROG* for *DURATION* seconds or until
>> user hits <Ctrl+C>. *DURATION* is optional. If *DURATION* is not specified,
>> - the profiling will run up to **UINT_MAX** seconds.
>> + the profiling will run up to **UINT_MAX** seconds. When **cycles** is
>> + selected, plain output also reports the average number of cycles per
>> + program run.
>>
>> bpftool prog help
>> Print short help message.
>> @@ -360,7 +362,7 @@ EXAMPLES
>> ::
>>
>> 51397 run_cnt
>> - 40176203 cycles (83.05%)
>> + 40176203 cycles # 781.68 cycles per run (83.05%)
>> 42518139 instructions # 1.06 insns per cycle (83.39%)
>> 123 llc_misses # 2.89 LLC misses per million insns (83.15%)
>>
>> diff --git a/tools/bpf/bpftool/prog.c b/tools/bpf/bpftool/prog.c
>> index a9f730d407a9..8935508f955b 100644
>> --- a/tools/bpf/bpftool/prog.c
>> +++ b/tools/bpf/bpftool/prog.c
>> @@ -2069,9 +2069,9 @@ struct profile_metric {
>> bool selected;
>>
>> /* calculate ratios like instructions per cycle */
>> - const int ratio_metric; /* 0 for N/A, 1 for index 0 (cycles) */
>> + const int ratio_metric; /* 0 for run_cnt, 1 for index 0 (cycles) */
>> const char *ratio_desc;
>> - const float ratio_mul;
>> + const double ratio_mul;
>> } metrics[] = {
>> {
>> .name = "cycles",
>> @@ -2080,6 +2080,9 @@ struct profile_metric {
>> .config = PERF_COUNT_HW_CPU_CYCLES,
>> .exclude_user = 1,
>> },
>> + .ratio_metric = 0,
>> + .ratio_desc = "cycles per run",
>> + .ratio_mul = 1.0,
>> },
>> {
>> .name = "instructions",
>> @@ -2256,17 +2259,18 @@ static void profile_print_readings_plain(void)
>> for (m = 0; m < ARRAY_SIZE(metrics); m++) {
>> struct bpf_perf_event_value *val = &metrics[m].val;
>> int r;
>> + __u64 ratio;
>>
>> if (!metrics[m].selected)
>> continue;
>> printf("%18llu %-20s", val->counter, metrics[m].name);
>>
>> - r = metrics[m].ratio_metric - 1;
>> - if (r >= 0 && metrics[r].selected &&
>> - metrics[r].val.counter > 0) {
>> + r = metrics[m].ratio_metric;
>> + /* r == 0 is a special case for run_cnt */
>> + ratio = r ? metrics[r - 1].val.counter : profile_total_count;
>> + if (metrics[m].ratio_desc && ratio) {
>
> this ratio_desc-based thing looks suspect. We used to check .selected,
> why did you change this?
We checked .selected on the metrics[r] (denominator metric), with run_cnt,
it does not exist.
Checking ratio for zero, merges 2 checks into onet:
- verify no division by zero
- if ratio is not zero, that metric[r] has to have .selected == true,
otherwise how did we bump it.
>
> And tbh, this whole ratio_metric would be much better done with enum,
> where you can have -1 as "NO_METRIC", -2 as "RUN_CNT", 0 - cycles, 1 -
> instructions, and so on.
That'll do. But feels a bit awkward:
metrics[] = {
...
{
...
.ratio_metric = 1, /* But really mean METRIC_CYCLES which is index 0 */
},
{
.ratio_metric = -1 /* But really mean METRIC_RUN_CNT which is -2 */
}
}
The core difficulty here is that .ratio_metric default initializes with 0 and
stands for NO_METRIC, then all indexes in .ratio_metric are shifted by one.
Alternatively we can explicitly set .ratio_metric for every element, but
that makes default initialized not safe (.ratio_metric == 0 means cycles, but
.ratio_desc is NULL).
>
> then in definition of metrics array you can use explicit
>
> [METRIC_CYCLES] = { .name = "cycles", ... },
> [METRIC_INSNS] = { .name = "instructions", ..., .ratio_metric = METRIC_CYCLES }
>
>
> makes everything consistent, explicit, easier to follow, wdyt?
>
> pw-bot: cr
>
>
>> printf("# %8.2f %-30s",
>> - val->counter * metrics[m].ratio_mul /
>> - metrics[r].val.counter,
>> + val->counter * metrics[m].ratio_mul / ratio,
>> metrics[m].ratio_desc);
>> } else {
>> printf("%-41s", "");
>>
>> ---
>> base-commit: 23ff631b3b8b1452dfe933ee21f96321a9c5e209
>> change-id: 20260827-bpftool_cyles_per_run-93169fcb30b7
>>
>> Best regards,
>> --
>> Mykyta Yatsenko <yatsenko@meta.com>
>>
^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [PATCH bpf-next] bpftool: Print average cycles per program run in profiler
2026-08-28 11:00 ` Mykyta Yatsenko
@ 2026-08-28 16:33 ` Andrii Nakryiko
2026-08-28 17:11 ` Mykyta Yatsenko
0 siblings, 1 reply; 7+ messages in thread
From: Andrii Nakryiko @ 2026-08-28 16:33 UTC (permalink / raw)
To: Mykyta Yatsenko
Cc: bpf, ast, andrii, daniel, kernel-team, eddyz87, memxor, qmo,
Mykyta Yatsenko
On Fri, Aug 28, 2026 at 4:01 AM Mykyta Yatsenko
<mykyta.yatsenko5@gmail.com> wrote:
>
>
>
> On 8/28/26 1:30 AM, Andrii Nakryiko wrote:
> > On Thu, Aug 27, 2026 at 8:29 AM Mykyta Yatsenko
> > <mykyta.yatsenko5@gmail.com> wrote:
> >>
> >> From: Mykyta Yatsenko <yatsenko@meta.com>
> >>
> >> Total cycle counts are difficult to compare across workloads with
> >> different run counts. Report cycles per run to expose the per-invocation
> >> cost directly.
> >>
> >> Example:
> >> sudo ./bpftool prog profile name mprog duration 15 cycles instructions
> >>
> >> 423256 run_cnt
> >> 947413975 cycles # 2238.39 cycles per run
> >> 333965846 instructions # 0.35 insns per cycle
> >>
> >> Signed-off-by: Mykyta Yatsenko <yatsenko@meta.com>
> >> ---
> >> tools/bpf/bpftool/Documentation/bpftool-prog.rst | 6 ++++--
> >> tools/bpf/bpftool/prog.c | 18 +++++++++++-------
> >> 2 files changed, 15 insertions(+), 9 deletions(-)
> >>
> >> diff --git a/tools/bpf/bpftool/Documentation/bpftool-prog.rst b/tools/bpf/bpftool/Documentation/bpftool-prog.rst
> >> index 90fa2a48cc26..2280dc4492c0 100644
> >> --- a/tools/bpf/bpftool/Documentation/bpftool-prog.rst
> >> +++ b/tools/bpf/bpftool/Documentation/bpftool-prog.rst
> >> @@ -217,7 +217,9 @@ bpftool prog run *PROG* data_in *FILE* [data_out *FILE* [data_size_out *L*]] [ct
> >> bpftool prog profile *PROG* [duration *DURATION*] *METRICs*
> >> Profile *METRICs* for bpf program *PROG* for *DURATION* seconds or until
> >> user hits <Ctrl+C>. *DURATION* is optional. If *DURATION* is not specified,
> >> - the profiling will run up to **UINT_MAX** seconds.
> >> + the profiling will run up to **UINT_MAX** seconds. When **cycles** is
> >> + selected, plain output also reports the average number of cycles per
> >> + program run.
> >>
> >> bpftool prog help
> >> Print short help message.
> >> @@ -360,7 +362,7 @@ EXAMPLES
> >> ::
> >>
> >> 51397 run_cnt
> >> - 40176203 cycles (83.05%)
> >> + 40176203 cycles # 781.68 cycles per run (83.05%)
> >> 42518139 instructions # 1.06 insns per cycle (83.39%)
> >> 123 llc_misses # 2.89 LLC misses per million insns (83.15%)
> >>
> >> diff --git a/tools/bpf/bpftool/prog.c b/tools/bpf/bpftool/prog.c
> >> index a9f730d407a9..8935508f955b 100644
> >> --- a/tools/bpf/bpftool/prog.c
> >> +++ b/tools/bpf/bpftool/prog.c
> >> @@ -2069,9 +2069,9 @@ struct profile_metric {
> >> bool selected;
> >>
> >> /* calculate ratios like instructions per cycle */
> >> - const int ratio_metric; /* 0 for N/A, 1 for index 0 (cycles) */
> >> + const int ratio_metric; /* 0 for run_cnt, 1 for index 0 (cycles) */
> >> const char *ratio_desc;
> >> - const float ratio_mul;
> >> + const double ratio_mul;
> >> } metrics[] = {
> >> {
> >> .name = "cycles",
> >> @@ -2080,6 +2080,9 @@ struct profile_metric {
> >> .config = PERF_COUNT_HW_CPU_CYCLES,
> >> .exclude_user = 1,
> >> },
> >> + .ratio_metric = 0,
> >> + .ratio_desc = "cycles per run",
> >> + .ratio_mul = 1.0,
> >> },
> >> {
> >> .name = "instructions",
> >> @@ -2256,17 +2259,18 @@ static void profile_print_readings_plain(void)
> >> for (m = 0; m < ARRAY_SIZE(metrics); m++) {
> >> struct bpf_perf_event_value *val = &metrics[m].val;
> >> int r;
> >> + __u64 ratio;
> >>
> >> if (!metrics[m].selected)
> >> continue;
> >> printf("%18llu %-20s", val->counter, metrics[m].name);
> >>
> >> - r = metrics[m].ratio_metric - 1;
> >> - if (r >= 0 && metrics[r].selected &&
> >> - metrics[r].val.counter > 0) {
> >> + r = metrics[m].ratio_metric;
> >> + /* r == 0 is a special case for run_cnt */
> >> + ratio = r ? metrics[r - 1].val.counter : profile_total_count;
> >> + if (metrics[m].ratio_desc && ratio) {
> >
> > this ratio_desc-based thing looks suspect. We used to check .selected,
> > why did you change this?
>
> We checked .selected on the metrics[r] (denominator metric), with run_cnt,
> it does not exist.
> Checking ratio for zero, merges 2 checks into onet:
> - verify no division by zero
> - if ratio is not zero, that metric[r] has to have .selected == true,
> otherwise how did we bump it.
ok, makes sense, thanks for explaining!
> >
> > And tbh, this whole ratio_metric would be much better done with enum,
> > where you can have -1 as "NO_METRIC", -2 as "RUN_CNT", 0 - cycles, 1 -
> > instructions, and so on.
>
> That'll do. But feels a bit awkward:
> metrics[] = {
> ...
> {
> ...
> .ratio_metric = 1, /* But really mean METRIC_CYCLES which is index 0 */
> },
> {
> .ratio_metric = -1 /* But really mean METRIC_RUN_CNT which is -2 */
> }
> }
> The core difficulty here is that .ratio_metric default initializes with 0 and
> stands for NO_METRIC, then all indexes in .ratio_metric are shifted by one.
> Alternatively we can explicitly set .ratio_metric for every element, but
> that makes default initialized not safe (.ratio_metric == 0 means cycles, but
> .ratio_desc is NULL).
I personally think that explicit .ratio_metric = METRIC_NO_METRIC or
something like that is just fine to do and not a problem.
But if that's a problem, I'd still do enum, just make zero a "NO
METRIC", and shift everything else by one. So basically what we have
today, but explicitly named (and with small comment next to enum we
can explain that shift-by-one convention).
Your choice, my point is that plain numbers make it hard to follow
what's going on, and really here we have a limited set of explicitly
connected things, so enum is the way, IMO.
> >
> > then in definition of metrics array you can use explicit
> >
> > [METRIC_CYCLES] = { .name = "cycles", ... },
> > [METRIC_INSNS] = { .name = "instructions", ..., .ratio_metric = METRIC_CYCLES }
> >
> >
> > makes everything consistent, explicit, easier to follow, wdyt?
> >
> > pw-bot: cr
> >
> >
> >> printf("# %8.2f %-30s",
> >> - val->counter * metrics[m].ratio_mul /
> >> - metrics[r].val.counter,
> >> + val->counter * metrics[m].ratio_mul / ratio,
> >> metrics[m].ratio_desc);
> >> } else {
> >> printf("%-41s", "");
> >>
> >> ---
> >> base-commit: 23ff631b3b8b1452dfe933ee21f96321a9c5e209
> >> change-id: 20260827-bpftool_cyles_per_run-93169fcb30b7
> >>
> >> Best regards,
> >> --
> >> Mykyta Yatsenko <yatsenko@meta.com>
> >>
>
^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [PATCH bpf-next] bpftool: Print average cycles per program run in profiler
2026-08-28 16:33 ` Andrii Nakryiko
@ 2026-08-28 17:11 ` Mykyta Yatsenko
0 siblings, 0 replies; 7+ messages in thread
From: Mykyta Yatsenko @ 2026-08-28 17:11 UTC (permalink / raw)
To: Andrii Nakryiko
Cc: bpf, ast, andrii, daniel, kernel-team, eddyz87, memxor, qmo,
Mykyta Yatsenko
On 8/28/26 5:33 PM, Andrii Nakryiko wrote:
> On Fri, Aug 28, 2026 at 4:01 AM Mykyta Yatsenko
> <mykyta.yatsenko5@gmail.com> wrote:
>>
>>
>>
>> On 8/28/26 1:30 AM, Andrii Nakryiko wrote:
>>> On Thu, Aug 27, 2026 at 8:29 AM Mykyta Yatsenko
>>> <mykyta.yatsenko5@gmail.com> wrote:
>>>>
>>>> From: Mykyta Yatsenko <yatsenko@meta.com>
>>>>
>>>> Total cycle counts are difficult to compare across workloads with
>>>> different run counts. Report cycles per run to expose the per-invocation
>>>> cost directly.
>>>>
>>>> Example:
>>>> sudo ./bpftool prog profile name mprog duration 15 cycles instructions
>>>>
>>>> 423256 run_cnt
>>>> 947413975 cycles # 2238.39 cycles per run
>>>> 333965846 instructions # 0.35 insns per cycle
>>>>
>>>> Signed-off-by: Mykyta Yatsenko <yatsenko@meta.com>
>>>> ---
>>>> tools/bpf/bpftool/Documentation/bpftool-prog.rst | 6 ++++--
>>>> tools/bpf/bpftool/prog.c | 18 +++++++++++-------
>>>> 2 files changed, 15 insertions(+), 9 deletions(-)
>>>>
>>>> diff --git a/tools/bpf/bpftool/Documentation/bpftool-prog.rst b/tools/bpf/bpftool/Documentation/bpftool-prog.rst
>>>> index 90fa2a48cc26..2280dc4492c0 100644
>>>> --- a/tools/bpf/bpftool/Documentation/bpftool-prog.rst
>>>> +++ b/tools/bpf/bpftool/Documentation/bpftool-prog.rst
>>>> @@ -217,7 +217,9 @@ bpftool prog run *PROG* data_in *FILE* [data_out *FILE* [data_size_out *L*]] [ct
>>>> bpftool prog profile *PROG* [duration *DURATION*] *METRICs*
>>>> Profile *METRICs* for bpf program *PROG* for *DURATION* seconds or until
>>>> user hits <Ctrl+C>. *DURATION* is optional. If *DURATION* is not specified,
>>>> - the profiling will run up to **UINT_MAX** seconds.
>>>> + the profiling will run up to **UINT_MAX** seconds. When **cycles** is
>>>> + selected, plain output also reports the average number of cycles per
>>>> + program run.
>>>>
>>>> bpftool prog help
>>>> Print short help message.
>>>> @@ -360,7 +362,7 @@ EXAMPLES
>>>> ::
>>>>
>>>> 51397 run_cnt
>>>> - 40176203 cycles (83.05%)
>>>> + 40176203 cycles # 781.68 cycles per run (83.05%)
>>>> 42518139 instructions # 1.06 insns per cycle (83.39%)
>>>> 123 llc_misses # 2.89 LLC misses per million insns (83.15%)
>>>>
>>>> diff --git a/tools/bpf/bpftool/prog.c b/tools/bpf/bpftool/prog.c
>>>> index a9f730d407a9..8935508f955b 100644
>>>> --- a/tools/bpf/bpftool/prog.c
>>>> +++ b/tools/bpf/bpftool/prog.c
>>>> @@ -2069,9 +2069,9 @@ struct profile_metric {
>>>> bool selected;
>>>>
>>>> /* calculate ratios like instructions per cycle */
>>>> - const int ratio_metric; /* 0 for N/A, 1 for index 0 (cycles) */
>>>> + const int ratio_metric; /* 0 for run_cnt, 1 for index 0 (cycles) */
>>>> const char *ratio_desc;
>>>> - const float ratio_mul;
>>>> + const double ratio_mul;
>>>> } metrics[] = {
>>>> {
>>>> .name = "cycles",
>>>> @@ -2080,6 +2080,9 @@ struct profile_metric {
>>>> .config = PERF_COUNT_HW_CPU_CYCLES,
>>>> .exclude_user = 1,
>>>> },
>>>> + .ratio_metric = 0,
>>>> + .ratio_desc = "cycles per run",
>>>> + .ratio_mul = 1.0,
>>>> },
>>>> {
>>>> .name = "instructions",
>>>> @@ -2256,17 +2259,18 @@ static void profile_print_readings_plain(void)
>>>> for (m = 0; m < ARRAY_SIZE(metrics); m++) {
>>>> struct bpf_perf_event_value *val = &metrics[m].val;
>>>> int r;
>>>> + __u64 ratio;
>>>>
>>>> if (!metrics[m].selected)
>>>> continue;
>>>> printf("%18llu %-20s", val->counter, metrics[m].name);
>>>>
>>>> - r = metrics[m].ratio_metric - 1;
>>>> - if (r >= 0 && metrics[r].selected &&
>>>> - metrics[r].val.counter > 0) {
>>>> + r = metrics[m].ratio_metric;
>>>> + /* r == 0 is a special case for run_cnt */
>>>> + ratio = r ? metrics[r - 1].val.counter : profile_total_count;
>>>> + if (metrics[m].ratio_desc && ratio) {
>>>
>>> this ratio_desc-based thing looks suspect. We used to check .selected,
>>> why did you change this?
>>
>> We checked .selected on the metrics[r] (denominator metric), with run_cnt,
>> it does not exist.
>> Checking ratio for zero, merges 2 checks into onet:
>> - verify no division by zero
>> - if ratio is not zero, that metric[r] has to have .selected == true,
>> otherwise how did we bump it.
>
> ok, makes sense, thanks for explaining!
>
>>>
>>> And tbh, this whole ratio_metric would be much better done with enum,
>>> where you can have -1 as "NO_METRIC", -2 as "RUN_CNT", 0 - cycles, 1 -
>>> instructions, and so on.
>>
>> That'll do. But feels a bit awkward:
>> metrics[] = {
>> ...
>> {
>> ...
>> .ratio_metric = 1, /* But really mean METRIC_CYCLES which is index 0 */
>> },
>> {
>> .ratio_metric = -1 /* But really mean METRIC_RUN_CNT which is -2 */
>> }
>> }
>> The core difficulty here is that .ratio_metric default initializes with 0 and
>> stands for NO_METRIC, then all indexes in .ratio_metric are shifted by one.
>> Alternatively we can explicitly set .ratio_metric for every element, but
>> that makes default initialized not safe (.ratio_metric == 0 means cycles, but
>> .ratio_desc is NULL).
>
> I personally think that explicit .ratio_metric = METRIC_NO_METRIC or
> something like that is just fine to do and not a problem.
>
> But if that's a problem, I'd still do enum, just make zero a "NO
> METRIC", and shift everything else by one. So basically what we have
> today, but explicitly named (and with small comment next to enum we
> can explain that shift-by-one convention).
>
> Your choice, my point is that plain numbers make it hard to follow
> what's going on, and really here we have a limited set of explicitly
> connected things, so enum is the way, IMO.
>
I'll respin with enum, thanks to taking a look.
>
>>>
>>> then in definition of metrics array you can use explicit
>>>
>>> [METRIC_CYCLES] = { .name = "cycles", ... },
>>> [METRIC_INSNS] = { .name = "instructions", ..., .ratio_metric = METRIC_CYCLES }
>>>
>>>
>>> makes everything consistent, explicit, easier to follow, wdyt?
>>>
>>> pw-bot: cr
>>>
>>>
>>>> printf("# %8.2f %-30s",
>>>> - val->counter * metrics[m].ratio_mul /
>>>> - metrics[r].val.counter,
>>>> + val->counter * metrics[m].ratio_mul / ratio,
>>>> metrics[m].ratio_desc);
>>>> } else {
>>>> printf("%-41s", "");
>>>>
>>>> ---
>>>> base-commit: 23ff631b3b8b1452dfe933ee21f96321a9c5e209
>>>> change-id: 20260827-bpftool_cyles_per_run-93169fcb30b7
>>>>
>>>> Best regards,
>>>> --
>>>> Mykyta Yatsenko <yatsenko@meta.com>
>>>>
>>
^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [PATCH bpf-next] bpftool: Print average cycles per program run in profiler
2026-08-27 15:29 [PATCH bpf-next] bpftool: Print average cycles per program run in profiler Mykyta Yatsenko
2026-08-27 16:22 ` bot+bpf-ci
2026-08-28 0:30 ` Andrii Nakryiko
@ 2026-08-28 18:46 ` Suchit Karunakaran
2 siblings, 0 replies; 7+ messages in thread
From: Suchit Karunakaran @ 2026-08-28 18:46 UTC (permalink / raw)
To: Mykyta Yatsenko, bpf, ast, andrii, daniel, kernel-team, eddyz87,
memxor, qmo
Cc: Mykyta Yatsenko
Hi,
On 8/27/26 8:59 PM, Mykyta Yatsenko wrote:
> From: Mykyta Yatsenko <yatsenko@meta.com>
>
> Total cycle counts are difficult to compare across workloads with
> different run counts. Report cycles per run to expose the per-invocation
> cost directly.
>
> Example:
> sudo ./bpftool prog profile name mprog duration 15 cycles instructions
>
> 423256 run_cnt
> 947413975 cycles # 2238.39 cycles per run
> 333965846 instructions # 0.35 insns per cycle
>
> Signed-off-by: Mykyta Yatsenko <yatsenko@meta.com>
> ---
> tools/bpf/bpftool/Documentation/bpftool-prog.rst | 6 ++++--
> tools/bpf/bpftool/prog.c | 18 +++++++++++-------
> 2 files changed, 15 insertions(+), 9 deletions(-)
>
> diff --git a/tools/bpf/bpftool/Documentation/bpftool-prog.rst b/tools/bpf/bpftool/Documentation/bpftool-prog.rst
> index 90fa2a48cc26..2280dc4492c0 100644
> --- a/tools/bpf/bpftool/Documentation/bpftool-prog.rst
> +++ b/tools/bpf/bpftool/Documentation/bpftool-prog.rst
> @@ -217,7 +217,9 @@ bpftool prog run *PROG* data_in *FILE* [data_out *FILE* [data_size_out *L*]] [ct
> bpftool prog profile *PROG* [duration *DURATION*] *METRICs*
> Profile *METRICs* for bpf program *PROG* for *DURATION* seconds or until
> user hits <Ctrl+C>. *DURATION* is optional. If *DURATION* is not specified,
> - the profiling will run up to **UINT_MAX** seconds.
> + the profiling will run up to **UINT_MAX** seconds. When **cycles** is
> + selected, plain output also reports the average number of cycles per
> + program run.
>
> bpftool prog help
> Print short help message.
> @@ -360,7 +362,7 @@ EXAMPLES
> ::
>
> 51397 run_cnt
> - 40176203 cycles (83.05%)
> + 40176203 cycles # 781.68 cycles per run (83.05%)
> 42518139 instructions # 1.06 insns per cycle (83.39%)
> 123 llc_misses # 2.89 LLC misses per million insns (83.15%)
>
> diff --git a/tools/bpf/bpftool/prog.c b/tools/bpf/bpftool/prog.c
> index a9f730d407a9..8935508f955b 100644
> --- a/tools/bpf/bpftool/prog.c
> +++ b/tools/bpf/bpftool/prog.c
> @@ -2069,9 +2069,9 @@ struct profile_metric {
> bool selected;
>
> /* calculate ratios like instructions per cycle */
> - const int ratio_metric; /* 0 for N/A, 1 for index 0 (cycles) */
> + const int ratio_metric; /* 0 for run_cnt, 1 for index 0 (cycles) */
> const char *ratio_desc;
> - const float ratio_mul;
> + const double ratio_mul;
> } metrics[] = {
> {
> .name = "cycles",
> @@ -2080,6 +2080,9 @@ struct profile_metric {
> .config = PERF_COUNT_HW_CPU_CYCLES,
> .exclude_user = 1,
> },
> + .ratio_metric = 0,
> + .ratio_desc = "cycles per run",
> + .ratio_mul = 1.0,
> },
> {
> .name = "instructions",
> @@ -2256,17 +2259,18 @@ static void profile_print_readings_plain(void)
> for (m = 0; m < ARRAY_SIZE(metrics); m++) {
> struct bpf_perf_event_value *val = &metrics[m].val;
> int r;
> + __u64 ratio;
>
> if (!metrics[m].selected)
> continue;
> printf("%18llu %-20s", val->counter, metrics[m].name);
>
> - r = metrics[m].ratio_metric - 1;
> - if (r >= 0 && metrics[r].selected &&
> - metrics[r].val.counter > 0) {
> + r = metrics[m].ratio_metric;
> + /* r == 0 is a special case for run_cnt */
> + ratio = r ? metrics[r - 1].val.counter : profile_total_count;
> + if (metrics[m].ratio_desc && ratio) {
> printf("# %8.2f %-30s",
> - val->counter * metrics[m].ratio_mul /
> - metrics[r].val.counter,
> + val->counter * metrics[m].ratio_mul / ratio,
> metrics[m].ratio_desc);
> } else {
> printf("%-41s", "");
>
> ---
> base-commit: 23ff631b3b8b1452dfe933ee21f96321a9c5e209
> change-id: 20260827-bpftool_cyles_per_run-93169fcb30b7
>
> Best regards,
> --
> Mykyta Yatsenko <yatsenko@meta.com>
>
I think val->counter be normalized for counter multiplexing before
dividing by profile_total_count? val->counter contains only the cycles
observed while the event was actively scheduled on the PMU, whereas
profile_total_count reflects all program invocations across the full
duration. When val->running < val->enabled, dividing raw val->counter by
profile_total_count undercounts the true cycles per run. Should we apply
scaling before computing the ratio like below?
if (val->running > 0) {
double scaled = (double)val->counter * val->enabled / val->running;
double cycles_per_run = scaled / profile_total_count;
}
We can actually see this in the example:
51397 run_cnt
40176203 cycles # 781.68 cycles per run (83.05%)
Here, 781.68 uses the raw unscaled count during the 83.05% runtime
window. If we scale it to 100% estimated coverage, the true cost is
closer to ~941.22 cycles per run.
^ permalink raw reply [flat|nested] 7+ messages in thread
end of thread, other threads:[~2026-08-28 18:46 UTC | newest]
Thread overview: 7+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-08-27 15:29 [PATCH bpf-next] bpftool: Print average cycles per program run in profiler Mykyta Yatsenko
2026-08-27 16:22 ` bot+bpf-ci
2026-08-28 0:30 ` Andrii Nakryiko
2026-08-28 11:00 ` Mykyta Yatsenko
2026-08-28 16:33 ` Andrii Nakryiko
2026-08-28 17:11 ` Mykyta Yatsenko
2026-08-28 18:46 ` Suchit Karunakaran
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox