Skip to content

Commit cf6444c

Browse files
Frederic WeisbeckerThomas Gleixner
authored andcommitted
tick/sched: Unify idle cputime accounting
The non-vtime dynticks-idle cputime accounting is a big mess that accumulates within two concurrent statistics, each having their own shortcomings: * The accounting for online CPUs which is based on the delta between tick_nohz_start_idle() and tick_nohz_stop_idle(). Pros: - Works when the tick is off - Has nsecs granularity Cons: - Account idle steal time but doesn't substract it from idle cputime. - Assumes CONFIG_IRQ_TIME_ACCOUNTING by not accounting IRQs but the IRQ time is simply ignored when CONFIG_IRQ_TIME_ACCOUNTING=n - The windows between 1) idle task scheduling and the first call to tick_nohz_start_idle() and 2) idle task between the last tick_nohz_stop_idle() and the rest of the idle time are blindspots wrt. cputime accounting (though mostly insignificant amount) - Relies on private fields outside of kernel stats, with specific accessors. * The accounting for offline CPUs which is based on ticks and the jiffies delta during which the tick was stopped. Pros: - Handles steal time correctly - Handle CONFIG_IRQ_TIME_ACCOUNTING=y and CONFIG_IRQ_TIME_ACCOUNTING=n correctly. - Handles the whole idle task - Accounts directly to kernel stats, without midlayer accumulator. Cons: - Doesn't elapse when the tick is off, which doesn't make it suitable for online CPUs. - Has TICK_NSEC granularity (jiffies) - Needs to track the dyntick-idle ticks that were accounted and substract them from the total jiffies time spent while the tick was stopped. This is an ugly workaround. Having two different accounting for a single context is not the only problem: since those accountings are of different natures, it is possible to observe the global idle time going backward after a CPU goes offline. Clean up the situation with introducing a hybrid approach that stays coherent and works for both online and offline CPUs: * Tick based or native vtime accounting operate before the idle loop is entered and resume once the idle loop prepares to exit. * When the idle loop starts, switch to dynticks-idle accounting as is done currently, except that the statistics accumulate directly to the relevant kernel stat fields. * Private dyntick cputime accounting fields are removed. * Works on both online and offline case. Further improvement will include: * Only switch to dynticks-idle cputime accounting when the tick actually goes in dynticks mode. * Handle CONFIG_IRQ_TIME_ACCOUNTING=n correctly such that the dynticks-idle accounting still elapses while on IRQs. * Correctly substract idle steal cputime from idle time Reported-by: Xin Zhao <jackzxcui1989@163.com> Signed-off-by: Frederic Weisbecker <frederic@kernel.org> Signed-off-by: Thomas Gleixner <tglx@kernel.org> Tested-by: Shrikanth Hegde <sshegde@linux.ibm.com> Link: https://patch.msgid.link/20260508131647.43868-8-frederic@kernel.org
1 parent ad5a9e1 commit cf6444c

4 files changed

Lines changed: 75 additions & 87 deletions

File tree

include/linux/kernel_stat.h

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,9 @@ enum cpu_usage_stat {
3434
};
3535

3636
struct kernel_cpustat {
37+
#ifdef CONFIG_NO_HZ_COMMON
38+
int idle_dyntick;
39+
#endif
3740
u64 cpustat[NR_STATS];
3841
};
3942

@@ -99,6 +102,20 @@ static inline unsigned long kstat_cpu_irqs_sum(unsigned int cpu)
99102
return kstat_cpu(cpu).irqs_sum;
100103
}
101104

105+
#ifdef CONFIG_NO_HZ_COMMON
106+
extern void kcpustat_dyntick_start(void);
107+
extern void kcpustat_dyntick_stop(void);
108+
static inline bool kcpustat_idle_dyntick(void)
109+
{
110+
return __this_cpu_read(kernel_cpustat.idle_dyntick);
111+
}
112+
#else
113+
static inline bool kcpustat_idle_dyntick(void)
114+
{
115+
return false;
116+
}
117+
#endif /* CONFIG_NO_HZ_COMMON */
118+
102119
#ifdef CONFIG_VIRT_CPU_ACCOUNTING_GEN
103120
extern u64 kcpustat_field(enum cpu_usage_stat usage, int cpu);
104121
extern void kcpustat_cpu_fetch(struct kernel_cpustat *dst, int cpu);
@@ -113,7 +130,7 @@ static inline void kcpustat_cpu_fetch(struct kernel_cpustat *dst, int cpu)
113130
*dst = kcpustat_cpu(cpu);
114131
}
115132

