All of lore.kernel.org
 help / color / mirror / Atom feed
From: "Arve Hjønnevåg" <arve@android.com>
To: linux-pm@lists.linux-foundation.org
Cc: ncunningham@crca.org.au, u.luckas@road.de, swetland@google.com,
	r-woodruff2@ti.comrjw, @sisk.pl
Subject: [PATCH 6/8] PM: suspend_block: Add suspend_blocker stats
Date: Tue, 14 Apr 2009 18:41:30 -0700	[thread overview]
Message-ID: <1239759692-28617-7-git-send-email-arve@android.com> (raw)
In-Reply-To: <1239759692-28617-6-git-send-email-arve@android.com>

Report suspend block stats in /proc/suspend_blockers.

Signed-off-by: Arve Hjønnevåg <arve@android.com>
---
 include/linux/suspend_block.h |   11 +++
 kernel/power/Kconfig          |    7 ++
 kernel/power/suspend_block.c  |  201 ++++++++++++++++++++++++++++++++++++++++-
 3 files changed, 217 insertions(+), 2 deletions(-)

diff --git a/include/linux/suspend_block.h b/include/linux/suspend_block.h
index 73ac5f7..31225c5 100755
--- a/include/linux/suspend_block.h
+++ b/include/linux/suspend_block.h
@@ -17,6 +17,7 @@
 #define _LINUX_SUSPEND_BLOCK_H
 
 #include <linux/list.h>
+#include <linux/ktime.h>
 
 /* A suspend_blocker prevents the system from entering suspend when active.
  */
@@ -26,6 +27,16 @@ struct suspend_blocker {
 	struct list_head    link;
 	int                 flags;
 	const char         *name;
+#ifdef CONFIG_SUSPEND_BLOCK_STAT
+	struct {
+		int             count;
+		int             wakeup_count;
+		ktime_t         total_time;
+		ktime_t         prevent_suspend_time;
+		ktime_t         max_time;
+		ktime_t         last_time;
+	} stat;
+#endif
 #endif
 };
 
diff --git a/kernel/power/Kconfig b/kernel/power/Kconfig
index 2cf4c4d..3de0c5f 100644
--- a/kernel/power/Kconfig
+++ b/kernel/power/Kconfig
@@ -126,6 +126,13 @@ config SUSPEND_BLOCK
 	  /sys/power/request_state, the requested sleep state will be entered
 	  when no suspend_blockers are active.
 
+config SUSPEND_BLOCK_STAT
+	bool "Suspend block stats"
+	depends on SUSPEND_BLOCK
+	default y
+	---help---
+	  Report suspend block stats in /proc/suspend_blockers
+
 config USER_SUSPEND_BLOCK
 	bool "Userspace suspend blockers"
 	depends on SUSPEND_BLOCK
diff --git a/kernel/power/suspend_block.c b/kernel/power/suspend_block.c
index 697f52a..78d5880 100644
--- a/kernel/power/suspend_block.c
+++ b/kernel/power/suspend_block.c
@@ -18,6 +18,9 @@
 #include <linux/suspend.h>
 #include <linux/suspend_block.h>
 #include <linux/sysdev.h>
