Skip to content

Commit a5fe724

Browse files
Frederic WeisbeckerThomas Gleixner
authored andcommitted
tick/sched: Move dyntick-idle cputime accounting to cputime code
Although the dynticks-idle cputime accounting is necessarily tied to the tick subsystem, the actual related accounting code has no business residing there and should be part of the scheduler cputime code. Move away the relevant pieces and state machine to where they belong. 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-10-frederic@kernel.org
1 parent bd0c77c commit a5fe724

4 files changed

Lines changed: 188 additions & 143 deletions

File tree

include/linux/kernel_stat.h

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,9 +35,12 @@ enum cpu_usage_stat {
3535

3636
struct kernel_cpustat {
3737
#ifdef CONFIG_NO_HZ_COMMON
38-
int idle_dyntick;
38+
bool idle_dyntick;
39+
bool idle_elapse;
40+
seqcount_t idle_sleeptime_seq;
41+
u64 idle_entrytime;
3942
#endif
40-
u64 cpustat[NR_STATS];
43+
u64 cpustat[NR_STATS];
4144
};
4245

4346
struct kernel_stat {
@@ -103,8 +106,11 @@ static inline unsigned long kstat_cpu_irqs_sum(unsigned int cpu)
103106
}
104107

105108
#ifdef CONFIG_NO_HZ_COMMON
106-
extern void kcpustat_dyntick_start(void);
107-
extern void kcpustat_dyntick_stop(void);
109+
extern void kcpustat_dyntick_start(u64 now);
110+
extern void kcpustat_dyntick_stop(u64 now);
111+
extern void kcpustat_irq_enter(u64 now);
112+
extern void kcpustat_irq_exit(u64 now);
113+
108114
static inline bool kcpustat_idle_dyntick(void)
109115
{
110116
return __this_cpu_read(kernel_cpustat.idle_dyntick);

kernel/sched/core.c

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5518,7 +5518,11 @@ void sched_exec(void)
55185518
}
55195519

55205520
DEFINE_PER_CPU(struct kernel_stat, kstat);
5521-
DEFINE_PER_CPU(struct kernel_cpustat, kernel_cpustat);
5521+
DEFINE_PER_CPU(struct kernel_cpustat, kernel_cpustat) = {
5522+
#ifdef CONFIG_NO_HZ_COMMON
5523+
.idle_sleeptime_seq = SEQCNT_ZERO(kernel_cpustat.idle_sleeptime_seq)
5524+
#endif
5525+
};
55225526

55235527
EXPORT_PER_CPU_SYMBOL(kstat);
55245528
EXPORT_PER_CPU_SYMBOL(kernel_cpustat);

kernel/sched/cputime.c

Lines changed: 141 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
/*
33
* Simple CPU accounting cgroup controller
44
*/
5+
#include <linux/sched/clock.h>
56
#include <linux/sched/cputime.h>
67
#include <linux/tsacct_kern.h>
78
#include "sched.h"
@@ -420,22 +421,155 @@ static inline void irqtime_account_process_tick(struct task_struct *p, int user_
420421
#endif /* !CONFIG_IRQ_TIME_ACCOUNTING */
421422

422423
#ifdef CONFIG_NO_HZ_COMMON
423-
void kcpustat_dyntick_start(void)
424+
static void kcpustat_idle_stop(struct kernel_cpustat *kc, u64 now)
424425
{
425-
if (!vtime_generic_enabled_this_cpu()) {
426-
vtime_dyntick_start();
427-
__this_cpu_write(kernel_cpustat.idle_dyntick, 1);
428-
}
426+
u64 *cpustat = kc->cpustat;
427+
u64 delta;
428+
429+
if (!kc->idle_elapse)
430+
return;
431+
432+
delta = now - kc->idle_entrytime;
433+
434+
write_seqcount_begin(&kc->idle_sleeptime_seq);
435+
if (nr_iowait_cpu(smp_processor_id()) > 0)
436+
cpustat[CPUTIME_IOWAIT] += delta;
437+
else
438+
cpustat[CPUTIME_IDLE] += delta;
439+
440+
kc->idle_entrytime = now;
441+
kc->idle_elapse = false;
442+
write_seqcount_end(&kc->idle_sleeptime_seq);
429443
}
430444

431-
void kcpustat_dyntick_stop(void)
445+
static void kcpustat_idle_start(struct kernel_cpustat *kc, u64 now)
432446
{
447+
write_seqcount_begin(&kc->idle_sleeptime_seq);
448+
kc->idle_entrytime = now;
449+
kc->idle_elapse = true;
450+
write_seqcount_end(&kc->idle_sleeptime_seq);
451+
}
452+
453+
void kcpustat_dyntick_stop(u64 now)
454+
{
455+
struct kernel_cpustat *kc = kcpustat_this_cpu;
456+
433457
if (!vtime_generic_enabled_this_cpu()) {
434-
__this_cpu_write(kernel_cpustat.idle_dyntick, 0);
458+
WARN_ON_ONCE(!kc->idle_dyntick);
459+
kcpustat_idle_stop(kc, now);
460+
kc->idle_dyntick = false;
435461
vtime_dyntick_stop();
436462
steal_account_process_time(ULONG_MAX);
437463
}
438464
}
465+
466+
void kcpustat_dyntick_start(u64 now)
467+
{
468+
struct kernel_cpustat *kc = kcpustat_this_cpu;
469+
470+
if (!vtime_generic_enabled_this_cpu()) {
471+
vtime_dyntick_start();
472+
kc->idle_dyntick = true;
473+
kcpustat_idle_start(kc, now);
474+
}
475+
}
476+
477+
void kcpustat_irq_enter(u64 now)
478+
{
479+
struct kernel_cpustat *kc = kcpustat_this_cpu;
480+
481+
if (!vtime_generic_enabled_this_cpu())
482+
kcpustat_idle_stop(kc, now);
483+
}
484+
485+
void kcpustat_irq_exit(u64 now)
486+
{
487+
struct kernel_cpustat *kc = kcpustat_this_cpu;
488+
489+
if (!vtime_generic_enabled_this_cpu())
490+
kcpustat_idle_start(kc, now);
491+
}
492+
493+
static u64 get_cpu_sleep_time_us(int cpu, enum cpu_usage_stat idx,
494+
bool compute_delta, u64 *last_update_time)
495+
{
496+
struct kernel_cpustat *kc = &kcpustat_cpu(cpu);
497+
u64 *cpustat = kc->cpustat;
498+
unsigned int seq;
499+
ktime_t now;
500+
u64 idle;
501+
502+
now = ktime_get();
503+
if (last_update_time)
504+
*last_update_time = ktime_to_us(now);
505+
506+
if (vtime_generic_enabled_cpu(cpu)) {
507+
idle = kcpustat_field(idx, cpu);
508+
goto to_us;
509+
}
510+
511+
do {
512+
seq = read_seqcount_begin(&kc->idle_sleeptime_seq);
513+
514+
idle = cpustat[idx];
515+
if (kc->idle_elapse && compute_delta && now > kc->idle_entrytime)
516+
idle += (now - kc->idle_entrytime);
517+
} while (read_seqcount_retry(&kc->idle_sleeptime_seq, seq));
518+
519+
to_us:
520+
do_div(idle, NSEC_PER_USEC);
521+
522+
return idle;
523+
}
524+
525+
/**
526+
* get_cpu_idle_time_us - get the total idle time of a CPU
527+
* @cpu: CPU number to query
528+
* @last_update_time: variable to store update time in. Do not update
529+
* counters if NULL.
530+
*
531+
* Return the cumulative idle time (since boot) for a given
532+
* CPU, in microseconds. Note that this is partially broken due to
533+
* the counter of iowait tasks that can be remotely updated without
534+
* any synchronization. Therefore it is possible to observe backward
535+
* values within two consecutive reads.
536+
*
537+
* This time is measured via accounting rather than sampling,
538+
* and is as accurate as ktime_get() is.
539+
*
540+
* Return: -1 if generic vtime is enabled, else total idle time of the @cpu
541+
*/
542+
u64 get_cpu_idle_time_us(int cpu, u64 *last_update_time)
543+
{
544+
return get_cpu_sleep_time_us(cpu, CPUTIME_IDLE,
545+
!nr_iowait_cpu(cpu), last_update_time);
546+
}
547+
EXPORT_SYMBOL_GPL(get_cpu_idle_time_us);
548+
549+
/**
550+
* get_cpu_iowait_time_us - get the total iowait time of a CPU
551+
* @cpu: CPU number to query
552+
* @last_update_time: variable to store update time in. Do not update
553+
* counters if NULL.
554+
*
555+
* Return the cumulative iowait time (since boot) for a given
556+
* CPU, in microseconds. Note this is partially broken due to
557+
* the counter of iowait tasks that can be remotely updated without
558+
* any synchronization. Therefore it is possible to observe backward
559+
* values within two consecutive reads.
560+
*
561+
* This time is measured via accounting rather than sampling,
562+
* and is as accurate as ktime_get() is.
563+
*
564+
* Return: -1 if generic vtime is enabled, else total iowait time of @cpu
565+
*/
566+
u64 get_cpu_iowait_time_us(int cpu, u64 *last_update_time)
567+
{
568+
return get_cpu_sleep_time_us(cpu, CPUTIME_IOWAIT,
569+
nr_iowait_cpu(cpu), last_update_time);
570+
}
571+
EXPORT_SYMBOL_GPL(get_cpu_iowait_time_us);
572+
439573
#endif /* CONFIG_NO_HZ_COMMON */
440574

441575
/*

0 commit comments

Comments
 (0)