116-
#endif
133+
#endif /* !CONFIG_VIRT_CPU_ACCOUNTING_GEN */
117134

118135
extern void account_user_time(struct task_struct *, u64);
119136
extern void account_guest_time(struct task_struct *, u64);
@@ -127,14 +144,13 @@ extern u64 get_idle_time(struct kernel_cpustat *kcs, int cpu);
127144
#ifdef CONFIG_VIRT_CPU_ACCOUNTING_NATIVE
128145
static inline void account_process_tick(struct task_struct *tsk, int user)
129146
{
130-
vtime_flush(tsk);
147+
if (!kcpustat_idle_dyntick())
148+
vtime_flush(tsk);
131149
}
132150
#else
133151
extern void account_process_tick(struct task_struct *, int user);
134152
#endif
135153

136-
extern void account_idle_ticks(unsigned long ticks);
137-
138154
#ifdef CONFIG_SCHED_CORE
139155
extern void __account_forceidle_time(struct task_struct *tsk, u64 delta);
140156
#endif

include/linux/vtime.h

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,11 @@ static inline bool vtime_generic_enabled_cpu(int cpu)
3131
return context_tracking_enabled_cpu(cpu);
3232
}
3333

34+
static inline bool vtime_generic_enabled_this_cpu(void)
35+
{
36+
return context_tracking_enabled_this_cpu();
37+
}
38+
3439
#ifdef CONFIG_VIRT_CPU_ACCOUNTING_NATIVE
3540
extern void vtime_account_idle(struct task_struct *tsk);
3641
extern void vtime_account_irq(struct task_struct *tsk, unsigned int offset);
@@ -90,7 +95,7 @@ static inline bool vtime_accounting_enabled_cpu(int cpu)
9095

9196
static inline bool vtime_accounting_enabled_this_cpu(void)
9297
{
93-
return context_tracking_enabled_this_cpu();
98+
return vtime_generic_enabled_this_cpu();
9499
}
95100

96101
extern void vtime_task_switch_generic(struct task_struct *prev);

kernel/sched/cputime.c

Lines changed: 30 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -414,16 +414,30 @@ static void irqtime_account_process_tick(struct task_struct *p, int user_tick,
414414
}
415415
}
416416

417-
static void irqtime_account_idle_ticks(int ticks)
418-
{
419-
irqtime_account_process_tick(current, 0, ticks);
420-
}
421417
#else /* !CONFIG_IRQ_TIME_ACCOUNTING: */
422-
static inline void irqtime_account_idle_ticks(int ticks) { }
423418
static inline void irqtime_account_process_tick(struct task_struct *p, int user_tick,
424419
int nr_ticks) { }
425420
#endif /* !CONFIG_IRQ_TIME_ACCOUNTING */
426421

