forked from Qortal/Brooklyn
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
179 lines
4.3 KiB
179 lines
4.3 KiB
/* 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 <[email protected]> |
|
*/ |
|
|
|
/* |
|
* 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);
|
|
|