Despite the sysfs presence with some statistics around the idle state usage, some information is missing to tune and check the cpuidle behavior.
Instead of polluting the sysfs directory with new debug information, let's create a debugfs file to be extended to show some interesting information about the cpuidle predictions.
Signed-off-by: Daniel Lezcano daniel.lezcano@linaro.org --- drivers/cpuidle/Kconfig | 7 ++ drivers/cpuidle/Makefile | 2 + drivers/cpuidle/cpuidle.c | 6 ++ drivers/cpuidle/debugfs.c | 190 ++++++++++++++++++++++++++++++++++++++++++++++ drivers/cpuidle/debugfs.h | 19 +++++ include/linux/cpuidle.h | 15 ++++ kernel/sched/idle-sched.c | 8 +- 7 files changed, 246 insertions(+), 1 deletion(-) create mode 100644 drivers/cpuidle/debugfs.c create mode 100644 drivers/cpuidle/debugfs.h
diff --git a/drivers/cpuidle/Kconfig b/drivers/cpuidle/Kconfig index dd17215..8b0f6c5 100644 --- a/drivers/cpuidle/Kconfig +++ b/drivers/cpuidle/Kconfig @@ -30,6 +30,13 @@ config CPU_IDLE_GOV_SCHED select IRQ_TIMINGS default y
+config CPU_IDLE_DEBUG + bool "Debug information about cpuidle" + select DEBUG_FS + help + Enables the debug filesystem showing information about the governor + predictions. + config DT_IDLE_STATES bool
diff --git a/drivers/cpuidle/Makefile b/drivers/cpuidle/Makefile index 3ba81b1..cc2f750 100644 --- a/drivers/cpuidle/Makefile +++ b/drivers/cpuidle/Makefile @@ -6,6 +6,8 @@ obj-y += cpuidle.o driver.o governor.o sysfs.o governors/ obj-$(CONFIG_ARCH_NEEDS_CPU_IDLE_COUPLED) += coupled.o obj-$(CONFIG_DT_IDLE_STATES) += dt_idle_states.o
+obj-$(CONFIG_CPU_IDLE_DEBUG) += debugfs.o + ################################################################################## # ARM SoC drivers obj-$(CONFIG_ARM_MVEBU_V7_CPUIDLE) += cpuidle-mvebu-v7.o diff --git a/drivers/cpuidle/cpuidle.c b/drivers/cpuidle/cpuidle.c index 17a6dc0..309aa0f 100644 --- a/drivers/cpuidle/cpuidle.c +++ b/drivers/cpuidle/cpuidle.c @@ -23,6 +23,7 @@ #include <linux/tick.h> #include <trace/events/power.h>
+#include "debugfs.h" #include "cpuidle.h"
DEFINE_PER_CPU(struct cpuidle_device *, cpuidle_devices); @@ -230,6 +231,7 @@ int cpuidle_enter_state(struct cpuidle_device *dev, struct cpuidle_driver *drv, */ dev->states_usage[entered_state].time += dev->last_residency; dev->states_usage[entered_state].usage++; + cpuidle_debugfs_prediction(drv, dev, diff, entered_state); } else { dev->last_residency = 0; } @@ -646,6 +648,10 @@ static int __init cpuidle_init(void) { int ret;
+ ret = cpuidle_debugfs_init(); + if (ret) + return ret; + if (cpuidle_disabled()) return -ENODEV;
diff --git a/drivers/cpuidle/debugfs.c b/drivers/cpuidle/debugfs.c new file mode 100644 index 0000000..c5aab98 --- /dev/null +++ b/drivers/cpuidle/debugfs.c @@ -0,0 +1,190 @@ +/* + * Debugfs support for cpuidle governors + * + * Copyright (C) 2015 Linaro : daniel.lezcano@linaro.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ +#include <linux/cpu.h> +#include <linux/cpuidle.h> +#include <linux/debugfs.h> +#include <linux/atomic.h> + +#include "cpuidle.h" + +static atomic_t correct; +static atomic_t failed; +static atomic_t early; +static atomic_t late; +static atomic_t total; + +static DEFINE_PER_CPU(atomic_t, _correct); +static DEFINE_PER_CPU(atomic_t, _failed); +static DEFINE_PER_CPU(atomic_t, _early); +static DEFINE_PER_CPU(atomic_t, _late); +static DEFINE_PER_CPU(atomic_t, _total); + +void cpuidle_debugfs_reset(void) +{ + int cpu; + + atomic_set(&correct, 0); + atomic_set(&failed, 0); + atomic_set(&early, 0); + atomic_set(&late, 0); + atomic_set(&total, 0); + + for_each_possible_cpu(cpu) { + atomic_set(&per_cpu(_correct, cpu), 0); + atomic_set(&per_cpu(_failed, cpu), 0); + atomic_set(&per_cpu(_early, cpu), 0); + atomic_set(&per_cpu(_late, cpu), 0); + atomic_set(&per_cpu(_total, cpu), 0); + } +} + +void cpuidle_debugfs_correct_inc(int cpu) +{ + atomic_inc(&per_cpu(_total, cpu)); + atomic_inc(&per_cpu(_correct, cpu)); + atomic_inc(&correct); + atomic_inc(&total); +} + +void cpuidle_debugfs_early_inc(int cpu) +{ + atomic_inc(&per_cpu(_total, cpu)); + atomic_inc(&per_cpu(_early, cpu)); + atomic_inc(&per_cpu(_failed, cpu)); + atomic_inc(&early); + atomic_inc(&failed); + atomic_inc(&total); +} + +void cpuidle_debugfs_late_inc(int cpu) +{ + atomic_inc(&per_cpu(_total, cpu)); + atomic_inc(&per_cpu(_late, cpu)); + atomic_inc(&per_cpu(_failed, cpu)); + atomic_inc(&late); + atomic_inc(&failed); + atomic_inc(&total); +} + +ssize_t cpuidle_debugfs_write_reset(struct file *file, + const char __user *user_buf, + size_t count, loff_t *ppos) +{ + ssize_t size; + bool reset = false; + + file->private_data = &reset; + + size = debugfs_write_file_bool(file, user_buf, count, ppos); + if (size > 0 && reset) + cpuidle_debugfs_reset(); + + return size; +} + +void cpuidle_debugfs_prediction(struct cpuidle_driver *drv, + struct cpuidle_device *dev, + s64 duration, int state) +{ + if (drv->states[state].target_residency <= duration) { + /* It is not the last state, check against the next one */ + if (state < drv->state_count - 1) { + /* The prediction was shorter than expected */ + if (drv->states[state + 1].target_residency <= duration) { + cpuidle_debugfs_early_inc(dev->cpu); + } else { + cpuidle_debugfs_correct_inc(dev->cpu); + } + } else { + cpuidle_debugfs_correct_inc(dev->cpu); + } + } else { + /* The prediction was longer than expected */ + cpuidle_debugfs_late_inc(dev->cpu); + } +} + +static const struct file_operations reset_fops = { + .open = simple_open, + .write = cpuidle_debugfs_write_reset, + .llseek = seq_lseek, +}; + +static int cpuidle_debugfs_stats_show(struct seq_file *m, void *v) +{ + int cpu; + + seq_printf(m, "#cpu\tcorrect\tearly\tlate\tfailed\ttotal\n"); + + seq_printf(m, "all\t"); + seq_printf(m, "%d\t", atomic_read(&correct)); + seq_printf(m, "%d\t", atomic_read(&early)); + seq_printf(m, "%d\t", atomic_read(&late)); + seq_printf(m, "%d\t", atomic_read(&failed)); + seq_printf(m, "%d\n", atomic_read(&total)); + + for_each_online_cpu(cpu) { + seq_printf(m, "%d\t", cpu); + seq_printf(m, "%d\t", atomic_read(&per_cpu(_correct, cpu))); + seq_printf(m, "%d\t", atomic_read(&per_cpu(_early, cpu))); + seq_printf(m, "%d\t", atomic_read(&per_cpu(_late, cpu))); + seq_printf(m, "%d\t", atomic_read(&per_cpu(_failed, cpu))); + seq_printf(m, "%d\n", atomic_read(&per_cpu(_total, cpu))); + } + + return 0; +} + +static int cpuidle_debugfs_stats_open(struct inode *inode, struct file *file) +{ + return single_open(file, cpuidle_debugfs_stats_show, + &inode->i_private); +} + +static const struct file_operations stats_fops = { + .open = cpuidle_debugfs_stats_open, + .read = seq_read, + .llseek = seq_lseek, +}; + +int __init cpuidle_debugfs_init(void) +{ + struct dentry *top_dentry; + int ret = -ENOMEM; + + /* + * Topmost directory. The reference is kept in order to do a + * recursive remove in case of an error. + */ + top_dentry = debugfs_create_dir("cpuidle", NULL); + if (!top_dentry) + return -ENOMEM; + + /* + * Overall statistics for all cpus + */ + if (!debugfs_create_file("stats", 0400, + top_dentry, NULL, &stats_fops)) + goto out; + + /* + * Reset statistics file + */ + if (!debugfs_create_file("reset", 0200, + top_dentry, NULL, &reset_fops)) + goto out; + + ret = 0; +out: + if (ret) + debugfs_remove_recursive(top_dentry); + + return ret; +} diff --git a/drivers/cpuidle/debugfs.h b/drivers/cpuidle/debugfs.h new file mode 100644 index 0000000..20fd719 --- /dev/null +++ b/drivers/cpuidle/debugfs.h @@ -0,0 +1,19 @@ +#ifndef __CPUIDLE_DEBUGFS_H +#define __CPUIDLE_DEBUGFS_H + +#ifdef CONFIG_CPU_IDLE_DEBUG +extern int __init cpuidle_debugfs_init(void); +extern void cpuidle_debugfs_reset(void); +extern void cpuidle_debugfs_correct_inc(int cpu); +extern void cpuidle_debugfs_early_inc(int cpu); +extern void cpuidle_debugfs_late_inc(int cpu); +#else +static int inline cpuidle_debugfs_init(void) { return 0; } +static inline void cpuidle_debugfs_reset(void) { return; } +static inline void cpuidle_debugfs_correct_inc(int cpu) {} +static inline void cpuidle_debugfs_early_inc(int cpu) {} +static inline void cpuidle_debugfs_late_inc(int cpu) {} + +#endif /* CONFIG_CPU_IDLE_DEBUG */ + +#endif diff --git a/include/linux/cpuidle.h b/include/linux/cpuidle.h index 786ad32..0884d76 100644 --- a/include/linux/cpuidle.h +++ b/include/linux/cpuidle.h @@ -207,6 +207,21 @@ static inline int cpuidle_enter_freeze(struct cpuidle_driver *drv, extern void sched_idle_set_state(struct cpuidle_state *idle_state); extern void default_idle_call(void);
+#ifdef CONFIG_CPU_IDLE_DEBUG +/* drivers/cpuidle/debugfs.c */ +extern void cpuidle_debugfs_prediction(struct cpuidle_driver *drv, + struct cpuidle_device *dev, + s64 diff, int state); +#else +static inline void cpuidle_debugfs_prediction(struct cpuidle_driver *, + struct cpuidle_device *, + s64 , int) +{ + ; +} +#endif + + #ifdef CONFIG_ARCH_NEEDS_CPU_IDLE_COUPLED void cpuidle_coupled_parallel_barrier(struct cpuidle_device *dev, atomic_t *a); #else diff --git a/kernel/sched/idle-sched.c b/kernel/sched/idle-sched.c index 97dc8a6..d483ba3 100644 --- a/kernel/sched/idle-sched.c +++ b/kernel/sched/idle-sched.c @@ -291,7 +291,13 @@ int sched_idle(s64 duration, unsigned int latency) * an interrupt occurs and will take care of * re-enabling the local interrupts */ - return cpuidle_enter(drv, dev, index); + ktime_t before = ktime_get(), after; + ret = cpuidle_enter(drv, dev, index); + after = ktime_get(); + cpuidle_debugfs_prediction(drv, dev, + ktime_us_delta(after, before), + index); + return ret; }
default_idle: -- 1.9.1