422+
#ifdef CONFIG_NO_HZ_COMMON
423+
void kcpustat_dyntick_start(void)
424+
{
425+
if (!vtime_generic_enabled_this_cpu()) {
426+
vtime_dyntick_start();
427+
__this_cpu_write(kernel_cpustat.idle_dyntick, 1);
428+
}
429+
}
430+
431+
void kcpustat_dyntick_stop(void)
432+
{
433+
if (!vtime_generic_enabled_this_cpu()) {
434+
__this_cpu_write(kernel_cpustat.idle_dyntick, 0);
435+
vtime_dyntick_stop();
436+
steal_account_process_time(ULONG_MAX);
437+
}
438+
}
439+
#endif /* CONFIG_NO_HZ_COMMON */
440+
427441
/*
428442
* Use precise platform statistics if available:
429443
*/
@@ -437,11 +451,15 @@ void vtime_account_irq(struct task_struct *tsk, unsigned int offset)
437451
vtime_account_hardirq(tsk);
438452
} else if (pc & SOFTIRQ_OFFSET) {
439453
vtime_account_softirq(tsk);
440-
} else if (!IS_ENABLED(CONFIG_HAVE_VIRT_CPU_ACCOUNTING_IDLE) &&
441-
is_idle_task(tsk)) {
442-
vtime_account_idle(tsk);
454+
} else if (!kcpustat_idle_dyntick()) {
455+
if (!IS_ENABLED(CONFIG_HAVE_VIRT_CPU_ACCOUNTING_IDLE) &&
456+
is_idle_task(tsk)) {
457+
vtime_account_idle(tsk);
458+
} else {
459+
vtime_account_kernel(tsk);
460+
}
443461
} else {
444-
vtime_account_kernel(tsk);
462+
vtime_reset();
445463
}
446464
}
447465

@@ -483,6 +501,9 @@ void account_process_tick(struct task_struct *p, int user_tick)
483501
if (vtime_accounting_enabled_this_cpu())
484502
return;
485503

504+
if (kcpustat_idle_dyntick())
505+
return;
506+
486507
if (irqtime_enabled()) {
487508
irqtime_account_process_tick(p, user_tick, 1);
488509
return;
@@ -504,29 +525,6 @@ void account_process_tick(struct task_struct *p, int user_tick)
504525
account_idle_time(cputime);
505526
}
506527