+#ifdef CONFIG_SUSPEND_BLOCK_STAT
+#include <linux/proc_fs.h>
+#endif
 #include "power.h"
 
 enum {
@@ -32,6 +35,7 @@ module_param_named(debug_mask, debug_mask, int, S_IRUGO | S_IWUSR | S_IWGRP);
 
 #define SB_INITIALIZED            (1U << 8)
 #define SB_ACTIVE                 (1U << 9)
+#define SB_PREVENTING_SUSPEND     (1U << 10)
 
 static DEFINE_SPINLOCK(list_lock);
 static DEFINE_SPINLOCK(state_lock);
@@ -42,6 +46,165 @@ struct workqueue_struct *suspend_work_queue;
 struct suspend_blocker main_suspend_blocker;
 static suspend_state_t requested_suspend_state = PM_SUSPEND_MEM;
 static bool enable_suspend_blockers;
+static struct suspend_blocker unknown_wakeup;
+
+#ifdef CONFIG_SUSPEND_BLOCK_STAT
+static struct suspend_blocker deleted_suspend_blockers;
+static ktime_t last_sleep_time_update;
+static bool wait_for_wakeup;
+
+static int print_blocker_stat(char *buf, struct suspend_blocker *blocker)
+{
+	int lock_count = blocker->stat.count;
+	ktime_t active_time = ktime_set(0, 0);
+	ktime_t total_time = blocker->stat.total_time;
+	ktime_t max_time = blocker->stat.max_time;
+	ktime_t prevent_suspend_time = blocker->stat.prevent_suspend_time;
+	if (blocker->flags & SB_ACTIVE) {
+		ktime_t now, add_time;
+		now = ktime_get();
+		add_time = ktime_sub(now, blocker->stat.last_time);
+		lock_count++;
+		active_time = add_time;
+		total_time = ktime_add(total_time, add_time);
+		if (blocker->flags & SB_PREVENTING_SUSPEND)
+			prevent_suspend_time = ktime_add(prevent_suspend_time,
+					ktime_sub(now, last_sleep_time_update));
+		if (add_time.tv64 > max_time.tv64)
+			max_time = add_time;
+	}
+
+	return sprintf(buf, "\"%s\"\t%d\t%d\t%lld\t%lld\t%lld\t%lld\t%lld\n",
+		       blocker->name, lock_count, blocker->stat.wakeup_count,
+		       ktime_to_ns(active_time), ktime_to_ns(total_time),
+		       ktime_to_ns(prevent_suspend_time), ktime_to_ns(max_time),
+		       ktime_to_ns(blocker->stat.last_time));
+}
+
+
+static int suspend_blocker_stats_read_proc(char *page, char **start, off_t off,
+			       int count, int *eof, void *data)
+{
+	unsigned long irqflags;
+	struct suspend_blocker *blocker;
+	int len = 0;
+	char *p = page;
+
+	spin_lock_irqsave(&list_lock, irqflags);
+
+	p += sprintf(p, "name\tcount\twake_count\tactive_since"
+		     "\ttotal_time\tsleep_time\tmax_time\tlast_change\n");
+	list_for_each_entry(blocker, &inactive_blockers, link) {
+		p += print_blocker_stat(p, blocker);
+	}
+	list_for_each_entry(blocker, &active_blockers, link)
+		p += print_blocker_stat(p, blocker);
+	spin_unlock_irqrestore(&list_lock, irqflags);
+
+	*start = page + off;
+
+	len = p - page;
+	if (len > off)
+		len -= off;
+	else
+		len = 0;
+
+	return len < count ? len  : count;
+}
+
+static void suspend_blocker_stat_init_locked(struct suspend_blocker *blocker)
+{
+	blocker->stat.count = 0;
+	blocker->stat.wakeup_count = 0;
+	blocker->stat.total_time = ktime_set(0, 0);
+	blocker->stat.prevent_suspend_time = ktime_set(0, 0);
+	blocker->stat.max_time = ktime_set(0, 0);
+	blocker->stat.last_time = ktime_set(0, 0);
+}
+
+static void suspend_blocker_stat_destroy_locked(struct suspend_blocker *bl)
+{
+	if (!bl->stat.count)
+		return;
+	deleted_suspend_blockers.stat.count += bl->stat.count;
+	deleted_suspend_blockers.stat.total_time = ktime_add(
+		deleted_suspend_blockers.stat.total_time, bl->stat.total_time);
+	deleted_suspend_blockers.stat.prevent_suspend_time = ktime_add(
+		deleted_suspend_blockers.stat.prevent_suspend_time,
+		bl->stat.prevent_suspend_time);
+	deleted_suspend_blockers.stat.max_time = ktime_add(
+		deleted_suspend_blockers.stat.max_time, bl->stat.max_time);
+}
+
+static void suspend_unblock_stat_locked(struct suspend_blocker *blocker)
+{
+	ktime_t duration;
+	ktime_t now;
+	if (!(blocker->flags & SB_ACTIVE))
+		return;
+	now = ktime_get();
+	blocker->stat.count++;
+	duration = ktime_sub(now, blocker->stat.last_time);
+	blocker->stat.total_time =
+		ktime_add(blocker->stat.total_time, duration);
+	if (ktime_to_ns(duration) > ktime_to_ns(blocker->stat.max_time))
+		blocker->stat.max_time = duration;
+	blocker->stat.last_time = ktime_get();
+	if (blocker->flags & SB_PREVENTING_SUSPEND) {
+		duration = ktime_sub(now, last_sleep_time_update);
+		blocker->stat.prevent_suspend_time = ktime_add(
+			blocker->stat.prevent_suspend_time, duration);
+		blocker->flags &= ~SB_PREVENTING_SUSPEND;
+	}
+}
+
+static void suspend_block_stat_locked(struct suspend_blocker *blocker)
+{
+	if (wait_for_wakeup) {
+		if (debug_mask & DEBUG_WAKEUP)
+			pr_info("wakeup suspend blocker: %s\n", blocker->name);
+		wait_for_wakeup = false;
+		blocker->stat.wakeup_count++;
+	}
+	if (!(blocker->flags & SB_ACTIVE))
+		blocker->stat.last_time = ktime_get();
+}
+
+static void update_sleep_wait_stats_locked(bool done)
+{
+	struct suspend_blocker *blocker;
+	ktime_t now, elapsed, add;
+
+	now = ktime_get();
+	elapsed = ktime_sub(now, last_sleep_time_update);
+	list_for_each_entry(blocker, &active_blockers, link) {
+		if (blocker->flags & SB_PREVENTING_SUSPEND) {
+			add = elapsed;
+			blocker->stat.prevent_suspend_time = ktime_add(
+				blocker->stat.prevent_suspend_time, add);
+		}
+		if (done)
+			blocker->flags &= ~SB_PREVENTING_SUSPEND;
+		else
+			blocker->flags |= SB_PREVENTING_SUSPEND;
+	}
+	last_sleep_time_update = now;
+}
+
+#else
+
+static inline void suspend_blocker_stat_init_locked(
+					struct suspend_blocker *blocker) {}
+static inline void suspend_blocker_stat_destroy_locked(
+					struct suspend_blocker *blocker) {}
+static inline void suspend_block_stat_locked(
+					struct suspend_blocker *blocker) {}
+static inline void suspend_unblock_stat_locked(
+					struct suspend_blocker *blocker) {}
+static inline void update_sleep_wait_stats_locked(bool done) {}
+
+#endif
+
 
 static void print_active_blockers_locked(void)
 {
@@ -65,7 +228,6 @@ static void suspend_worker(struct work_struct *work)
 
 	enable_suspend_blockers = 1;
 
-retry:
 	if (suspend_is_blocked()) {
 		if (debug_mask & DEBUG_SUSPEND)
 			pr_info("suspend: abort suspend\n");
@@ -89,7 +251,8 @@ retry:
 	if (current_event_num == entry_event_num) {
 		if (debug_mask & DEBUG_SUSPEND)
 			pr_info("suspend: pm_suspend returned with no event\n");
-		goto retry;
+		suspend_block(&unknown_wakeup);
+		suspend_unblock(&unknown_wakeup);
 	}
 abort:
 	enable_suspend_blockers = 0;
@@ -99,6 +262,9 @@ static DECLARE_WORK(suspend_work, suspend_worker);
 static int suspend_block_suspend(struct sys_device *dev, pm_message_t state)
 {
 	int ret = suspend_is_blocked() ? -EAGAIN : 0;
+#ifdef CONFIG_SUSPEND_BLOCK_STAT
+	wait_for_wakeup = true;
+#endif
 	if (debug_mask & DEBUG_SUSPEND)
 		pr_info("suspend_block_suspend return %d\n", ret);
 	return ret;
@@ -133,6 +299,7 @@ void suspend_blocker_init(struct suspend_blocker *blocker, const char *name)
 	INIT_LIST_HEAD(&blocker->link);
 
 	spin_lock_irqsave(&list_lock, irqflags);
+	suspend_blocker_stat_init_locked(blocker);
 	list_add(&blocker->link, &inactive_blockers);
 	spin_unlock_irqrestore(&list_lock, irqflags);
 }
@@ -148,6 +315,7 @@ void suspend_blocker_destroy(struct suspend_blocker *blocker)
 	if (debug_mask & DEBUG_SUSPEND_BLOCKER)
 		pr_info("suspend_blocker_destroy name=%s\n", blocker->name);
 	spin_lock_irqsave(&list_lock, irqflags);
+	suspend_blocker_stat_destroy_locked(blocker);
 	blocker->flags &= ~SB_INITIALIZED;
 	list_del(&blocker->link);
 	if ((blocker->flags & SB_ACTIVE) && list_empty(&active_blockers))
@@ -167,6 +335,7 @@ void suspend_block(struct suspend_blocker *blocker)
 	spin_lock_irqsave(&list_lock, irqflags);
 	BUG_ON(!(blocker->flags & SB_INITIALIZED));
 
+	suspend_block_stat_locked(blocker);
 	blocker->flags |= SB_ACTIVE;
 	list_del(&blocker->link);
 	if (debug_mask & DEBUG_SUSPEND_BLOCKER)
@@ -174,6 +343,10 @@ void suspend_block(struct suspend_blocker *blocker)
 	list_add(&blocker->link, &active_blockers);
 
 	current_event_num++;
+	if (blocker == &main_suspend_blocker)
+		update_sleep_wait_stats_locked(true);
+	else if (!is_blocking_suspend(&main_suspend_blocker))
+		update_sleep_wait_stats_locked(false);
 	spin_unlock_irqrestore(&list_lock, irqflags);
 }
 EXPORT_SYMBOL(suspend_block);
@@ -190,6 +363,8 @@ void suspend_unblock(struct suspend_blocker *blocker)
 
 	spin_lock_irqsave(&list_lock, irqflags);
 
+	suspend_unblock_stat_locked(blocker);
+
 	if (debug_mask & DEBUG_SUSPEND_BLOCKER)
 		pr_info("suspend_unblock: %s\n", blocker->name);
 	list_del(&blocker->link);
@@ -201,6 +376,7 @@ void suspend_unblock(struct suspend_blocker *blocker)
 	if (blocker == &main_suspend_blocker) {
 		if (debug_mask & DEBUG_SUSPEND)
 			print_active_blockers_locked();
+		update_sleep_wait_stats_locked(false);
 	}
 	spin_unlock_irqrestore(&list_lock, irqflags);
 }
@@ -245,8 +421,13 @@ static int __init suspend_block_init(void)
 {
 	int ret;
 
+#ifdef CONFIG_SUSPEND_BLOCK_STAT
+	suspend_blocker_init(&deleted_suspend_blockers,
+				"deleted_suspend_blockers");
+#endif
 	suspend_blocker_init(&main_suspend_blocker, "main");
 	suspend_block(&main_suspend_blocker);
+	suspend_blocker_init(&unknown_wakeup, "unknown_wakeups");
 
 	ret = sysdev_class_register(&suspend_block_sysclass);
 	if (ret) {
@@ -265,6 +446,11 @@ static int __init suspend_block_init(void)
 		goto err_suspend_work_queue;
 	}
 
+#ifdef CONFIG_SUSPEND_BLOCK_STAT
+	create_proc_read_entry("suspend_blockers", S_IRUGO, NULL,
+				suspend_blocker_stats_read_proc, NULL);
+#endif
+
 	return 0;
 
 err_suspend_work_queue:
@@ -272,16 +458,27 @@ err_suspend_work_queue:
 err_sysdev_register:
 	sysdev_class_unregister(&suspend_block_sysclass);
 err_sysdev_class_register:
+	suspend_blocker_destroy(&unknown_wakeup);
 	suspend_blocker_destroy(&main_suspend_blocker);
+#ifdef CONFIG_SUSPEND_BLOCK_STAT
+	suspend_blocker_destroy(&deleted_suspend_blockers);
+#endif
 	return ret;
 }
 
 static void  __exit suspend_block_exit(void)
 {
+#ifdef CONFIG_SUSPEND_BLOCK_STAT
+	remove_proc_entry("suspend_blockers", NULL);
+#endif
 	destroy_workqueue(suspend_work_queue);
 	sysdev_unregister(&suspend_block_sysdev);
 	sysdev_class_unregister(&suspend_block_sysclass);
+	suspend_blocker_destroy(&unknown_wakeup);
 	suspend_blocker_destroy(&main_suspend_blocker);
+#ifdef CONFIG_SUSPEND_BLOCK_STAT
+	suspend_blocker_destroy(&deleted_suspend_blockers);
+#endif
 }
 
 core_initcall(suspend_block_init);
-- 
1.6.1

_______________________________________________
linux-pm mailing list
linux-pm@lists.linux-foundation.org
https://lists.linux-foundation.org/mailman/listinfo/linux-pm

  reply	other threads:[~2009-04-15  1:41 UTC|newest]

Thread overview: 32+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2009-04-15  1:41 [RFC][PATCH 0/8] Suspend block api Arve Hjønnevåg
2009-04-15  1:41 ` [PATCH 1/8] PM: Add suspend " Arve Hjønnevåg
2009-04-15  1:41   ` [PATCH 2/8] PM: suspend_block: Add driver to access suspend blockers from user-space Arve Hjønnevåg
2009-04-15  1:41     ` [PATCH 3/8] PM: suspend_block: Abort task freezing if a suspend_blocker is active Arve Hjønnevåg
2009-04-15  1:41       ` [PATCH 4/8] Input: Block suspend while event queue is not empty Arve Hjønnevåg
2009-04-15  1:41         ` [PATCH 5/8] PM: suspend_block: Switch to list of active and inactive suspend blockers Arve Hjønnevåg
2009-04-15  1:41           ` Arve Hjønnevåg [this message]
2009-04-15  1:41             ` [PATCH 7/8] PM: suspend_block: Add timeout support Arve Hjønnevåg
2009-04-15  1:41               ` [PATCH 8/8] PM: suspend_block: Add timeout support to user-space suspend_blockers Arve Hjønnevåg
2009-04-29 22:56       ` [PATCH 3/8] PM: suspend_block: Abort task freezing if a suspend_blocker is active Rafael J. Wysocki
2009-04-29 22:52     ` [PATCH 2/8] PM: suspend_block: Add driver to access suspend blockers from user-space Rafael J. Wysocki
2009-04-15 15:29   ` [PATCH 1/8] PM: Add suspend block api Alan Stern
2009-04-15 19:08     ` mark gross
2009-04-16  0:40       ` Arve Hjønnevåg
2009-04-16  0:34     ` Arve Hjønnevåg
2009-04-15 22:31   ` mark gross
2009-04-16  1:45     ` Arve Hjønnevåg
2009-04-16 17:49       ` mark gross
2009-04-20  9:29   ` Pavel Machek
2009-04-21  4:44     ` Arve Hjønnevåg
2009-04-24 20:59       ` Pavel Machek
2009-04-29 21:24         ` Rafael J. Wysocki
2009-04-29 22:52           ` Arve Hjønnevåg
2009-04-29 22:34   ` Rafael J. Wysocki
2009-04-29 23:45     ` Arve Hjønnevåg
2009-04-30  0:49     ` Arve Hjønnevåg
2009-04-26  9:42       ` Pavel Machek
2009-05-02 12:17         ` Rafael J. Wysocki
2009-05-02 12:14       ` Rafael J. Wysocki
2009-05-02 20:51         ` Pavel Machek
2009-05-05  3:48         ` Arve Hjønnevåg
2009-04-15 23:04 ` [RFC][PATCH 0/8] Suspend " Rafael J. Wysocki

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=1239759692-28617-7-git-send-email-arve@android.com \
    --to=arve@android.com \
    --cc=linux-pm@lists.linux-foundation.org \
    --cc=ncunningham@crca.org.au \
    --cc=r-woodruff2@ti.comrjw \
    --cc=swetland@google.com \
    --cc=u.luckas@road.de \
    /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.