From: Shrikanth Hegde <sshegde@linux.ibm.com>
To: Yury Norov <ynorov@nvidia.com>
Cc: linux-kernel@vger.kernel.org, mingo@kernel.org,
peterz@infradead.org, juri.lelli@redhat.com,
vincent.guittot@linaro.org, yury.norov@gmail.com,
kprateek.nayak@amd.com, iii@linux.ibm.com, corbet@lwn.net,
tglx@kernel.org, gregkh@linuxfoundation.org, pbonzini@redhat.com,
seanjc@google.com, vschneid@redhat.com, huschle@linux.ibm.com,
rostedt@goodmis.org, dietmar.eggemann@arm.com,
maddy@linux.ibm.com, srikar@linux.ibm.com, hdanton@sina.com,
chleroy@kernel.org, vineeth@bitbyteword.org, frederic@kernel.org,
arighi@nvidia.com, pauld@redhat.com, christian.loehle@arm.com,
tj@kernel.org, tommaso.cucinotta@gmail.com, maz@kernel.org,
rafael@kernel.org, rdunlap@infradead.org, kernellwp@gmail.com,
linux-doc@vger.kernel.org, jgross@suse.com,
virtualization@lists.linux.dev
Subject: Re: [PATCH v8 10/11] virt/steal_governor: Implement steal_governor policy loop
Date: Wed, 22 Jul 2026 12:44:03 +0530 [thread overview]
Message-ID: <c64ed18e-7863-44c3-9c7b-2f1efd1a805e@linux.ibm.com> (raw)
In-Reply-To: <al_IbeAa3sDrt9A7@yury>
Hi Yury.
On 7/22/26 12:58 AM, Yury Norov wrote:
> On Mon, Jul 20, 2026 at 10:52:49PM +0530, Shrikanth Hegde wrote:
[...]
>> +/*
>> + * Returns steal time of the full system.
>> + * Compute collective steal time across all possible CPUs.
>> + */
>> +static u64 get_system_steal_time(void)
>> +{
>> + int cpu;
>> + u64 total_steal = 0;
>> +
>> + for_each_possible_cpu(cpu)
>> + total_steal += kcpustat_cpu(cpu).cpustat[CPUTIME_STEAL];
>> +
>> + return total_steal;
>> +}
>
> There's another implementation of the same logic in
> hd_calculate_steal_percentage())
>
> It means it should live in include/linux/kernel_stat.h as:
>
> u64 kcpustat_steal_time(struct cpumask *cpus);
>
> Or possibly even more generic:
>
> u64 kcpustat_field_total(enum cpu_usage_stat usage, struct cpumask *cpus);
That's good idea. Below are the callsites i think can be
refactored in addition to this patch.
arch/s390/kernel/hiperdispatch.c: hd_calculate_steal_percentage
fs/proc/uptime.c: uptime_proc_show
I can pick up this refactoring post the series. I think it is better
not to club them together. is that ok?
>
> Similarly to the existing kcpustat_field().
>
> The other possible candidates are: fs/proc/stat.c:: show_stat()
> arch_cpu_idle_time(), but I think it's out of the scope of your
> series.
Yes.
drivers/leds/trigger/ledtrig-activity.c: led_activity_function
fs/proc/stat.c: show_stat
These two sum up multiple CPUTIME_ fields. Doing them in one loop is
likely better than getting each of them in a separate loop. No?
>
> What's the relation between the arch/s390/kernel/hiperdispatch.c and
> your steal governor? Is that a similar concept?
>
It is similar but independent idea. Ilya explained it in brief below.
https://youtu.be/adxUKFPlOp0?t=1846
Tobais, Ilya, Correct me if i am wrong.
- It samples steal time and decides on CPU_CAPACITY of a
CPU (between 1024 and 126) periodically.
- Triggers sched domain rebuild.
But Using CPU_CAPACITY to ensure no task running has drawbacks as we had
explored it very early.
- It fails under high concurrency. Typical real life workloads spawns
threads/tasks based on online CPUs. Scheduler chooses low capacity
CPUs instead of running them on busy CPUs. So under high concurrency or
high utilization it simply doesn't work.
- It takes a long time to get the running task out of it.
- It needs a sched domain rebuild. That is costly
(One can play tricks to avoid it)
- There are PR/SM overheads.
Since S390 has explicit knowledge of which CPUs to take out, it will likely
want a arch specific mechanism in steal_governor to take out Vertical Low CPUs
first. That is being deferred for now, once s390 has that, my take is
hiperdispatch can be removed altogether.
>> +/*
>> + * Returns number of CPUs to consider for steal ratio.
>> + * Return possible CPUs.
>> + */
>
> Can you rephrase the comment? It has 2 'return' sections with
> different meaning. If the 2nd one is the implementation detail,
> I'd put it inside the function scope, or drop entirely.
>
Ok. Let me drop second statement there.
>> +static unsigned int get_num_cpus_steal_ratio(void)
>> +{
>> + return num_possible_cpus();
>> +}
>> +
>> +/*
>> + * Take action to decrease preferred CPUs.
>> + *
>
> Drop this 'Take action' wording please.
Ok.
>
>> + * Decrease the preferred CPUs by 1 core.
>> + * Take out the last core in the active & preferred.
>> + *
>> + * Must ensure
>> + * - least one housekeeping core is always kept as preferred
>
> s/least/at least/ ?
Yep.
>
>> + * - preferred is always subset of active.
>> + */
>> +static void decrease_preferred_cpus(void)
>> +{
>> + int tmp_cpu, first_hk_cpu, last_cpu;
>> + const struct cpumask *first_hk_core;
>> + int target_cpu = nr_cpu_ids;
>> +
>> + guard(cpus_read_lock)();
>> + first_hk_cpu = cpumask_first_and(housekeeping_cpumask(HK_TYPE_KERNEL_NOISE),
>> + cpu_preferred_mask);
>> + if (first_hk_cpu >= nr_cpu_ids)
>> + return;
>> +
>> + last_cpu = cpumask_last(cpu_preferred_mask);
>> +
>> + if (last_cpu >= nr_cpu_ids)
>> + return;
>> +
>> + /* Always leave first housekeeping core as preferred. */
>> + first_hk_core = topology_sibling_cpumask(first_hk_cpu);
>> +
>> + /* Find the last CPU which doesn't belong to that first hk_core. */
>> + if (!cpumask_test_cpu(last_cpu, first_hk_core)) {
>> + target_cpu = last_cpu;
>> + } else {
>> + for_each_cpu_andnot(tmp_cpu, cpu_preferred_mask, first_hk_core)
>> + target_cpu = tmp_cpu;
>> + }
>
> Too much local variables. You can drop those tmp_cpu, last_cpu and
> target_cpu, and just use a single variable 'cpu'. That would also
> simplify your logic:
>
> cpu = cpumask_last(cpu_preferred_mask);
> core = topology_sibling_cpumask(first_hk_cpu);
>
> if (cpumask_test_cpu(cpu, core)) {
> for_each_cpu_andnot(cpu, cpu_preferred_mask, core)
> /* nop */ ;
> }
>
> if (cpu >= nr_cpu_ids)
> return;
>
> And so on.
Ok. Just two would suffice i think. cpu, and target_cpu.
With a bit shuffling first_hk_cpu can also go away.
I need at least two so that this loop works.
for_each_cpu_and(tmp_cpu, topology_sibling_cpumask(target_cpu),
cpu_preferred_mask)
set_cpu_preferred(tmp_cpu, false);
>
>> +
>> + /* Only the first housekeeping core remains */
>> + if (target_cpu >= nr_cpu_ids)
>> + return;
>> +
>> + for_each_cpu_and(tmp_cpu, topology_sibling_cpumask(target_cpu),
>> + cpu_preferred_mask)
>> + set_cpu_preferred(tmp_cpu, false);
>> +}
>> +
>> +/*
>> + * Take action to increase preferred CPUs.
>> + *
>
> Again, drop this 'take action' thing.
Sure.
>
>> + * Increase the preferred CPUs by 1 core.
>> + * Add the first core in active & !preferred
>> + *
>> + * Must ensure preferred is subset of active.
>> + */
>> +static void increase_preferred_cpus(void)
>> +{
>> + int first_cpu, tmp_cpu;
>> +
>> + guard(cpus_read_lock)();
>> + first_cpu = cpumask_first_andnot(cpu_active_mask, cpu_preferred_mask);
>> +
>> + /* All CPUs are preferred. Nothing to increase further */
>> + if (first_cpu >= nr_cpu_ids)
>> + return;
>> +
>> + for_each_cpu_and(tmp_cpu, topology_sibling_cpumask(first_cpu),
>> + cpu_active_mask)
>> + set_cpu_preferred(tmp_cpu, true);
>> +}
>> +
>> +static void compute_preferred_cpus_work(struct work_struct *work)
>> +{
>> + u64 curr_steal, delta_steal, delta_ns, steal_ratio;
>> + ktime_t now;
>> +
>> + now = ktime_get();
>> + delta_ns = ktime_to_ns(ktime_sub(now, sg_core_ctx.time));
>> +
>> + if (unlikely(delta_ns < NSEC_PER_MSEC)) {
>> + pr_err_ratelimited("steal_governor: work scheduled too soon delta_ns: %llu\n",
>> + delta_ns);
>> + goto requeue_work;
>> + }
>> +
>> + curr_steal = get_system_steal_time();
>> + delta_steal = curr_steal > sg_core_ctx.steal ?
>> + curr_steal - sg_core_ctx.steal : 0;
>> +
>> + /* Update for next calculation */
>> + sg_core_ctx.steal = curr_steal;
>> + sg_core_ctx.time = now;
>> +
>> + /*
>> + * steal_ratio = (delta_steal * 100*100)/(delta_ns * num_cpus())
>> + * To avoid possible overflow, divide the denominator early.
>> + * Note minimum interval is 100ms.
>> + */
>> + delta_ns = max_t(u64, div_u64(delta_ns * get_num_cpus_steal_ratio(),
>> + 100 * 100), 1);
>> + steal_ratio = div64_u64(delta_steal, delta_ns);
>> +
>> + if (steal_ratio > sg_core_ctx.high_threshold)
>> + decrease_preferred_cpus();
>> + if (steal_ratio <= sg_core_ctx.low_threshold)
>> + increase_preferred_cpus();
>
> If you neither increase, nor decrease, you don't need to check the mask
> because you know you don't modify it. Also, I'd wrap the below integrity
> checks into a helper function.
>
> if (steal_ratio > sg_core_ctx.high_threshold)
> decrease_preferred_cpus();
> else if (steal_ratio <= sg_core_ctx.low_threshold)
> increase_preferred_cpus();
> else
> goto requeue_work;
>
> if (check_integrity())
> return;
>
Ack. Good catch. Will do.
>> + /* maintain design constructs always */
>> + if (cpumask_empty(cpu_preferred_mask)) {
>> + pr_err("empty preferred mask. stop steal governor\n");
>> + restore_preferred_to_active();
>> + return;
>> + }
>> +
>> + if (!cpumask_subset(cpu_preferred_mask, cpu_active_mask)) {
>> + pr_err("preferred: %*pbl is not subset of active: %*pbl, stop steal governor\n",
>> + cpumask_pr_args(cpu_preferred_mask),
>> + cpumask_pr_args(cpu_active_mask));
>> + restore_preferred_to_active();
>> + return;
>> + }
>> +
>> +requeue_work:
>> + /* Trigger for next sampling */
>
> The lablel above is pretty explaining to me. The comment just
> duplicates it. Maybe drop the comment?
Ok.
>
>> + schedule_delayed_work(&sg_core_ctx.work,
>> + msecs_to_jiffies(sg_core_ctx.interval_ms));
>
> If you need jiffies, why don't you have them in the structure, instead of
> milliseconds?
>
> schedule_delayed_work(&sg_core_ctx.work, sg_core_ctx.delay);
>
I would prefer milliseconds as jiffies is very difficult for users to understand.
It depends on HZ value and one has to query from configs.
HZ can very from 100 to 1000 today. Again I will have to play tricks to schedule
the governor at fixed intervals.
So i think it is better to use milliseconds here.
Correct me if i am not making sense.
>> +}
>> +
>> static int __init steal_governor_init(void)
>> {
>> if (sg_core_ctx.low_threshold >= sg_core_ctx.high_threshold) {
>> @@ -100,11 +252,19 @@ static int __init steal_governor_init(void)
>> pr_info("steal_governor is enabled. interval: %ums, high_threshold: %u, low_threshold: %u\n",
>> sg_core_ctx.interval_ms, sg_core_ctx.high_threshold, sg_core_ctx.low_threshold);
>>
>> + INIT_DELAYED_WORK(&sg_core_ctx.work, compute_preferred_cpus_work);
>> + sg_core_ctx.steal = get_system_steal_time();
>> + sg_core_ctx.time = ktime_get();
>> +
>> + schedule_delayed_work(&sg_core_ctx.work,
>> + msecs_to_jiffies(sg_core_ctx.interval_ms));
>> +
>> return 0;
>> }
>>
>> static void __exit steal_governor_exit(void)
>> {
>> + disable_delayed_work_sync(&sg_core_ctx.work);
>> restore_preferred_to_active();
>> pr_info("steal_governor is disabled\n");
>> }
>> diff --git a/drivers/virt/steal_governor/core.h b/drivers/virt/steal_governor/core.h
>> index e27305284ac0..59329c1d7109 100644
>> --- a/drivers/virt/steal_governor/core.h
>> +++ b/drivers/virt/steal_governor/core.h
>> @@ -12,6 +12,11 @@
>> #include <linux/workqueue.h>
>> #include <linux/ktime.h>
>> #include <linux/kconfig.h>
>> +#include <linux/kernel_stat.h>
>> +#include <linux/topology.h>
>> +#include <linux/sched/isolation.h>
>> +#include <linux/cleanup.h>
>> +#include <linux/math64.h>
>>
>> struct steal_governor {
>> struct delayed_work work;
>> --
>> 2.47.3
next prev parent reply other threads:[~2026-07-22 7:14 UTC|newest]
Thread overview: 18+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-07-20 17:22 [PATCH v8 00/11] sched, steal_governor: Introduce cpu_preferred_mask and steal-driven vCPU backoff Shrikanth Hegde
2026-07-20 17:22 ` [PATCH v8 01/11] sched/docs: Document cpu_preferred_mask and Preferred CPU concept Shrikanth Hegde
2026-07-20 17:22 ` [PATCH v8 02/11] cpumask: Introduce cpu_preferred_mask Shrikanth Hegde
2026-07-20 17:22 ` [PATCH v8 03/11] sysfs: Add preferred CPU file Shrikanth Hegde
2026-07-20 17:22 ` [PATCH v8 04/11] sched/core: Try to use a preferred CPU in is_cpu_allowed Shrikanth Hegde
2026-07-20 17:22 ` [PATCH v8 05/11] sched/fair: Load balance only among preferred CPUs Shrikanth Hegde
2026-07-20 17:22 ` [PATCH v8 06/11] sched/core: Push current task from non preferred CPU Shrikanth Hegde
2026-07-20 17:22 ` [PATCH v8 07/11] sched/debug: Add migration stats due to non preferred CPUs Shrikanth Hegde
2026-07-20 17:22 ` [PATCH v8 08/11] virt: Introduce steal governor driver Shrikanth Hegde
2026-07-21 17:09 ` Yury Norov
2026-07-22 5:38 ` Shrikanth Hegde
2026-07-20 17:22 ` [PATCH v8 09/11] virt/steal_governor: Add control knobs for handling steal values Shrikanth Hegde
2026-07-20 17:22 ` [PATCH v8 10/11] virt/steal_governor: Implement steal_governor policy loop Shrikanth Hegde
2026-07-21 19:28 ` Yury Norov
2026-07-22 7:14 ` Shrikanth Hegde [this message]
2026-07-22 7:39 ` Yury Norov
2026-07-22 10:27 ` Shrikanth Hegde
2026-07-20 17:22 ` [PATCH v8 11/11] virt/steal_governor: Enable the driver Shrikanth Hegde
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=c64ed18e-7863-44c3-9c7b-2f1efd1a805e@linux.ibm.com \
--to=sshegde@linux.ibm.com \
--cc=arighi@nvidia.com \
--cc=chleroy@kernel.org \
--cc=christian.loehle@arm.com \
--cc=corbet@lwn.net \
--cc=dietmar.eggemann@arm.com \
--cc=frederic@kernel.org \
--cc=gregkh@linuxfoundation.org \
--cc=hdanton@sina.com \
--cc=huschle@linux.ibm.com \
--cc=iii@linux.ibm.com \
--cc=jgross@suse.com \
--cc=juri.lelli@redhat.com \
--cc=kernellwp@gmail.com \
--cc=kprateek.nayak@amd.com \
--cc=linux-doc@vger.kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=maddy@linux.ibm.com \
--cc=maz@kernel.org \
--cc=mingo@kernel.org \
--cc=pauld@redhat.com \
--cc=pbonzini@redhat.com \
--cc=peterz@infradead.org \
--cc=rafael@kernel.org \
--cc=rdunlap@infradead.org \
--cc=rostedt@goodmis.org \
--cc=seanjc@google.com \
--cc=srikar@linux.ibm.com \
--cc=tglx@kernel.org \
--cc=tj@kernel.org \
--cc=tommaso.cucinotta@gmail.com \
--cc=vincent.guittot@linaro.org \
--cc=vineeth@bitbyteword.org \
--cc=virtualization@lists.linux.dev \
--cc=vschneid@redhat.com \
--cc=ynorov@nvidia.com \
--cc=yury.norov@gmail.com \
/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
Powered by JetHome