507-
/*
508-
* Account multiple ticks of idle time.
509-
* @ticks: number of stolen ticks
510-
*/
511-
void account_idle_ticks(unsigned long ticks)
512-
{
513-
u64 cputime, steal;
514-
515-
if (irqtime_enabled()) {
516-
irqtime_account_idle_ticks(ticks);
517-
return;
518-
}
519-
520-
cputime = ticks * TICK_NSEC;
521-
steal = steal_account_process_time(ULONG_MAX);
522-
523-
if (steal >= cputime)
524-
return;
525-
526-
cputime -= steal;
527-
account_idle_time(cputime);
528-
}
529-
530528
/*
531529
* Adjust tick based cputime random precision against scheduler runtime
532530
* accounting.

kernel/time/tick-sched.c

Lines changed: 19 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -285,8 +285,6 @@ static void tick_sched_handle(struct tick_sched *ts, struct pt_regs *regs)
285285
if (IS_ENABLED(CONFIG_NO_HZ_COMMON) &&
286286
tick_sched_flag_test(ts, TS_FLAG_STOPPED)) {
287287
touch_softlockup_watchdog_sched();
288-
if (is_idle_task(current))
289-
ts->idle_jiffies++;
290288
/*
291289
* In case the current tick fired too early past its expected
292290
* expiration, make sure we don't bypass the next clock reprogramming
@@ -753,18 +751,22 @@ static void tick_nohz_update_jiffies(ktime_t now)
753751

754752
static void tick_nohz_stop_idle(struct tick_sched *ts, ktime_t now)
755753
{
754+
u64 *cpustat = kcpustat_this_cpu->cpustat;
756755
ktime_t delta;
757756

757+
if (vtime_generic_enabled_this_cpu())
758+
return;
759+
758760
if (WARN_ON_ONCE(!tick_sched_flag_test(ts, TS_FLAG_IDLE_ACTIVE)))
759761
return;
760762

761763
delta = ktime_sub(now, ts->idle_entrytime);
762764

763765
write_seqcount_begin(&ts->idle_sleeptime_seq);
764766
if (nr_iowait_cpu(smp_processor_id()) > 0)
765-
ts->iowait_sleeptime = ktime_add(ts->iowait_sleeptime, delta);
767+
cpustat[CPUTIME_IOWAIT] = ktime_add(cpustat[CPUTIME_IOWAIT], delta);
766768
else
767-
ts->idle_sleeptime = ktime_add(ts->idle_sleeptime, delta);
769+
cpustat[CPUTIME_IDLE] = ktime_add(cpustat[CPUTIME_IDLE], delta);
768770

769771
ts->idle_entrytime = now;
770772
tick_sched_flag_clear(ts, TS_FLAG_IDLE_ACTIVE);
@@ -775,18 +777,21 @@ static void tick_nohz_stop_idle(struct tick_sched *ts, ktime_t now)
775777

776778
static void tick_nohz_start_idle(struct tick_sched *ts)
777779
{
780+
if (vtime_generic_enabled_this_cpu())
781+
return;
782+
778783
write_seqcount_begin(&ts->idle_sleeptime_seq);
779784
ts->idle_entrytime = ktime_get();
780785
tick_sched_flag_set(ts, TS_FLAG_IDLE_ACTIVE);
781786
write_seqcount_end(&ts->idle_sleeptime_seq);
782-
783787
sched_clock_idle_sleep_event();
784788
}
785789

786-
static u64 get_cpu_sleep_time_us(int cpu, enum cpu_usage_stat idx, ktime_t *sleeptime,
790+
static u64 get_cpu_sleep_time_us(int cpu, enum cpu_usage_stat idx,
787791
bool compute_delta, u64 *last_update_time)
788792
{
789793
struct tick_sched *ts = &per_cpu(tick_cpu_sched, cpu);
794+
u64 *cpustat = kcpustat_cpu(cpu).cpustat;
790795
ktime_t now, idle;
791796
unsigned int seq;
792797

@@ -812,7 +817,7 @@ static u64 get_cpu_sleep_time_us(int cpu, enum cpu_usage_stat idx, ktime_t *slee
812817
delta = ktime_sub(now, ts->idle_entrytime);
813818
}
814819

815-
idle = ktime_add(*sleeptime, delta);
820+
idle = ktime_add(cpustat[idx], delta);
816821
} while (read_seqcount_retry(&ts->idle_sleeptime_seq, seq));
817822

818823
return ktime_to_us(idle);
@@ -838,9 +843,7 @@ static u64 get_cpu_sleep_time_us(int cpu, enum cpu_usage_stat idx, ktime_t *slee
838843
*/
839844
u64 get_cpu_idle_time_us(int cpu, u64 *last_update_time)
840845
{
841-
struct tick_sched *ts = &per_cpu(tick_cpu_sched, cpu);
842-
843-
return get_cpu_sleep_time_us(cpu, CPUTIME_IDLE, &ts->idle_sleeptime,
846+
return get_cpu_sleep_time_us(cpu, CPUTIME_IDLE,
844847
!nr_iowait_cpu(cpu), last_update_time);
845848
}
846849
EXPORT_SYMBOL_GPL(get_cpu_idle_time_us);
@@ -864,9 +867,7 @@ EXPORT_SYMBOL_GPL(get_cpu_idle_time_us);
864867
*/
865868
u64 get_cpu_iowait_time_us(int cpu, u64 *last_update_time)
866869
{
867-
struct tick_sched *ts = &per_cpu(tick_cpu_sched, cpu);
868-
869-
return get_cpu_sleep_time_us(cpu, CPUTIME_IOWAIT, &ts->iowait_sleeptime,
870+
return get_cpu_sleep_time_us(cpu, CPUTIME_IOWAIT,
870871
nr_iowait_cpu(cpu), last_update_time);
871872
}
872873
EXPORT_SYMBOL_GPL(get_cpu_iowait_time_us);
@@ -1279,10 +1280,8 @@ void tick_nohz_idle_stop_tick(void)
12791280
ts->idle_sleeps++;
12801281
ts->idle_expires = expires;
12811282

1282-
if (!was_stopped && tick_sched_flag_test(ts, TS_FLAG_STOPPED)) {
1283-
ts->idle_jiffies = ts->last_jiffies;
1283+
if (!was_stopped && tick_sched_flag_test(ts, TS_FLAG_STOPPED))
12841284
nohz_balance_enter_idle(cpu);
1285-
}
12861285
} else {
12871286
tick_nohz_retain_tick(ts);
12881287
}
@@ -1311,6 +1310,7 @@ void tick_nohz_idle_enter(void)
13111310
WARN_ON_ONCE(ts->timer_expires_base);
13121311

13131312
tick_sched_flag_set(ts, TS_FLAG_INIDLE);
1313+
kcpustat_dyntick_start();
13141314
tick_nohz_start_idle(ts);
13151315

13161316
local_irq_enable();
@@ -1436,37 +1436,12 @@ unsigned long tick_nohz_get_idle_calls_cpu(int cpu)
14361436
return ts->idle_calls;
14371437
}
14381438

