Messages in this thread Patch in this message | | | From | Tri Vo <> | Subject | [PATCH v2] PM / wakeup: show wakeup sources stats in sysfs | Date | Thu, 27 Jun 2019 15:53:35 -0700 |
| |
Userspace can use wakeup_sources debugfs node to plot history of suspend blocking wakeup sources over device's boot cycle. This information can then be used (1) for power-specific bug reporting and (2) towards attributing battery consumption to specific processes over a period of time.
However, debugfs doesn't have stable ABI. For this reason, expose wakeup sources statistics in sysfs under /sys/power/wakeup_sources/<name>/
Embedding a struct kobject into struct wakeup_source changes lifetime requirements on the latter. To that end, change deallocation of struct wakeup_source using kfree to kobject_put().
Change struct wakelock's wakeup_source member to a pointer to decouple lifetimes of struct wakelock and struct wakeup_source for above reason.
Introduce CONFIG_PM_SLEEP_STATS that enables/disables showing wakeup source statistics in sysfs.
Signed-off-by: Tri Vo <trong@android.com> --- Documentation/ABI/testing/sysfs-power | 80 ++++++++++- drivers/base/power/Makefile | 2 +- drivers/base/power/power.h | 7 + drivers/base/power/wakeup.c | 16 ++- drivers/base/power/wakeup_stats.c | 194 ++++++++++++++++++++++++++ include/linux/pm_wakeup.h | 15 ++ kernel/power/Kconfig | 10 ++ kernel/power/wakelock.c | 42 ++++-- 8 files changed, 347 insertions(+), 19 deletions(-) create mode 100644 drivers/base/power/wakeup_stats.c
v2: - Updated Documentation/ABI/, as per Greg. - Removed locks in attribute functions, as per Greg. - Lifetimes of struct wakelock and struck wakeup_source are now different due to the latter embedding a refcounted kobject. Changed it so that struct wakelock only has a pointer to struct wakeup_source, instead of embedding it. - Added CONFIG_PM_SLEEP_STATS that enables/disables wakeup source statistics in sysfs.
diff --git a/Documentation/ABI/testing/sysfs-power b/Documentation/ABI/testing/sysfs-power index 18b7dc929234..ba35f1eef73b 100644 --- a/Documentation/ABI/testing/sysfs-power +++ b/Documentation/ABI/testing/sysfs-power @@ -300,4 +300,82 @@ Description: attempt.
Using this sysfs file will override any values that were - set using the kernel command line for disk offset. \ No newline at end of file + set using the kernel command line for disk offset. + +What: /sys/power/wakeup_sources/.../ +Date: June 2019 +Contact: Tri Vo <trong@android.com> +Description: + The /sys/power/wakeup_sources/.../ directory contains attributes + that expose statistics about a given wakeup source to user + space. Attributes in this directory are read-only. + +What: /sys/power/wakeup_sources/.../active_count +Date: June 2019 +Contact: Tri Vo <trong@android.com> +Description: + The /sys/power/wakeup_sources/.../active_count file contains the + number of times the wakeup source was activated. + +What: /sys/power/wakeup_sources/.../event_count +Date: June 2019 +Contact: Tri Vo <trong@android.com> +Description: + The /sys/power/wakeup_sources/.../event_count file contains the + number of signaled wakeup events associated with the wakeup + source. + +What: /sys/power/wakeup_sources/.../wakeup_count +Date: June 2019 +Contact: Tri Vo <trong@android.com> +Description: + The /sys/power/wakeup_sources/.../wakeup_count file contains the + number of times the wakeup source might abort suspend. + +What: /sys/power/wakeup_sources/.../expire_count +Date: June 2019 +Contact: Tri Vo <trong@android.com> +Description: + The /sys/power/wakeup_sources/.../expire_count file contains the + number of times the wakeup source's timeout has expired. + +What: /sys/power/wakeup_sources/.../active_time_ms +Date: June 2019 +Contact: Tri Vo <trong@android.com> +Description: + The /sys/power/wakeup_sources/.../active_time_ms file contains + the amount of time the wakeup source has been continuously + active, in milliseconds. If the wakeup source is not active, + this file contains '0'. + +What: /sys/power/wakeup_sources/.../total_time_ms +Date: June 2019 +Contact: Tri Vo <trong@android.com> +Description: + The /sys/power/wakeup_sources/.../total_time_ms file contains + the total amount of time this wakeup source has been active, in + milliseconds. + +What: /sys/power/wakeup_sources/.../max_time_ms +Date: June 2019 +Contact: Tri Vo <trong@android.com> +Description: + The /sys/power/wakeup_sources/.../max_time_ms file contains the + maximum amount of time this wakeup source has been continuously + active, in milliseconds. + +What: /sys/power/wakeup_sources/.../last_change_ms +Date: June 2019 +Contact: Tri Vo <trong@android.com> +Description: + The /sys/power/wakeup_sources/.../last_change_ms file contains + the monotonic clock time when the wakeup source was touched last + time, in milliseconds. + +What: /sys/power/wakeup_sources/.../prevent_suspend_time_ms +Date: June 2019 +Contact: Tri Vo <trong@android.com> +Description: + The /sys/power/wakeup_sources/.../prevent_suspend_time_ms file + contains the total amount of time this wakeup source has been + preventing autosleep, in milliseconds. diff --git a/drivers/base/power/Makefile b/drivers/base/power/Makefile index e1bb691cf8f1..ec5bb190b9d0 100644 --- a/drivers/base/power/Makefile +++ b/drivers/base/power/Makefile @@ -1,6 +1,6 @@ # SPDX-License-Identifier: GPL-2.0 obj-$(CONFIG_PM) += sysfs.o generic_ops.o common.o qos.o runtime.o wakeirq.o -obj-$(CONFIG_PM_SLEEP) += main.o wakeup.o +obj-$(CONFIG_PM_SLEEP) += main.o wakeup.o wakeup_stats.o obj-$(CONFIG_PM_TRACE_RTC) += trace.o obj-$(CONFIG_PM_GENERIC_DOMAINS) += domain.o domain_governor.o obj-$(CONFIG_HAVE_CLK) += clock_ops.o diff --git a/drivers/base/power/power.h b/drivers/base/power/power.h index ec33fbdb919b..c288298f57c0 100644 --- a/drivers/base/power/power.h +++ b/drivers/base/power/power.h @@ -149,3 +149,10 @@ static inline void device_pm_init(struct device *dev) device_pm_sleep_init(dev); pm_runtime_init(dev); } + +#ifdef CONFIG_PM_SLEEP + +/* drivers/base/power/wakeup_stats.c */ +extern struct kobj_type wakeup_source_ktype; + +#endif /* CONFIG_PM_SLEEP */ diff --git a/drivers/base/power/wakeup.c b/drivers/base/power/wakeup.c index 5b2b6a05a4f3..620c87923a3e 100644 --- a/drivers/base/power/wakeup.c +++ b/drivers/base/power/wakeup.c @@ -102,6 +102,9 @@ struct wakeup_source *wakeup_source_create(const char *name) return NULL;
wakeup_source_prepare(ws, name ? kstrdup_const(name, GFP_KERNEL) : NULL); + + kobject_init(&ws->kobj, &wakeup_source_ktype); + return ws; } EXPORT_SYMBOL_GPL(wakeup_source_create); @@ -147,8 +150,7 @@ void wakeup_source_destroy(struct wakeup_source *ws)
__pm_relax(ws); wakeup_source_record(ws); - kfree_const(ws->name); - kfree(ws); + kobject_put(&ws->kobj); } EXPORT_SYMBOL_GPL(wakeup_source_destroy);
@@ -205,11 +207,17 @@ EXPORT_SYMBOL_GPL(wakeup_source_remove); struct wakeup_source *wakeup_source_register(const char *name) { struct wakeup_source *ws; + int ret;
ws = wakeup_source_create(name); - if (ws) + if (ws) { + ret = wakeup_source_sysfs_add(ws); + if (ret) { + kobject_put(&ws->kobj); + return NULL; + } wakeup_source_add(ws); - + } return ws; } EXPORT_SYMBOL_GPL(wakeup_source_register); diff --git a/drivers/base/power/wakeup_stats.c b/drivers/base/power/wakeup_stats.c new file mode 100644 index 000000000000..05ab8283ed66 --- /dev/null +++ b/drivers/base/power/wakeup_stats.c @@ -0,0 +1,194 @@ +// SPDX-License-Identifier: GPL-2.0 + +#include <linux/slab.h> + +#include "power.h" + +struct wakeup_source_attribute { + struct attribute attr; + ssize_t (*show)(struct wakeup_source *ws, + struct wakeup_source_attribute *attr, char *buf); + ssize_t (*store)(struct wakeup_source *ws, + struct wakeup_source_attribute *attr, const char *buf, + size_t count); +}; + +#define to_wakeup_source_obj(x) container_of(x, struct wakeup_source, kobj) +#define to_wakeup_source_attr(x) \ + container_of(x, struct wakeup_source_attribute, attr) + +static ssize_t wakeup_source_attr_show(struct kobject *kobj, + struct attribute *attr, + char *buf) +{ + struct wakeup_source_attribute *attribute; + struct wakeup_source *ws; + + ws = to_wakeup_source_obj(kobj); + attribute = to_wakeup_source_attr(attr); + + if (!attribute->show) + return -EIO; + + return attribute->show(ws, attribute, buf); +} + +static const struct sysfs_ops wakeup_source_sysfs_ops = { + .show = wakeup_source_attr_show, +}; + +static void wakeup_source_release(struct kobject *kobj) +{ + struct wakeup_source *ws; + + ws = to_wakeup_source_obj(kobj); + kfree_const(ws->name); + kfree(ws); +} + +static ssize_t wakeup_source_count_show(struct wakeup_source *ws, + struct wakeup_source_attribute *attr, + char *buf) +{ + unsigned long var; + + if (strcmp(attr->attr.name, "active_count") == 0) + var = ws->active_count; + else if (strcmp(attr->attr.name, "event_count") == 0) + var = ws->event_count; + else if (strcmp(attr->attr.name, "wakeup_count") == 0) + var = ws->wakeup_count; + else + var = ws->expire_count; + + return sprintf(buf, "%lu\n", var); +} + +#define wakeup_source_count_attr(_name) \ +static struct wakeup_source_attribute _name##_attr = { \ + .attr = { \ + .name = __stringify(_name), \ + .mode = 0444, \ + }, \ + .show = wakeup_source_count_show, \ +} + +wakeup_source_count_attr(active_count); +wakeup_source_count_attr(event_count); +wakeup_source_count_attr(wakeup_count); +wakeup_source_count_attr(expire_count); + +#define wakeup_source_attr(_name) \ +static struct wakeup_source_attribute _name##_attr = __ATTR_RO(_name) + +static ssize_t active_time_ms_show(struct wakeup_source *ws, + struct wakeup_source_attribute *attr, + char *buf) +{ + ktime_t active_time = + ws->active ? ktime_sub(ktime_get(), ws->last_time) : 0; + return sprintf(buf, "%lld\n", ktime_to_ms(active_time)); +} +wakeup_source_attr(active_time_ms); + +static ssize_t total_time_ms_show(struct wakeup_source *ws, + struct wakeup_source_attribute *attr, + char *buf) +{ + ktime_t active_time; + ktime_t total_time = ws->total_time; + + if (ws->active) { + active_time = ktime_sub(ktime_get(), ws->last_time); + total_time = ktime_add(total_time, active_time); + } + return sprintf(buf, "%lld\n", ktime_to_ms(total_time)); +} +wakeup_source_attr(total_time_ms); + +static ssize_t max_time_ms_show(struct wakeup_source *ws, + struct wakeup_source_attribute *attr, + char *buf) +{ + ktime_t active_time; + ktime_t max_time = ws->max_time; + + if (ws->active) { + active_time = ktime_sub(ktime_get(), ws->last_time); + if (active_time > max_time) + max_time = active_time; + } + return sprintf(buf, "%lld\n", ktime_to_ms(max_time)); +} +wakeup_source_attr(max_time_ms); + +static ssize_t last_change_ms_show(struct wakeup_source *ws, + struct wakeup_source_attribute *attr, + char *buf) +{ + return sprintf(buf, "%lld\n", ktime_to_ms(ws->last_time)); +} +wakeup_source_attr(last_change_ms); + +static ssize_t prevent_suspend_time_ms_show(struct wakeup_source *ws, + struct wakeup_source_attribute *attr, + char *buf) +{ + ktime_t prevent_sleep_time = ws->prevent_sleep_time; + + if (ws->active && ws->autosleep_enabled) { + prevent_sleep_time = ktime_add(prevent_sleep_time, + ktime_sub(ktime_get(), ws->start_prevent_time)); + } + return sprintf(buf, "%lld\n", ktime_to_ms(prevent_sleep_time)); +} +wakeup_source_attr(prevent_suspend_time_ms); + +static struct attribute *wakeup_source_default_attrs[] = { + &active_count_attr.attr, + &event_count_attr.attr, + &wakeup_count_attr.attr, + &expire_count_attr.attr, + &active_time_ms_attr.attr, + &total_time_ms_attr.attr, + &max_time_ms_attr.attr, + &last_change_ms_attr.attr, + &prevent_suspend_time_ms_attr.attr, + NULL, +}; +ATTRIBUTE_GROUPS(wakeup_source_default); + +struct kobj_type wakeup_source_ktype = { + .sysfs_ops = &wakeup_source_sysfs_ops, + .release = wakeup_source_release, + .default_groups = wakeup_source_default_groups, +}; + +#ifdef CONFIG_PM_SLEEP_STATS + +static struct kset *wakeup_source_kset; + +/** + * wakeup_source_sysfs_add - Add wakeup_source attributes to sysfs. + * @ws: Wakeup source to be exposed in sysfs. + */ +int wakeup_source_sysfs_add(struct wakeup_source *ws) +{ + ws->kobj.kset = wakeup_source_kset; + return kobject_add(&ws->kobj, NULL, "%s", ws->name); +} +EXPORT_SYMBOL_GPL(wakeup_source_sysfs_add); + +static int __init wakeup_sources_sysfs_init(void) +{ + wakeup_source_kset = kset_create_and_add("wakeup_sources", NULL, + power_kobj); + if (!wakeup_source_kset) + return -ENOMEM; + + return 0; +} + +postcore_initcall(wakeup_sources_sysfs_init); + +#endif /* !CONFIG_PM_SLEEP_STATS */ diff --git a/include/linux/pm_wakeup.h b/include/linux/pm_wakeup.h index ce57771fab9b..67026c52975c 100644 --- a/include/linux/pm_wakeup.h +++ b/include/linux/pm_wakeup.h @@ -57,6 +57,7 @@ struct wakeup_source { unsigned long wakeup_count; bool active:1; bool autosleep_enabled:1; + struct kobject kobj; };
#ifdef CONFIG_PM_SLEEP @@ -181,6 +182,20 @@ static inline void pm_wakeup_dev_event(struct device *dev, unsigned int msec,
#endif /* !CONFIG_PM_SLEEP */
+#ifdef CONFIG_PM_SLEEP_STATS + +/* drivers/base/power/wakeup_stats.c */ +extern int wakeup_source_sysfs_add(struct wakeup_source *ws); + +#else /* !CONFIG_PM_SLEEP_STATS */ + +static inline int wakeup_source_sysfs_add(struct wakeup_source *ws) +{ + return 0; +} + +#endif /* !CONFIG_PM_SLEEP_STATS */ + static inline void wakeup_source_init(struct wakeup_source *ws, const char *name) { diff --git a/kernel/power/Kconfig b/kernel/power/Kconfig index ff8592ddedee..4d258f34020b 100644 --- a/kernel/power/Kconfig +++ b/kernel/power/Kconfig @@ -195,6 +195,16 @@ config PM_SLEEP_DEBUG def_bool y depends on PM_DEBUG && PM_SLEEP
+config PM_SLEEP_STATS + bool "Wakeup sources statistics" + depends on PM_SLEEP + help + Expose wakeup sources statistics to user space via sysfs. Collected + statistics are located under /sys/power/wakeup_sources. For more + information, read <file:Documentation/ABI/testing/sysfs-power>. + + If in doubt, say N. + config DPM_WATCHDOG bool "Device suspend/resume watchdog" depends on PM_DEBUG && PSTORE && EXPERT diff --git a/kernel/power/wakelock.c b/kernel/power/wakelock.c index 4210152e56f0..eebbc537321f 100644 --- a/kernel/power/wakelock.c +++ b/kernel/power/wakelock.c @@ -27,7 +27,7 @@ static DEFINE_MUTEX(wakelocks_lock); struct wakelock { char *name; struct rb_node node; - struct wakeup_source ws; + struct wakeup_source *ws; #ifdef CONFIG_PM_WAKELOCKS_GC struct list_head lru; #endif @@ -46,7 +46,7 @@ ssize_t pm_show_wakelocks(char *buf, bool show_active)
for (node = rb_first(&wakelocks_tree); node; node = rb_next(node)) { wl = rb_entry(node, struct wakelock, node); - if (wl->ws.active == show_active) + if (wl->ws->active == show_active) str += scnprintf(str, end - str, "%s ", wl->name); } if (str > buf) @@ -112,18 +112,19 @@ static void __wakelocks_gc(struct work_struct *work) u64 idle_time_ns; bool active;
- spin_lock_irq(&wl->ws.lock); - idle_time_ns = ktime_to_ns(ktime_sub(now, wl->ws.last_time)); - active = wl->ws.active; - spin_unlock_irq(&wl->ws.lock); + spin_lock_irq(&wl->ws->lock); + idle_time_ns = ktime_to_ns(ktime_sub(now, wl->ws->last_time)); + active = wl->ws->active; + spin_unlock_irq(&wl->ws->lock);
if (idle_time_ns < ((u64)WL_GC_TIME_SEC * NSEC_PER_SEC)) break;
if (!active) { - wakeup_source_remove(&wl->ws); + wakeup_source_remove(wl->ws); rb_erase(&wl->node, &wakelocks_tree); list_del(&wl->lru); + kobject_put(&wl->ws->kobj); kfree(wl->name); kfree(wl); decrement_wakelocks_number(); @@ -153,6 +154,7 @@ static struct wakelock *wakelock_lookup_add(const char *name, size_t len, struct rb_node **node = &wakelocks_tree.rb_node; struct rb_node *parent = *node; struct wakelock *wl; + int ret;
while (*node) { int diff; @@ -187,9 +189,23 @@ static struct wakelock *wakelock_lookup_add(const char *name, size_t len, kfree(wl); return ERR_PTR(-ENOMEM); } - wl->ws.name = wl->name; - wl->ws.last_time = ktime_get(); - wakeup_source_add(&wl->ws); + + wl->ws = wakeup_source_create(wl->name); + if (!wl->ws) { + kfree(wl); + return ERR_PTR(-ENOMEM); + } + wl->ws->last_time = ktime_get(); + + ret = wakeup_source_sysfs_add(wl->ws); + if (ret) { + kobject_put(&wl->ws->kobj); + kfree(wl->name); + kfree(wl); + return ERR_PTR(ret); + } + + wakeup_source_add(wl->ws); rb_link_node(&wl->node, parent, node); rb_insert_color(&wl->node, &wakelocks_tree); wakelocks_lru_add(wl); @@ -233,9 +249,9 @@ int pm_wake_lock(const char *buf) u64 timeout_ms = timeout_ns + NSEC_PER_MSEC - 1;
do_div(timeout_ms, NSEC_PER_MSEC); - __pm_wakeup_event(&wl->ws, timeout_ms); + __pm_wakeup_event(wl->ws, timeout_ms); } else { - __pm_stay_awake(&wl->ws); + __pm_stay_awake(wl->ws); }
wakelocks_lru_most_recent(wl); @@ -271,7 +287,7 @@ int pm_wake_unlock(const char *buf) ret = PTR_ERR(wl); goto out; } - __pm_relax(&wl->ws); + __pm_relax(wl->ws);
wakelocks_lru_most_recent(wl); wakelocks_gc(); -- 2.22.0.410.gd8fdbe21b5-goog
| |