Skip to contentSkip to conversation

lock contention tracking -v2

PATCH In review Peter Zijlstra 4 participants 16 messages May 23, 2007

Subsystems (3)
lore
Share & tools
lore
#0 lore raw

cover lock contention tracking -v2

Use the lockdep infrastructure to track lock contention and other lock statistics.

It tracks lock contention events, and the first four unique call-sites that encountered contention.

It also measures lock wait-time and hold-time in nanoseconds. The minimum and maximum times are tracked, as well as a total (which together with the number of event can give the avg).

All statistics are done per lock class, per write (exclusive state) and per read (shared state).

The statistics are collected per-cpu, so that the collection overhead is minimized via having no global cachemisses.

This new lock statistics feature is independent of the lock dependency checking traditionally done by lockdep; it just shares the lock tracking code. It is also possible to enable both and runtime disabled either component - thereby avoiding the O(n^2) lock chain walks for instance.

TODO:
- create new output format

#1 lore raw

PATCH 1/7 fix raw_spinlock_t vs lockdep

raw_spinlock_t should not use lockdep (and doesn't) since lockdep itself relies on it.

---
 include/linux/spinlock_types.h    |    4 ++--
 include/linux/spinlock_types_up.h |    9 +--------
 2 files changed, 3 insertions(+), 10 deletions(-)
include/linux/spinlock_types_up.h +1 -8
Index: linux-2.6/include/linux/spinlock_types_up.h
===================================================================
@@ -12,14 +12,10 @@
  * Released under the General Public License (GPL).
  */
 
-#if defined(CONFIG_DEBUG_SPINLOCK) || \
-	defined(CONFIG_DEBUG_LOCK_ALLOC)
+#ifdef CONFIG_DEBUG_SPINLOCK
 
 typedef struct {
 	volatile unsigned int slock;
-#ifdef CONFIG_DEBUG_LOCK_ALLOC
-	struct lockdep_map dep_map;
-#endif
 } raw_spinlock_t;
 
 #define __RAW_SPIN_LOCK_UNLOCKED { 1 }
@@ -34,9 +30,6 @@ typedef struct { } raw_spinlock_t;
 
 typedef struct {
 	/* no debug version on UP */
-#ifdef CONFIG_DEBUG_LOCK_ALLOC
-	struct lockdep_map dep_map;
-#endif
 } raw_rwlock_t;
 
 #define __RAW_RW_LOCK_UNLOCKED { }
include/linux/spinlock_types.h +2 -2
Index: linux-2.6/include/linux/spinlock_types.h
===================================================================
@@ -9,14 +9,14 @@
  * Released under the General Public License (GPL).
  */
 
-#include <linux/lockdep.h>
-
 #if defined(CONFIG_SMP)
 # include <asm/spinlock_types.h>
 #else
 # include <linux/spinlock_types_up.h>
 #endif
 
+#include <linux/lockdep.h>
+
 typedef struct {
 	raw_spinlock_t raw_lock;
 #if defined(CONFIG_PREEMPT) && defined(CONFIG_SMP)
#2 lore raw

PATCH 2/7 lockdep: isolate lock graph walking

In reply to Peter Zijlstra

Put the lock dependancy tracking bits under CONFIG_PROVE_LOCKING This way we can reuse the hold_lock part of lockdep for other purposes.

---
 kernel/lockdep.c |   15 ++++++++++++++-
 1 file changed, 14 insertions(+), 1 deletion(-)
kernel/lockdep.c +14 -1
Index: linux-2.6/kernel/lockdep.c
===================================================================
@@ -95,6 +95,7 @@ static int lockdep_initialized;
 unsigned long nr_list_entries;
 static struct lock_list list_entries[MAX_LOCKDEP_ENTRIES];
 
+#ifdef CONFIG_PROVE_LOCKING
 /*
  * Allocate a lockdep entry. (assumes the graph_lock held, returns
  * with NULL on failure)
@@ -111,6 +112,7 @@ static struct lock_list *alloc_list_entr
 	}
 	return list_entries + nr_list_entries++;
 }
+#endif
 
 /*
  * All data structures here are protected by the global debug_lock.
@@ -140,7 +142,9 @@ LIST_HEAD(all_lock_classes);
 static struct list_head classhash_table[CLASSHASH_SIZE];
 
 unsigned long nr_lock_chains;
+#ifdef CONFIG_PROVE_LOCKING
 static struct lock_chain lock_chains[MAX_LOCKDEP_CHAINS];
+#endif
 
 /*
  * We put the lock dependency chains into a hash-table as well, to cache
@@ -482,6 +486,7 @@ static void print_lock_dependencies(stru
 	}
 }
 
+#ifdef CONFIG_PROVE_LOCKING
 /*
  * Add a new dependency to the head of the list:
  */
@@ -541,6 +546,7 @@ print_circular_bug_entry(struct lock_lis
 
 	return 0;
 }
+#endif
 
 static void print_kernel_version(void)
 {
@@ -549,6 +555,7 @@ static void print_kernel_version(void)
 		init_utsname()->version);
 }
 
+#ifdef CONFIG_PROVE_LOCKING
 /*
  * When a circular dependency is detected, print the
  * header first:
@@ -639,6 +646,7 @@ check_noncircular(struct lock_class *sou
 	}
 	return 1;
 }
+#endif
 
 static int very_verbose(struct lock_class *class)
 {
@@ -823,6 +831,7 @@ check_usage(struct task_struct *curr, st
 
 #endif
 
+#ifdef CONFIG_PROVE_LOCKING
 static int
 print_deadlock_bug(struct task_struct *curr, struct held_lock *prev,
 		   struct held_lock *next)
@@ -1087,7 +1096,7 @@ out_bug:
 
 	return 0;
 }
-
+#endif
 
 /*
  * Is this the address of a static object:
@@ -1307,6 +1316,7 @@ out_unlock_set:
 	return class;
 }
 
+#ifdef CONFIG_PROVE_LOCKING
 /*
  * Look up a dependency chain. If the key is not present yet then
  * add it and return 1 - in this case the new dependency chain is
@@ -1381,6 +1391,7 @@ cache_hit:
 
 	return 1;
 }
+#endif
 
 /*
  * We are building curr_chain_key incrementally, so double-check
@@ -2168,6 +2179,7 @@ out_calc_hash:
 	chain_key = iterate_chain_key(chain_key, id);
 	curr->curr_chain_key = chain_key;
 
+#ifdef CONFIG_PROVE_LOCKING
 	/*
 	 * Trylock needs to maintain the stack of held locks, but it
 	 * does not add new dependencies, because trylock can be done
@@ -2214,6 +2226,7 @@ out_calc_hash:
 		/* after lookup_chain_cache(): */
 		if (unlikely(!debug_locks))
 			return 0;
+#endif
 
 	curr->lockdep_depth++;
 	check_chain_key(curr);
#3 lore raw

PATCH 3/7 lockdep: lock contention tracking

In reply to Peter Zijlstra

Count lock contention events per lock class. Additionally track the first four callsites that resulted in the contention.

/proc/lock_contentions - gives a single line per lock class:

<class name> <(write) contentions> <read contentions>

Followed by another three columns per contention point:

<contentions from this point> <IP> <symbol name>

writing a '0' to the file will clear the stats.

example output:

[root@taijtu ~]# cat /proc/lock_contentions | sort -rnk 2 | head
dcache_lock: 3000 0 [618] [<ffffffff8033badd>] _atomic_dec_and_lock+0x39/0x58 [17] [<ffffffff802d6dce>] sysfs_open_file+0x28/0x25a [160] [<ffffffff802a5b61>] d_instantiate+0x2a/0xad [52] [<ffffffff8029d926>] __link_path_walk+0x270/0xde9 &inode->i_mutex: 235 0 [15] [<ffffffff8029b053>] pipe_read+0x74/0x39d [23] [<ffffffff8029a9f8>] pipe_wait+0x86/0x8d [105] [<ffffffff8029bc9a>] do_lookup+0x81/0x1ae [85] [<ffffffff802a0be7>] vfs_readdir+0x5c/0xa9 &zone->lru_lock: 141 0 [11] [<ffffffff8027366b>] activate_page+0x37/0xd6 [75] [<ffffffff80273327>] release_pages+0xb2/0x16c [42] [<ffffffff80273443>] __pagevec_lru_add_active+0x62/0xe5 [12] [<ffffffff80273529>] __pagevec_lru_add+0x63/0xe2 &rq->rq_lock_key: 93 0 [66] [<ffffffff8022b202>] task_rq_lock+0x42/0x74 [24] [<ffffffff80461f2a>] __sched_text_start+0x17a/0x869 [2] [<ffffffff8022a58e>] double_rq_lock+0x38/0x3d [1] [<ffffffff8022b1af>] double_lock_balance+0x41/0x52 &rq->rq_lock_key: 59 0 [15] [<ffffffff80461f2a>] __sched_text_start+0x17a/0x869 [27] [<ffffffff8022b202>] task_rq_lock+0x42/0x74 [17] [<ffffffff80462090>] __sched_text_start+0x2e0/0x869
files_lock: 57 0 [28] [<ffffffff8029581d>] file_move+0x1d/0x51 [29] [<ffffffff802957dc>] file_kill+0x15/0x39
&inode->i_data.i_mmap_lock: 52 0 [16] [<ffffffff8023149f>] copy_process+0xe10/0x1684 [5] [<ffffffff8027e38d>] vma_link+0x40/0x107 [20] [<ffffffff8027d866>] unlink_file_vma+0x2c/0x53 [11] [<ffffffff8027da53>] vma_adjust+0x154/0x452 &q->__queue_lock: 47 0 [16] [<ffffffff8033348a>] __make_request+0x5e/0x663 [4] [<ffffffff803339e8>] __make_request+0x5bc/0x663 [2] [<ffffffff803320c3>] generic_unplug_device+0x18/0x25 [1] [<ffffffff803320bb>] generic_unplug_device+0x10/0x25 &dentry->d_lock: 26 0 [22] [<ffffffff802a58be>] __d_lookup+0x7d/0x136 [4] [<ffffffff802ccd61>] dnotify_parent+0x1f/0x6d
vfsmount_lock: 17 0 [16] [<ffffffff802ab31d>] lookup_mnt+0x19/0x4c [1] [<ffffffff802a51df>] __d_path+0xae/0x14c

---
 include/linux/lockdep.h |   33 ++++++++++++++++
 kernel/lockdep.c        |   95 ++++++++++++++++++++++++++++++++++++++++++++++++
 kernel/lockdep_proc.c   |   79 +++++++++++++++++++++++++++++++++++++++
 kernel/mutex.c          |    7 +++
 kernel/rwsem.c          |    8 ++--
 kernel/spinlock.c       |   28 +++++++-------
 lib/Kconfig.debug       |   13 ++++++
 7 files changed, 245 insertions(+), 18 deletions(-)
include/linux/lockdep.h +33 -0
Index: linux-2.6/include/linux/lockdep.h
===================================================================
@@ -16,6 +16,7 @@ struct task_struct;
 #include <linux/list.h>
 #include <linux/debug_locks.h>
 #include <linux/stacktrace.h>
+#include <asm/atomic.h>
 
 /*
  * Lock-class usage-state bits:
@@ -71,6 +72,11 @@ struct lock_class_key {
 	struct lockdep_subclass_key	subkeys[MAX_LOCKDEP_SUBCLASSES];
 };
 
+struct lock_contention_point {
+	unsigned long ip;
+	atomic_t count;
+};
+
 /*
  * The lock-class itself:
  */
@@ -114,6 +120,12 @@ struct lock_class {
 
 	const char			*name;
 	int				name_version;
+
+#ifdef CONFIG_LOCK_STAT
+	atomic_t			read_contentions;
+	atomic_t			write_contentions;
+	struct lock_contention_point	contention_point[4];
+#endif
 };
 
 /*
@@ -281,6 +293,27 @@ struct lock_class_key { };
 
 #endif /* !LOCKDEP */
 
+#ifdef CONFIG_LOCK_STAT
+
+extern void lock_contended(struct lockdep_map *lock, unsigned long ip);
+
+#define LOCK_CONTENDED(_lock, try, lock)			\
+do {								\
+	if (!try(_lock)) {					\
+		lock_contended(&(_lock)->dep_map, _RET_IP_);	\
+		lock(_lock);					\
+	}							\
+} while (0)
+
+#else /* CONFIG_LOCK_STAT */
+
+#define lock_contended(l, i)			do { } while (0)
+
+#define LOCK_CONTENDED(_lock, try, lock) \
+	lock(_lock)
+
+#endif /* CONFIG_LOCK_STAT */
+
 #if defined(CONFIG_TRACE_IRQFLAGS) && defined(CONFIG_GENERIC_HARDIRQS)
 extern void early_init_irq_lock_class(void);
 #else
kernel/lockdep.c +95 -0

113 lines not on this page · raw message

kernel/mutex.c +7 -0
Index: linux-2.6/kernel/mutex.c
===================================================================
@@ -139,6 +139,12 @@ __mutex_lock_common(struct mutex *lock, 
 	list_add_tail(&waiter.list, &lock->wait_list);
 	waiter.task = task;
 
+	old_val = atomic_xchg(&lock->count, -1);
+	if (old_val == 1)
+		goto done;
+
+	lock_contended(&lock->dep_map, _RET_IP_);
+
 	for (;;) {
 		/*
 		 * Lets try to take the lock again - this is needed even if
@@ -174,6 +180,7 @@ __mutex_lock_common(struct mutex *lock, 
 		spin_lock_mutex(&lock->wait_lock, flags);
 	}
 
+done:
 	/* got the lock - rejoice! */
 	mutex_remove_waiter(lock, &waiter, task_thread_info(task));
 	debug_mutex_set_owner(lock, task_thread_info(task));
kernel/lockdep_proc.c +79 -0

97 lines not on this page · raw message

kernel/rwsem.c +4 -4
Index: linux-2.6/kernel/rwsem.c
===================================================================
@@ -20,7 +20,7 @@ void down_read(struct rw_semaphore *sem)
 	might_sleep();
 	rwsem_acquire_read(&sem->dep_map, 0, 0, _RET_IP_);
 
-	__down_read(sem);
+	LOCK_CONTENDED(sem, __down_read_trylock, __down_read);
 }
 
 EXPORT_SYMBOL(down_read);
@@ -47,7 +47,7 @@ void down_write(struct rw_semaphore *sem
 	might_sleep();
 	rwsem_acquire(&sem->dep_map, 0, 0, _RET_IP_);
 
-	__down_write(sem);
+	LOCK_CONTENDED(sem, __down_write_trylock, __down_write);
 }
 
 EXPORT_SYMBOL(down_write);
@@ -111,7 +111,7 @@ void down_read_nested(struct rw_semaphor
 	might_sleep();
 	rwsem_acquire_read(&sem->dep_map, subclass, 0, _RET_IP_);
 
-	__down_read(sem);
+	LOCK_CONTENDED(sem, __down_read_trylock, __down_read);
 }
 
 EXPORT_SYMBOL(down_read_nested);
@@ -130,7 +130,7 @@ void down_write_nested(struct rw_semapho
 	might_sleep();
 	rwsem_acquire(&sem->dep_map, subclass, 0, _RET_IP_);
 
-	__down_write_nested(sem, subclass);
+	LOCK_CONTENDED(sem, __down_write_trylock, __down_write);
 }
 
 EXPORT_SYMBOL(down_write_nested);
kernel/spinlock.c +14 -14
Index: linux-2.6/kernel/spinlock.c
===================================================================
@@ -72,7 +72,7 @@ void __lockfunc _read_lock(rwlock_t *loc
 {
 	preempt_disable();
 	rwlock_acquire_read(&lock->dep_map, 0, 0, _RET_IP_);
-	_raw_read_lock(lock);
+	LOCK_CONTENDED(lock, _raw_read_trylock, _raw_read_lock);
 }
 EXPORT_SYMBOL(_read_lock);
 
@@ -89,7 +89,7 @@ unsigned long __lockfunc _spin_lock_irqs
 	 * that interrupts are not re-enabled during lock-acquire:
 	 */
 #ifdef CONFIG_PROVE_LOCKING
-	_raw_spin_lock(lock);
+	LOCK_CONTENDED(lock, _raw_spin_trylock, _raw_spin_lock);
 #else
 	_raw_spin_lock_flags(lock, &flags);
 #endif
@@ -102,7 +102,7 @@ void __lockfunc _spin_lock_irq(spinlock_
 	local_irq_disable();
 	preempt_disable();
 	spin_acquire(&lock->dep_map, 0, 0, _RET_IP_);
-	_raw_spin_lock(lock);
+	LOCK_CONTENDED(lock, _raw_spin_trylock, _raw_spin_lock);
 }
 EXPORT_SYMBOL(_spin_lock_irq);
 
@@ -111,7 +111,7 @@ void __lockfunc _spin_lock_bh(spinlock_t
 	local_bh_disable();
 	preempt_disable();
 	spin_acquire(&lock->dep_map, 0, 0, _RET_IP_);
-	_raw_spin_lock(lock);
+	LOCK_CONTENDED(lock, _raw_spin_trylock, _raw_spin_lock);
 }
 EXPORT_SYMBOL(_spin_lock_bh);
 
@@ -122,7 +122,7 @@ unsigned long __lockfunc _read_lock_irqs
 	local_irq_save(flags);
 	preempt_disable();
 	rwlock_acquire_read(&lock->dep_map, 0, 0, _RET_IP_);
-	_raw_read_lock(lock);
+	LOCK_CONTENDED(lock, _raw_read_trylock, _raw_read_lock);
 	return flags;
 }
 EXPORT_SYMBOL(_read_lock_irqsave);
@@ -132,7 +132,7 @@ void __lockfunc _read_lock_irq(rwlock_t 
 	local_irq_disable();
 	preempt_disable();
 	rwlock_acquire_read(&lock->dep_map, 0, 0, _RET_IP_);
-	_raw_read_lock(lock);
+	LOCK_CONTENDED(lock, _raw_read_trylock, _raw_read_lock);
 }
 EXPORT_SYMBOL(_read_lock_irq);
 
@@ -141,7 +141,7 @@ void __lockfunc _read_lock_bh(rwlock_t *
 	local_bh_disable();
 	preempt_disable();
 	rwlock_acquire_read(&lock->dep_map, 0, 0, _RET_IP_);
-	_raw_read_lock(lock);
+	LOCK_CONTENDED(lock, _raw_read_trylock, _raw_read_lock);
 }
 EXPORT_SYMBOL(_read_lock_bh);
 
@@ -152,7 +152,7 @@ unsigned long __lockfunc _write_lock_irq
 	local_irq_save(flags);
 	preempt_disable();
 	rwlock_acquire(&lock->dep_map, 0, 0, _RET_IP_);
-	_raw_write_lock(lock);
+	LOCK_CONTENDED(lock, _raw_write_trylock, _raw_write_lock);
 	return flags;
 }
 EXPORT_SYMBOL(_write_lock_irqsave);
@@ -162,7 +162,7 @@ void __lockfunc _write_lock_irq(rwlock_t
 	local_irq_disable();
 	preempt_disable();
 	rwlock_acquire(&lock->dep_map, 0, 0, _RET_IP_);
-	_raw_write_lock(lock);
+	LOCK_CONTENDED(lock, _raw_write_trylock, _raw_write_lock);
 }
 EXPORT_SYMBOL(_write_lock_irq);
 
@@ -171,7 +171,7 @@ void __lockfunc _write_lock_bh(rwlock_t 
 	local_bh_disable();
 	preempt_disable();
 	rwlock_acquire(&lock->dep_map, 0, 0, _RET_IP_);
-	_raw_write_lock(lock);
+	LOCK_CONTENDED(lock, _raw_write_trylock, _raw_write_lock);
 }
 EXPORT_SYMBOL(_write_lock_bh);
 
@@ -179,7 +179,7 @@ void __lockfunc _spin_lock(spinlock_t *l
 {
 	preempt_disable();
 	spin_acquire(&lock->dep_map, 0, 0, _RET_IP_);
-	_raw_spin_lock(lock);
+	LOCK_CONTENDED(lock, _raw_spin_trylock, _raw_spin_lock);
 }
 
 EXPORT_SYMBOL(_spin_lock);
@@ -188,7 +188,7 @@ void __lockfunc _write_lock(rwlock_t *lo
 {
 	preempt_disable();
 	rwlock_acquire(&lock->dep_map, 0, 0, _RET_IP_);
-	_raw_write_lock(lock);
+	LOCK_CONTENDED(lock, _raw_write_trylock, _raw_write_lock);
 }
 
 EXPORT_SYMBOL(_write_lock);
@@ -289,7 +289,7 @@ void __lockfunc _spin_lock_nested(spinlo
 {
 	preempt_disable();
 	spin_acquire(&lock->dep_map, subclass, 0, _RET_IP_);
-	_raw_spin_lock(lock);
+	LOCK_CONTENDED(lock, _raw_spin_trylock, _raw_spin_lock);
 }
 
 EXPORT_SYMBOL(_spin_lock_nested);
@@ -306,7 +306,7 @@ unsigned long __lockfunc _spin_lock_irqs
 	 * that interrupts are not re-enabled during lock-acquire:
 	 */
 #ifdef CONFIG_PROVE_SPIN_LOCKING
-	_raw_spin_lock(lock);
+	LOCK_CONTENDED(lock, _raw_spin_trylock, _raw_spin_lock);
 #else
 	_raw_spin_lock_flags(lock, &flags);
 #endif
lib/Kconfig.debug +13 -0
Index: linux-2.6/lib/Kconfig.debug
===================================================================
@@ -258,6 +258,17 @@ config LOCKDEP
 	select KALLSYMS
 	select KALLSYMS_ALL
 
+config LOCK_STAT
+	bool "Lock usage statisitics"
+	depends on DEBUG_KERNEL && TRACE_IRQFLAGS_SUPPORT && STACKTRACE_SUPPORT && LOCKDEP_SUPPORT
+	select LOCKDEP
+	select DEBUG_SPINLOCK
+	select DEBUG_MUTEXES
+	select DEBUG_LOCK_ALLOC
+	default n
+	help
+	 This feature enables tracking lock contention points
+
 config DEBUG_LOCKDEP
 	bool "Lock dependency engine debugging"
 	depends on DEBUG_KERNEL && LOCKDEP
@@ -442,3 +453,5 @@ config FAULT_INJECTION_STACKTRACE_FILTER
 	select FRAME_POINTER
 	help
 	  Provide stacktrace filter for fault-injection capabilities
+
+
#4 lore raw

Re: [PATCH 3/7] lockdep: lock contention tracking

Quoted 3 lines · Peter Zijlstra

Count lock contention events per lock class. Additionally track the first four callsites that resulted in the contention.

I think that we need the total number of locking calls, not just the total number of *contended* locking calls, in order to make the data significant. Same for waittime. Yes, this pollutes the fastpath. In the contention case, its one extra addition, and for the waittime, its a call the sched_clock(). I don't think that's too much to pay for much more meaningful data.

thanks,

-Jason

#5 lore raw

Re: [PATCH 3/7] lockdep: lock contention tracking

Quoted 6 lines · Jason Baron
Quoted 3 lines · Peter Zijlstra

Count lock contention events per lock class. Additionally track the first four callsites that resulted in the contention.

Quoted 6 lines

I think that we need the total number of locking calls, not just the total number of *contended* locking calls, in order to make the data significant. Same for waittime. Yes, this pollutes the fastpath. In the contention case, its one extra addition, and for the waittime, its a call the sched_clock(). I don't think that's too much to pay for much more meaningful data.

The holdtime statistics add these numbers.

#6 lore raw

Re: [PATCH 3/7] lockdep: lock contention tracking

Quoted 7 lines · Peter Zijlstra
Quoted 6 lines · Jason Baron
Quoted 3 lines · Peter Zijlstra

Count lock contention events per lock class. Additionally track the first four callsites that resulted in the contention.

Quoted 9 lines
Quoted 6 lines

I think that we need the total number of locking calls, not just the total number of *contended* locking calls, in order to make the data significant. Same for waittime. Yes, this pollutes the fastpath. In the contention case, its one extra addition, and for the waittime, its a call the sched_clock(). I don't think that's too much to pay for much more meaningful data.

The holdtime statistics add these numbers.

ok, i see what you are saying...however, the 'waittime' statistic as implemented, is only recorded when we don't get the lock immediately. Thus, it's really measuring the waittime when there is lock contention. I understand that in the non-contended case we are only talking a a few cycles, but the fact that the non-contended case is not counted as another waittime of even zero length (so no measurement is required), might skew the stats a bit. For examples, if there was a lock that was almost never contended but one time happened to be contended for a long time, its average wait time would look really high.

thanks,

-Jason

#7 lore raw

Re: [PATCH 3/7] lockdep: lock contention tracking

Quoted 19 lines · Jason Baron
Quoted 7 lines · Peter Zijlstra
Quoted 6 lines · Jason Baron
Quoted 3 lines · Peter Zijlstra

Count lock contention events per lock class. Additionally track the first four callsites that resulted in the contention.

Quoted 9 lines
Quoted 6 lines

I think that we need the total number of locking calls, not just the total number of *contended* locking calls, in order to make the data significant. Same for waittime. Yes, this pollutes the fastpath. In the contention case, its one extra addition, and for the waittime, its a call the sched_clock(). I don't think that's too much to pay for much more meaningful data.

The holdtime statistics add these numbers.

Quoted 9 lines

ok, i see what you are saying...however, the 'waittime' statistic as implemented, is only recorded when we don't get the lock immediately. Thus, it's really measuring the waittime when there is lock contention. I understand that in the non-contended case we are only talking a a few cycles, but the fact that the non-contended case is not counted as another waittime of even zero length (so no measurement is required), might skew the stats a bit. For examples, if there was a lock that was almost never contended but one time happened to be contended for a long time, its average wait time would look really high.

I'm not seeing how or why this is a problem. The number of contentions is reported on the same line, so it should be obvious.

Your definition of wait-time is also obtainable from the numbers, one would just divide waittime-total by acquisitions, instead of contentions.

Acked-by
#8 lore raw

Re: [PATCH 3/7] lockdep: lock contention tracking

Quoted 30 lines · Peter Zijlstra
Quoted 19 lines · Jason Baron
Quoted 7 lines · Peter Zijlstra
Quoted 6 lines · Jason Baron
Quoted 3 lines · Peter Zijlstra

Count lock contention events per lock class. Additionally track the first four callsites that resulted in the contention.

Quoted 9 lines
Quoted 6 lines

I think that we need the total number of locking calls, not just the total number of *contended* locking calls, in order to make the data significant. Same for waittime. Yes, this pollutes the fastpath. In the contention case, its one extra addition, and for the waittime, its a call the sched_clock(). I don't think that's too much to pay for much more meaningful data.

The holdtime statistics add these numbers.

Quoted 9 lines

ok, i see what you are saying...however, the 'waittime' statistic as implemented, is only recorded when we don't get the lock immediately. Thus, it's really measuring the waittime when there is lock contention. I understand that in the non-contended case we are only talking a a few cycles, but the fact that the non-contended case is not counted as another waittime of even zero length (so no measurement is required), might skew the stats a bit. For examples, if there was a lock that was almost never contended but one time happened to be contended for a long time, its average wait time would look really high.

Quoted 7 lines

I'm not seeing how or why this is a problem. The number of contentions is reported on the same line, so it should be obvious.

Your definition of wait-time is also obtainable from the numbers, one would just divide waittime-total by acquisitions, instead of contentions.

agreed, I just want to point out that under my definition of waitime, I would have to make the assumption that the waittime is 0 for a lock that is acquired without fallback to the slowpath. For a spinlock acquisition, this is likely a fair assumption, however the trylock path for semaphores can be longer.

#9 lore raw

PATCH 4/7 lockdep: add waittime to the lock statistics

In reply to Peter Zijlstra

Track lock waittime - that is the time spend waiting on lock acquisition.

/proc/lock_waittime - starts with the same three colums as lock_contentions:

<class name> <(write) contentions> <read contentions>

After that come two times four more columns:

<nr> <min> <max> <total>

for both (write) contentions and read contentions. Times are in 1e-9 seconds. Writing a 0 to the file clears the stats.

---
 include/linux/lockdep.h |   15 ++++++++
 kernel/lockdep.c        |   79 +++++++++++++++++++++++++++++++++++++++++++++
 kernel/lockdep_proc.c   |   84 ++++++++++++++++++++++++++++++++++++++++++++++++
 kernel/mutex.c          |    1 
 4 files changed, 179 insertions(+)
include/linux/lockdep.h +15 -0
Index: linux-2.6/include/linux/lockdep.h
===================================================================
@@ -77,6 +77,13 @@ struct lock_contention_point {
 	atomic_t count;
 };
 
+struct lock_time {
+	raw_spinlock_t lock;
+	unsigned long long min, max;
+	unsigned long long total;
+	unsigned long nr;
+};
+
 /*
  * The lock-class itself:
  */
@@ -125,6 +132,8 @@ struct lock_class {
 	atomic_t			read_contentions;
 	atomic_t			write_contentions;
 	struct lock_contention_point	contention_point[4];
+	struct lock_time		read_waittime;
+	struct lock_time		write_waittime;
 #endif
 };
 
@@ -177,6 +186,9 @@ struct held_lock {
 	unsigned long			acquire_ip;
 	struct lockdep_map		*instance;
 
+#ifdef CONFIG_LOCK_STAT
+	unsigned long long 		waittime_stamp;
+#endif
 	/*
 	 * The lock-stack is unified in that the lock chains of interrupt
 	 * contexts nest ontop of process context chains, but we 'separate'
@@ -296,18 +308,21 @@ struct lock_class_key { };
 #ifdef CONFIG_LOCK_STAT
 
 extern void lock_contended(struct lockdep_map *lock, unsigned long ip);
+extern void lock_acquired(struct lockdep_map *lock);
 
 #define LOCK_CONTENDED(_lock, try, lock)			\
 do {								\
 	if (!try(_lock)) {					\
 		lock_contended(&(_lock)->dep_map, _RET_IP_);	\
 		lock(_lock);					\
+		lock_acquired(&(_lock)->dep_map);		\
 	}							\
 } while (0)
 
 #else /* CONFIG_LOCK_STAT */
 
 #define lock_contended(l, i)			do { } while (0)
+#define lock_acquired(l)			do { } while (0)
 
 #define LOCK_CONTENDED(_lock, try, lock) \
 	lock(_lock)
kernel/lockdep.c +79 -0

115 lines not on this page · raw message

kernel/lockdep_proc.c +84 -0

102 lines not on this page · raw message

kernel/mutex.c +1 -0
Index: linux-2.6/kernel/mutex.c
===================================================================
@@ -180,6 +180,7 @@ __mutex_lock_common(struct mutex *lock, 
 		spin_lock_mutex(&lock->wait_lock, flags);
 	}
 
+	lock_acquired(&lock->dep_map);
 done:
 	/* got the lock - rejoice! */
 	mutex_remove_waiter(lock, &waiter, task_thread_info(task));
#10 lore raw

PATCH 5/7 lockdep: add holdtime to the lock statistics

In reply to Peter Zijlstra

Track lock hold times - that is the time we held the lock.

/proc/lock_holdtime - starts with the same three colums as lock_contentions:

<class name> <(write) contentions> <read contentions>

After that come two times four more columns:

<nr> <min> <max> <total>

for both (write) locks and read locks. Times are in 1e-9 seconds. Writing a 0 to the file clears the stats.

---
 include/linux/lockdep.h |    3 ++
 kernel/lockdep.c        |   32 ++++++++++++++++++++++++--
 kernel/lockdep_proc.c   |   58 ++++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 91 insertions(+), 2 deletions(-)
include/linux/lockdep.h +3 -0
Index: linux-2.6/include/linux/lockdep.h
===================================================================
@@ -134,6 +134,8 @@ struct lock_class {
 	struct lock_contention_point	contention_point[4];
 	struct lock_time		read_waittime;
 	struct lock_time		write_waittime;
+	struct lock_time		read_holdtime;
+	struct lock_time		write_holdtime;
 #endif
 };
 
@@ -188,6 +190,7 @@ struct held_lock {
 
 #ifdef CONFIG_LOCK_STAT
 	unsigned long long 		waittime_stamp;
+	unsigned long long		holdtime_stamp;
 #endif
 	/*
 	 * The lock-stack is unified in that the lock chains of interrupt
kernel/lockdep.c +30 -2
Index: linux-2.6/kernel/lockdep.c
===================================================================
@@ -1283,6 +1283,8 @@ register_lock_class(struct lockdep_map *
 #ifdef CONFIG_LOCK_STAT
 	class->read_waittime.lock = (raw_spinlock_t)__RAW_SPIN_LOCK_UNLOCKED;
 	class->write_waittime.lock = (raw_spinlock_t)__RAW_SPIN_LOCK_UNLOCKED;
+	class->read_holdtime.lock = (raw_spinlock_t)__RAW_SPIN_LOCK_UNLOCKED;
+	class->write_holdtime.lock = (raw_spinlock_t)__RAW_SPIN_LOCK_UNLOCKED;
 #endif
 	class->name_version = count_matching_names(class);
 	/*
@@ -2087,6 +2089,7 @@ static int __lock_acquire(struct lockdep
 	hlock->hardirqs_off = hardirqs_off;
 #ifdef CONFIG_LOCK_STAT
 	hlock->waittime_stamp = 0;
+	hlock->holdtime_stamp = sched_clock();
 #endif
 
 	if (check != 2)
@@ -2327,6 +2330,7 @@ static int check_unlock(struct task_stru
 	return 1;
 }
 
+#ifdef CONFIG_LOCK_STAT
 static void lock_time_add(struct lock_time *lt, unsigned long long wt)
 {
 	__raw_spin_lock(&lt->lock);
@@ -2341,6 +2345,23 @@ static void lock_time_add(struct lock_ti
 	__raw_spin_unlock(&lt->lock);
 }
 
+static void lock_release_holdtime(struct held_lock *hlock)
+{
+	unsigned long long holdtime;
+
+	holdtime = sched_clock() - hlock->holdtime_stamp;
+
+	if (hlock->read)
+		lock_time_add(&hlock->class->read_holdtime, holdtime);
+	else
+		lock_time_add(&hlock->class->write_holdtime, holdtime);
+}
+#else
+static void lock_release_holdtime(struct held_lock *hlock)
+{
+}
+#endif
+
 /*
  * Remove the lock to the list of currently held locks in a
  * potentially non-nested (out of order) manner. This is a
@@ -2378,6 +2399,8 @@ lock_release_non_nested(struct task_stru
 	return print_unlock_inbalance_bug(curr, lock, ip);
 
 found_it:
+	lock_release_holdtime(hlock);
+
 	/*
 	 * We have the right lock to unlock, 'hlock' points to it.
 	 * Now we remove it from the stack, and add back the other
@@ -2430,6 +2453,8 @@ static int lock_release_nested(struct ta
 
 	curr->curr_chain_key = hlock->prev_chain_key;
 
+	lock_release_holdtime(hlock);
+
 #ifdef CONFIG_DEBUG_LOCKDEP
 	hlock->prev_chain_key = 0;
 	hlock->class = NULL;
@@ -2519,7 +2544,7 @@ __lock_acquired(struct lockdep_map *lock
 	struct task_struct *curr = current;
 	struct held_lock *hlock, *prev_hlock;
 	unsigned int depth;
-	unsigned long long waittime;
+	unsigned long long now, waittime;
 	int i;
 
 	depth = curr->lockdep_depth;
@@ -2545,7 +2570,10 @@ found_it:
 	if (!hlock->waittime_stamp)
 		return;
 
-	waittime = sched_clock() - hlock->waittime_stamp;
+	now = sched_clock();
+	waittime = now - hlock->waittime_stamp;
+
+	hlock->holdtime_stamp = now;
 
 	if (hlock->read)
 		lock_time_add(&hlock->class->read_waittime, waittime);
kernel/lockdep_proc.c +58 -0

70 lines not on this page · raw message

#11 lore raw

PATCH 6/7 lockdep: runtime configure prove_locking and lock_stat

In reply to Peter Zijlstra

Allow for runtime configuration of lockdep and lockstat.

/proc/sys/kernel/prove_locking
/proc/sys/kernel/lock_stat

---
 kernel/lockdep.c |   27 +++++++++++++++++++++++++++
 kernel/sysctl.c  |   27 +++++++++++++++++++++++++++
 2 files changed, 54 insertions(+)
kernel/lockdep.c +27 -0
Index: linux-2.6/kernel/lockdep.c
===================================================================
@@ -42,6 +42,16 @@
 
 #include "lockdep_internals.h"
 
+#ifdef CONFIG_PROVE_LOCKING
+int prove_locking = 1;
+module_param(prove_locking, int, 0644);
+#endif
+
+#ifdef CONFIG_LOCK_STAT
+int lock_stat = 1;
+module_param(lock_stat, int, 0644);
+#endif
+
 /*
  * lockdep_lock: protects the lockdep graph, the hashes and the
  *               class/list/hash allocators.
@@ -2091,9 +2101,13 @@ static int __lock_acquire(struct lockdep
 	hlock->waittime_stamp = 0;
 	hlock->holdtime_stamp = sched_clock();
 #endif
+#ifdef CONFIG_PROVE_LOCKING
+	if (!prove_locking)
+		goto out_irqflags;
 
 	if (check != 2)
 		goto out_calc_hash;
+#endif
 #ifdef CONFIG_TRACE_IRQFLAGS
 	/*
 	 * If non-trylock use in a hardirq or softirq context, then
@@ -2138,6 +2152,7 @@ static int __lock_acquire(struct lockdep
 		}
 	}
 #endif
+out_irqflags:
 	/* mark it as used: */
 	if (!mark_lock(curr, hlock, LOCK_USED))
 		return 0;
@@ -2190,6 +2205,8 @@ out_calc_hash:
 	curr->curr_chain_key = chain_key;
 
 #ifdef CONFIG_PROVE_LOCKING
+	if (!prove_locking)
+		goto out_graph;
 	/*
 	 * Trylock needs to maintain the stack of held locks, but it
 	 * does not add new dependencies, because trylock can be done
@@ -2236,6 +2253,7 @@ out_calc_hash:
 		/* after lookup_chain_cache(): */
 		if (unlikely(!debug_locks))
 			return 0;
+out_graph:
 #endif
 
 	curr->lockdep_depth++;
@@ -2349,6 +2367,9 @@ static void lock_release_holdtime(struct
 {
 	unsigned long long holdtime;
 
+	if (!lock_stat)
+		return;
+
 	holdtime = sched_clock() - hlock->holdtime_stamp;
 
 	if (hlock->read)
@@ -2659,6 +2680,9 @@ void lock_contended(struct lockdep_map *
 {
 	unsigned long flags;
 
+	if (!lock_stat)
+		return;
+
 	if (unlikely(current->lockdep_recursion))
 		return;
 
@@ -2675,6 +2699,9 @@ void lock_acquired(struct lockdep_map *l
 {
 	unsigned long flags;
 
+	if (!lock_stat)
+		return;
+
 	if (unlikely(current->lockdep_recursion))
 		return;
 
kernel/sysctl.c +27 -0
Index: linux-2.6/kernel/sysctl.c
===================================================================
@@ -160,6 +160,13 @@ extern ctl_table inotify_table[];
 int sysctl_legacy_va_layout;
 #endif
 
+#ifdef CONFIG_PROVE_LOCKING
+extern int prove_locking;
+#endif
+
+#ifdef CONFIG_LOCK_STAT
+extern int lock_stat;
+#endif
 
 /* The default sysctl tables: */
 
@@ -615,6 +622,26 @@ static ctl_table kern_table[] = {
 		.proc_handler   = &proc_dointvec,
 	},
 #endif
+#ifdef CONFIG_PROVE_LOCKING
+	{
+		.ctl_name	= CTL_UNNUMBERED,
+		.procname	= "prove_locking",
+		.data		= &prove_locking,
+		.maxlen		= sizeof(int),
+		.mode		= 0664,
+		.proc_handler	= &proc_dointvec,
+	},
+#endif
+#ifdef CONFIG_LOCK_STAT
+	{
+		.ctl_name	= CTL_UNNUMBERED,
+		.procname	= "lock_stat",
+		.data		= &lock_stat,
+		.maxlen		= sizeof(int),
+		.mode		= 0664,
+		.proc_handler	= &proc_dointvec,
+	},
+#endif
 
 	{ .ctl_name = 0 }
 };
#12 lore raw

PATCH 7/7 lockdep: scalable statistics

In reply to Peter Zijlstra

Rework the lock stat counters to be scalable.

---
 include/linux/lockdep.h |   34 +++++-----
 kernel/lockdep.c        |  162 ++++++++++++++++++++++++++++++++++++------------
 kernel/lockdep_proc.c   |  136 ++++++++++------------------------------
 3 files changed, 178 insertions(+), 154 deletions(-)
include/linux/lockdep.h +18 -16
Index: linux-2.6/include/linux/lockdep.h
===================================================================
@@ -72,18 +72,6 @@ struct lock_class_key {
 	struct lockdep_subclass_key	subkeys[MAX_LOCKDEP_SUBCLASSES];
 };
 
-struct lock_contention_point {
-	unsigned long ip;
-	atomic_t count;
-};
-
-struct lock_time {
-	raw_spinlock_t lock;
-	unsigned long long min, max;
-	unsigned long long total;
-	unsigned long nr;
-};
-
 /*
  * The lock-class itself:
  */
@@ -128,17 +116,31 @@ struct lock_class {
 	const char			*name;
 	int				name_version;
 
+	unsigned long			contention_point[4];
+};
+
 #ifdef CONFIG_LOCK_STAT
-	atomic_t			read_contentions;
-	atomic_t			write_contentions;
-	struct lock_contention_point	contention_point[4];
+struct lock_time {
+	unsigned long long		min;
+	unsigned long long		max;
+	unsigned long long		total;
+	unsigned long			nr;
+};
+
+struct lock_class_stats {
+	unsigned long			read_contentions;
+	unsigned long			write_contentions;
+	unsigned long			contention_point[4];
 	struct lock_time		read_waittime;
 	struct lock_time		write_waittime;
 	struct lock_time		read_holdtime;
 	struct lock_time		write_holdtime;
-#endif
 };
 
+struct lock_class_stats lock_stats(struct lock_class *class);
+void clear_lock_stats(struct lock_class *class);
+#endif
+
 /*
  * Map the lock object (the lock instance) to the lock-class object.
  * This is embedded into specific lock instances:
kernel/lockdep.c +124 -38

220 lines not on this page · raw message

kernel/lockdep_proc.c +36 -100

195 lines not on this page · raw message

Acked-by on the v1 series
#13 lore raw
In reply to Peter Zijlstra
Quoted 13 lines · Peter Zijlstra

Use the lockdep infrastructure to track lock contention and other lock statistics.

It tracks lock contention events, and the first four unique call-sites that encountered contention.

It also measures lock wait-time and hold-time in nanoseconds. The minimum and maximum times are tracked, as well as a total (which together with the number of event can give the avg).

All statistics are done per lock class, per write (exclusive state) and per read (shared state).

Quoted 8 lines

The statistics are collected per-cpu, so that the collection overhead is minimized via having no global cachemisses.

This new lock statistics feature is independent of the lock dependency checking traditionally done by lockdep; it just shares the lock tracking code. It is also possible to enable both and runtime disabled either component - thereby avoiding the O(n^2) lock chain walks for instance.

really nice changes! The wait-time and hold-time changes should make it as capable as lockmeter and more: lockmeter only measured spinlocks, while your approach covers all lock types (spinlocks, rwlocks and mutexes).

The performance enhancements in -v2 should make it much more scalable than your first version was. (in fact i think it should be completely scalable as the statistics counters are all per-cpu, so there should be no cacheline bouncing at all from this)

Also, you've detached it from CONFIG_PROVE_LOCKING so the lock-dependency overhead should be gone too.

Looks really good to me!

Ingo

#14 lore raw
Quoted 1 line · Ingo Molnar

* Peter Zijlstra <[email protected]> wrote:

...

Quoted 9 lines
Quoted 9 lines

It also measures lock wait-time and hold-time in nanoseconds. The minimum and maximum times are tracked, as well as a total (which together with the number of event can give the avg).

All statistics are done per lock class, per write (exclusive state) and per read (shared state).

The statistics are collected per-cpu, so that the collection overhead is minimized via having no global cachemisses.

...

Quoted 9 lines

really nice changes! The wait-time and hold-time changes should make it as capable as lockmeter and more: lockmeter only measured spinlocks, while your approach covers all lock types (spinlocks, rwlocks and mutexes).

The performance enhancements in -v2 should make it much more scalable than your first version was. (in fact i think it should be completely scalable as the statistics counters are all per-cpu, so there should be no cacheline bouncing at all from this)

per cpu is pretty important since you can potentially hit that logic more often with your wait-time code. You don't want to effect the actual measurement with the measurement code. It's that uncertainty principal thing.

It is looking pretty good. :) You might like to pretty the output even more, but it's pretty usable as is.

bill

#15 lore raw

[PATCH 8/7] new output format

In reply to Peter Zijlstra

Perhaps this makes things more clear...

----

<edited output - real one is waaaay to big>

$ cat /proc/lock_stat
T                               class name    contentions   waittime-min   waittime-max waittime-total   acquisitions   holdtime-min   holdtime-max holdtime-total
------------------------------------------------------------------------------------------------------------------------------------------------------------------
X                              dcache_lock            543            478          23751         427570         166757            217         572426       90179147
                               dcache_lock            192          [<ffffffff802a5980>] d_instantiate+0x2a/0xad
                               dcache_lock            215          [<ffffffff8033b7a1>] _atomic_dec_and_lock+0x39/0x58
                               dcache_lock              2          [<ffffffff802a4703>] d_rehash+0x1b/0x44
                               dcache_lock              1          [<ffffffff802a5c53>] d_alloc+0x170/0x1d0
W                            tasklist_lock              9           1322          12870          21729           7543            593         125518       25759730
R                            tasklist_lock             36           1742          10475          76717           6104            417          56540        9121541
                             tasklist_lock             42          [<ffffffff80235240>] do_wait+0x7b/0xaa1
                             tasklist_lock              0          [<ffffffff802406a3>] sys_setpriority+0x4a/0x1ca
                             tasklist_lock              0          [<ffffffff80231786>] copy_process+0x116b/0x1689
                             tasklist_lock              0          [<ffffffff802361ac>] do_exit+0x4d2/0x878
X                       nl_table_wait.lock              0              0              0              0           1133            365         103424         581560
X                      cpufreq_driver_lock              0              0              0              0            122            438          44040         136777
X                       net_todo_run_mutex              0              0              0              0            121            636          42205         112073
---
 kernel/lockdep_proc.c |  241 ++++++++++++++++++++++++++------------------------
 1 file changed, 129 insertions(+), 112 deletions(-)
kernel/lockdep_proc.c +128 -112
Index: linux-2.6/kernel/lockdep_proc.c
===================================================================
@@ -15,6 +15,8 @@
 #include <linux/seq_file.h>
 #include <linux/kallsyms.h>
 #include <linux/debug_locks.h>
+#include <linux/vmalloc.h>
+#include <linux/sort.h>
 #include <asm/uaccess.h>
 #include <asm/div64.h>
 
@@ -345,49 +347,145 @@ static const struct file_operations proc
 };
 
 #ifdef CONFIG_LOCK_STAT
-static int lock_contentions_show(struct seq_file *m, void *v)
-{
-	char sym[KSYM_SYMBOL_LEN];
+
+struct lock_stat_data {
 	struct lock_class *class;
 	struct lock_class_stats stats;
-	int i;
+};
+
+/*
+ * sort on absolute number of contentions
+ */
+int lock_stat_cmp(const void *l, const void *r)
+{
+	const struct lock_stat_data *dl = l, *dr = r;
+	unsigned long nl, nr;
+
+	nl = dl->stats.read_waittime.nr + dl->stats.write_waittime.nr;
+	nr = dr->stats.read_waittime.nr + dr->stats.write_waittime.nr;
+
+	return nr - nl;
+}
+
+static int lock_stat_show(struct seq_file *m, void *v)
+{
+	struct lock_class *class;
+	struct lock_stat_data *stat, *stat_end;
+	struct lock_stat_data *stat_snapshot =
+		vmalloc(sizeof(struct lock_stat_data) * MAX_LOCKDEP_KEYS);
+
+	if (!stat_snapshot) {
+		seq_printf(m, "-ENOMEM\n");
+		return 0;
+	}
+
+	stat_end = stat_snapshot;
 
 	list_for_each_entry(class, &all_lock_classes, lock_entry) {
-		stats = lock_stats(class);
+		stat_end->class = class;
+		stat_end->stats = lock_stats(class);
+		stat_end++;
+	}
 
-		if (stats.read_contentions || stats.write_contentions) {
-			seq_printf(m, "%s: %lu %lu", class->name,
-					stats.write_contentions,
-					stats.read_contentions);
-
-			for (i = 0; i < ARRAY_SIZE(class->contention_point);
-					i++) {
-				if (class->contention_point[i] == 0)
-					break;
-				sprint_symbol(sym, class->contention_point[i]);
-				seq_printf(m, " %lu [<%p>] %s",
-					stats.contention_point[i],
-					(void *)class->contention_point[i],
-					sym);
-			}
-			seq_printf(m, "\n");
+	sort(stat_snapshot, stat_end - stat_snapshot,
+			sizeof(struct lock_stat_data),
+			lock_stat_cmp, NULL);
+
+	seq_printf(m, "%1s %40s %14s %14s %14s %14s %14s %14s %14s %14s\n",
+			"T",
+			"class name",
+			"contentions",
+			"waittime-min",
+			"waittime-max",
+			"waittime-total",
+			"acquisitions",
+			"holdtime-min",
+			"holdtime-max",
+			"holdtime-total");
+	seq_printf(m, "-" "-"
+		      "----------------------------------------" "-"
+	              "--------------" "-"
+	              "--------------" "-"
+	              "--------------" "-"
+	              "--------------" "-"
+	              "--------------" "-"
+	              "--------------" "-"
+	              "--------------" "-"
+	              "--------------" "\n");
+
+	for (stat = stat_snapshot; stat != stat_end; stat++) {
+		struct lock_class_stats *stats = &stat->stats;
+		int i;
+
+		class = stat->class;
+
+		if (stats->write_waittime.nr ||
+				stats->write_holdtime.nr)
+			seq_printf(m, "%c %40s %14lu %14llu %14llu %14llu"
+					" %14lu %14llu %14llu %14llu\n",
+					(stats->read_holdtime.nr ? 'W' : 'X'),
+					class->name,
+					stats->write_waittime.nr,
+					stats->write_waittime.min,
+					stats->write_waittime.max,
+					stats->write_waittime.total,
+					stats->write_holdtime.nr,
+					stats->write_holdtime.min,
+					stats->write_holdtime.max,
+					stats->write_holdtime.total);
+
+		if (stats->read_waittime.nr ||
+				stats->read_holdtime.nr)
+			seq_printf(m, "%c %40s %14lu %14llu %14llu %14llu"
+					" %14lu %14llu %14llu %14llu\n",
+					'R',
+					class->name,
+					stats->read_waittime.nr,
+					stats->read_waittime.min,
+					stats->read_waittime.max,
+					stats->read_waittime.total,
+					stats->read_holdtime.nr,
+					stats->read_holdtime.min,
+					stats->read_holdtime.max,
+					stats->read_holdtime.total);
+
+		for (i = 0; i < ARRAY_SIZE(class->contention_point); i++) {
+			char sym[KSYM_SYMBOL_LEN];
+			char ip[32];
+
+			if (class->contention_point[i] == 0)
+				break;
+
+			if (!i)
+				seq_printf(m, "\n");
+
+			sprint_symbol(sym, class->contention_point[i]);
+			snprintf(ip, sizeof(ip), "[<%p>]",
+					(void *)class->contention_point[i]);
+			seq_printf(m, "  %40s %14lu %29s %s\n",
+					class->name,
+					stats->contention_point[i],
+					ip, sym);
 		}
+		if (i)
+			seq_printf(m, "\n");
 	}
 
+	vfree(stat_snapshot);
+
 	return 0;
 }
 
-static int lock_contentions_open(struct inode *inode, struct file *file)
+static int lock_stat_open(struct inode *inode, struct file *file)
 {
-	return single_open(file, lock_contentions_show, NULL);
+	return single_open(file, lock_stat_show, NULL);
 }
 
-ssize_t lock_contentions_write(struct file *file, const char __user *buf,
+ssize_t lock_stat_write(struct file *file, const char __user *buf,
 		size_t count, loff_t *ppos)
 {
 	struct lock_class *class;
 	char c;
-	int i;
 
 	if (count) {
 		if (get_user(c, buf))
@@ -402,83 +501,9 @@ ssize_t lock_contentions_write(struct fi
 	return count;
 }
 
-static const struct file_operations proc_lock_contentions_operations = {
-	.open		= lock_contentions_open,
-	.write		= lock_contentions_write,
-	.read		= seq_read,
-	.llseek		= seq_lseek,
-	.release	= seq_release,
-};
-
-static void print_time(struct seq_file *m, struct lock_time *lt)
-{
-	seq_printf(m, " %lu %llu %llu %llu",
-			lt->nr, lt->min, lt->max, lt->total);
-}
-
-static int lock_waittime_show(struct seq_file *m, void *v)
-{
-	struct lock_class *class;
-	struct lock_class_stats stats;
-
-	list_for_each_entry(class, &all_lock_classes, lock_entry) {
-		stats = lock_stats(class);
-
-		if (stats.read_contentions || stats.write_contentions) {
-			seq_printf(m, "%s: %lu %lu", class->name,
-					stats.write_contentions,
-					stats.read_contentions);
-			print_time(m, &stats.write_waittime);
-			print_time(m, &stats.read_waittime);
-			seq_printf(m, "\n");
-		}
-	}
-
-	return 0;
-}
-
-static int lock_waittime_open(struct inode *inode, struct file *file)
-{
-	return single_open(file, lock_waittime_show, NULL);
-}
-
-static const struct file_operations proc_lock_waittime_operations = {
-	.open		= lock_waittime_open,
-	.write		= lock_contentions_write,
-	.read		= seq_read,
-	.llseek		= seq_lseek,
-	.release	= seq_release,
-};
-
-static int lock_holdtime_show(struct seq_file *m, void *v)
-{
-	struct lock_class *class;
-	struct lock_class_stats stats;
-
-	list_for_each_entry(class, &all_lock_classes, lock_entry) {
-		stats = lock_stats(class);
-
-		if (stats.read_contentions || stats.write_contentions) {
-			seq_printf(m, "%s: %lu %lu", class->name,
-					stats.write_contentions,
-					stats.read_contentions);
-			print_time(m, &stats.write_holdtime);
-			print_time(m, &stats.read_holdtime);
-			seq_printf(m, "\n");
-		}
-	}
-
-	return 0;
-}
-
-static int lock_holdtime_open(struct inode *inode, struct file *file)
-{
-	return single_open(file, lock_holdtime_show, NULL);
-}
-
-static const struct file_operations proc_lock_holdtime_operations = {
-	.open		= lock_holdtime_open,
-	.write		= lock_contentions_write,
+static const struct file_operations proc_lock_stat_operations = {
+	.open		= lock_stat_open,
+	.write		= lock_stat_write,
 	.read		= seq_read,
 	.llseek		= seq_lseek,
 	.release	= seq_release,
@@ -498,17 +523,9 @@ static int __init lockdep_proc_init(void
 		entry->proc_fops = &proc_lockdep_stats_operations;
 
 #ifdef CONFIG_LOCK_STAT
-	entry = create_proc_entry("lock_contentions", S_IRUSR, NULL);
-	if (entry)
-		entry->proc_fops = &proc_lock_contentions_operations;
-
-	entry = create_proc_entry("lock_waittime", S_IRUSR, NULL);
-	if (entry)
-		entry->proc_fops = &proc_lock_waittime_operations;
-
-	entry = create_proc_entry("lock_holdtime", S_IRUSR, NULL);
+	entry = create_proc_entry("lock_stat", S_IRUSR, NULL);
 	if (entry)
-		entry->proc_fops = &proc_lock_holdtime_operations;
+		entry->proc_fops = &proc_lock_stat_operations;
 #endif
 
 	return 0;