1439-
static void tick_nohz_account_idle_time(struct tick_sched *ts,
1440-
ktime_t now)
1441-
{
1442-
unsigned long ticks;
1443-
1444-
ts->idle_exittime = now;
1445-
1446-
if (vtime_accounting_enabled_this_cpu())
1447-
return;
1448-
/*
1449-
* We stopped the tick in idle. update_process_times() would miss the
1450-
* time we slept, as it does only a 1 tick accounting.
1451-
* Enforce that this is accounted to idle !
1452-
*/
1453-
ticks = jiffies - ts->idle_jiffies;
1454-
/*
1455-
* We might be one off. Do not randomly account a huge number of ticks!
1456-
*/
1457-
if (ticks && ticks < LONG_MAX)
1458-
account_idle_ticks(ticks);
1459-
}
1460-
14611439
void tick_nohz_idle_restart_tick(void)
14621440
{
14631441
struct tick_sched *ts = this_cpu_ptr(&tick_cpu_sched);
14641442

1465-
if (tick_sched_flag_test(ts, TS_FLAG_STOPPED)) {
1466-
ktime_t now = ktime_get();
1467-
tick_nohz_restart_sched_tick(ts, now);
1468-
tick_nohz_account_idle_time(ts, now);
1469-
}
1443+
if (tick_sched_flag_test(ts, TS_FLAG_STOPPED))
1444+
tick_nohz_restart_sched_tick(ts, ktime_get());
14701445
}
14711446

14721447
static void tick_nohz_idle_update_tick(struct tick_sched *ts, ktime_t now)
@@ -1475,8 +1450,6 @@ static void tick_nohz_idle_update_tick(struct tick_sched *ts, ktime_t now)
14751450
__tick_nohz_full_update_tick(ts, now);
14761451
else
14771452
tick_nohz_restart_sched_tick(ts, now);
1478-
1479-
tick_nohz_account_idle_time(ts, now);
14801453
}
14811454

14821455
/**
@@ -1518,6 +1491,7 @@ void tick_nohz_idle_exit(void)
15181491

15191492
if (tick_stopped)
15201493
tick_nohz_idle_update_tick(ts, now);
1494+
kcpustat_dyntick_stop();
15211495

15221496
local_irq_enable();
15231497
}
@@ -1655,20 +1629,15 @@ void tick_setup_sched_timer(bool hrtimer)
16551629
void tick_sched_timer_dying(int cpu)
16561630
{
16571631
struct tick_sched *ts = &per_cpu(tick_cpu_sched, cpu);
1658-
ktime_t idle_sleeptime, iowait_sleeptime;
16591632
unsigned long idle_calls, idle_sleeps;
16601633

16611634
/* This must happen before hrtimers are migrated! */
16621635
if (tick_sched_flag_test(ts, TS_FLAG_HIGHRES))
16631636
hrtimer_cancel(&ts->sched_timer);
16641637

1665-
idle_sleeptime = ts->idle_sleeptime;
1666-
iowait_sleeptime = ts->iowait_sleeptime;
16671638
idle_calls = ts->idle_calls;
16681639
idle_sleeps = ts->idle_sleeps;
16691640
memset(ts, 0, sizeof(*ts));
1670-
ts->idle_sleeptime = idle_sleeptime;
1671-
ts->iowait_sleeptime = iowait_sleeptime;
16721641
ts->idle_calls = idle_calls;
16731642
ts->idle_sleeps = idle_sleeps;
16741643
}

0 commit comments

Comments
 (0)