From: Tri Vo <trong@android.com>
To: rjw@rjwysocki.net, gregkh@linuxfoundation.org, viresh.kumar@linaro.org
Cc: rafael@kernel.org, hridya@google.com, sspatil@google.com,
kaleshsingh@google.com, linux-kernel@vger.kernel.org,
linux-pm@vger.kernel.org, kernel-team@android.com,
Tri Vo <trong@android.com>
Subject: [PATCH v2] PM / wakeup: show wakeup sources stats in sysfs
Date: Thu, 27 Jun 2019 15:53:35 -0700 [thread overview]
Message-ID: <20190627225335.72107-1-trong@android.com> (raw)
In-Reply-To: <20190627000412.GA527@kroah.com>
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
next prev parent reply other threads:[~2019-06-27 22:54 UTC|newest]
Thread overview: 24+ messages / expand[flat|nested] mbox.gz Atom feed top
2019-06-26 0:54 [PATCH] PM / wakeup: show wakeup sources stats in sysfs Tri Vo
2019-06-26 0:59 ` Tri Vo
2019-06-26 1:12 ` Greg KH
2019-06-26 1:33 ` Tri Vo
2019-06-26 1:46 ` Greg KH
2019-06-26 22:26 ` Tri Vo
2019-06-26 22:48 ` Tri Vo
2019-06-27 0:04 ` Greg KH
2019-06-27 22:53 ` Tri Vo [this message]
2019-06-28 15:10 ` [PATCH v2] " Greg KH
2019-07-04 10:31 ` Rafael J. Wysocki
2019-07-08 3:33 ` Tri Vo
2019-07-15 20:11 ` [PATCH v3] " Tri Vo
2019-07-15 20:36 ` Greg KH
2019-07-15 21:43 ` [PATCH v4] " Tri Vo
2019-07-15 21:48 ` Rafael J. Wysocki
2019-07-16 2:11 ` Greg Kroah-Hartman
2019-07-16 4:16 ` Tri Vo
2019-07-16 8:30 ` Rafael J. Wysocki
2019-07-16 8:39 ` Greg Kroah-Hartman
2019-07-16 9:36 ` Rafael J. Wysocki
2019-07-15 21:48 ` [PATCH v3] " Tri Vo
2019-07-16 2:12 ` Greg KH
2019-06-27 22:57 ` [PATCH] " Tri Vo
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=20190627225335.72107-1-trong@android.com \
--to=trong@android.com \
--cc=gregkh@linuxfoundation.org \
--cc=hridya@google.com \
--cc=kaleshsingh@google.com \
--cc=kernel-team@android.com \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-pm@vger.kernel.org \
--cc=rafael@kernel.org \
--cc=rjw@rjwysocki.net \
--cc=sspatil@google.com \
--cc=viresh.kumar@linaro.org \
/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 an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.