diff options
Diffstat (limited to 'kernel/locking/lock_events.c')
| -rw-r--r-- | kernel/locking/lock_events.c | 179 | 
1 files changed, 179 insertions, 0 deletions
diff --git a/kernel/locking/lock_events.c b/kernel/locking/lock_events.c new file mode 100644 index 000000000000..fa2c2f951c6b --- /dev/null +++ b/kernel/locking/lock_events.c @@ -0,0 +1,179 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + * GNU General Public License for more details. + * + * Authors: Waiman Long <waiman.long@hpe.com> + */ + +/* + * Collect locking event counts + */ +#include <linux/debugfs.h> +#include <linux/sched.h> +#include <linux/sched/clock.h> +#include <linux/fs.h> + +#include "lock_events.h" + +#undef  LOCK_EVENT +#define LOCK_EVENT(name)	[LOCKEVENT_ ## name] = #name, + +#define LOCK_EVENTS_DIR		"lock_event_counts" + +/* + * When CONFIG_LOCK_EVENT_COUNTS is enabled, event counts of different + * types of locks will be reported under the <debugfs>/lock_event_counts/ + * directory. See lock_events_list.h for the list of available locking + * events. + * + * Writing to the special ".reset_counts" file will reset all the above + * locking event counts. This is a very slow operation and so should not + * be done frequently. + * + * These event counts are implemented as per-cpu variables which are + * summed and computed whenever the corresponding debugfs files are read. This + * minimizes added overhead making the counts usable even in a production + * environment. + */ +static const char * const lockevent_names[lockevent_num + 1] = { + +#include "lock_events_list.h" + +	[LOCKEVENT_reset_cnts] = ".reset_counts", +}; + +/* + * Per-cpu counts + */ +DEFINE_PER_CPU(unsigned long, lockevents[lockevent_num]); + +/* + * The lockevent_read() function can be overridden. + */ +ssize_t __weak lockevent_read(struct file *file, char __user *user_buf, +			      size_t count, loff_t *ppos) +{ +	char buf[64]; +	int cpu, id, len; +	u64 sum = 0; + +	/* +	 * Get the counter ID stored in file->f_inode->i_private +	 */ +	id = (long)file_inode(file)->i_private; + +	if (id >= lockevent_num) +		return -EBADF; + +	for_each_possible_cpu(cpu) +		sum += per_cpu(lockevents[id], cpu); +	len = snprintf(buf, sizeof(buf) - 1, "%llu\n", sum); + +	return simple_read_from_buffer(user_buf, count, ppos, buf, len); +} + +/* + * Function to handle write request + * + * When idx = reset_cnts, reset all the counts. + */ +static ssize_t lockevent_write(struct file *file, const char __user *user_buf, +			   size_t count, loff_t *ppos) +{ +	int cpu; + +	/* +	 * Get the counter ID stored in file->f_inode->i_private +	 */ +	if ((long)file_inode(file)->i_private != LOCKEVENT_reset_cnts) +		return count; + +	for_each_possible_cpu(cpu) { +		int i; +		unsigned long *ptr = per_cpu_ptr(lockevents, cpu); + +		for (i = 0 ; i < lockevent_num; i++) +			WRITE_ONCE(ptr[i], 0); +	} +	return count; +} + +/* + * Debugfs data structures + */ +static const struct file_operations fops_lockevent = { +	.read = lockevent_read, +	.write = lockevent_write, +	.llseek = default_llseek, +}; + +#ifdef CONFIG_PARAVIRT_SPINLOCKS +#include <asm/paravirt.h> + +static bool __init skip_lockevent(const char *name) +{ +	static int pv_on __initdata = -1; + +	if (pv_on < 0) +		pv_on = !pv_is_native_spin_unlock(); +	/* +	 * Skip PV qspinlock events on bare metal. +	 */ +	if (!pv_on && !memcmp(name, "pv_", 3)) +		return true; +	return false; +} +#else +static inline bool skip_lockevent(const char *name) +{ +	return false; +} +#endif + +/* + * Initialize debugfs for the locking event counts. + */ +static int __init init_lockevent_counts(void) +{ +	struct dentry *d_counts = debugfs_create_dir(LOCK_EVENTS_DIR, NULL); +	int i; + +	if (!d_counts) +		goto out; + +	/* +	 * Create the debugfs files +	 * +	 * As reading from and writing to the stat files can be slow, only +	 * root is allowed to do the read/write to limit impact to system +	 * performance. +	 */ +	for (i = 0; i < lockevent_num; i++) { +		if (skip_lockevent(lockevent_names[i])) +			continue; +		if (!debugfs_create_file(lockevent_names[i], 0400, d_counts, +					 (void *)(long)i, &fops_lockevent)) +			goto fail_undo; +	} + +	if (!debugfs_create_file(lockevent_names[LOCKEVENT_reset_cnts], 0200, +				 d_counts, (void *)(long)LOCKEVENT_reset_cnts, +				 &fops_lockevent)) +		goto fail_undo; + +	return 0; +fail_undo: +	debugfs_remove_recursive(d_counts); +out: +	pr_warn("Could not create '%s' debugfs entries\n", LOCK_EVENTS_DIR); +	return -ENOMEM; +} +fs_initcall(init_lockevent_counts);  | 
