1 | /* SPDX-License-Identifier: GPL-2.0 */ |
2 | /* |
3 | * This program is free software; you can redistribute it and/or modify |
4 | * it under the terms of the GNU General Public License as published by |
5 | * the Free Software Foundation; either version 2 of the License, or |
6 | * (at your option) any later version. |
7 | * |
8 | * This program is distributed in the hope that it will be useful, |
9 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
10 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
11 | * GNU General Public License for more details. |
12 | * |
13 | * Authors: Waiman Long <waiman.long@hpe.com> |
14 | */ |
15 | |
16 | /* |
17 | * Collect locking event counts |
18 | */ |
19 | #include <linux/debugfs.h> |
20 | #include <linux/sched.h> |
21 | #include <linux/sched/clock.h> |
22 | #include <linux/fs.h> |
23 | |
24 | #include "lock_events.h" |
25 | |
26 | #undef LOCK_EVENT |
27 | #define LOCK_EVENT(name) [LOCKEVENT_ ## name] = #name, |
28 | |
29 | #define LOCK_EVENTS_DIR "lock_event_counts" |
30 | |
31 | /* |
32 | * When CONFIG_LOCK_EVENT_COUNTS is enabled, event counts of different |
33 | * types of locks will be reported under the <debugfs>/lock_event_counts/ |
34 | * directory. See lock_events_list.h for the list of available locking |
35 | * events. |
36 | * |
37 | * Writing to the special ".reset_counts" file will reset all the above |
38 | * locking event counts. This is a very slow operation and so should not |
39 | * be done frequently. |
40 | * |
41 | * These event counts are implemented as per-cpu variables which are |
42 | * summed and computed whenever the corresponding debugfs files are read. This |
43 | * minimizes added overhead making the counts usable even in a production |
44 | * environment. |
45 | */ |
46 | static const char * const lockevent_names[lockevent_num + 1] = { |
47 | |
48 | #include "lock_events_list.h" |
49 | |
50 | [LOCKEVENT_reset_cnts] = ".reset_counts" , |
51 | }; |
52 | |
53 | /* |
54 | * Per-cpu counts |
55 | */ |
56 | DEFINE_PER_CPU(unsigned long, lockevents[lockevent_num]); |
57 | |
58 | /* |
59 | * The lockevent_read() function can be overridden. |
60 | */ |
61 | ssize_t __weak lockevent_read(struct file *file, char __user *user_buf, |
62 | size_t count, loff_t *ppos) |
63 | { |
64 | char buf[64]; |
65 | int cpu, id, len; |
66 | u64 sum = 0; |
67 | |
68 | /* |
69 | * Get the counter ID stored in file->f_inode->i_private |
70 | */ |
71 | id = (long)file_inode(f: file)->i_private; |
72 | |
73 | if (id >= lockevent_num) |
74 | return -EBADF; |
75 | |
76 | for_each_possible_cpu(cpu) |
77 | sum += per_cpu(lockevents[id], cpu); |
78 | len = snprintf(buf, size: sizeof(buf) - 1, fmt: "%llu\n" , sum); |
79 | |
80 | return simple_read_from_buffer(to: user_buf, count, ppos, from: buf, available: len); |
81 | } |
82 | |
83 | /* |
84 | * Function to handle write request |
85 | * |
86 | * When idx = reset_cnts, reset all the counts. |
87 | */ |
88 | static ssize_t lockevent_write(struct file *file, const char __user *user_buf, |
89 | size_t count, loff_t *ppos) |
90 | { |
91 | int cpu; |
92 | |
93 | /* |
94 | * Get the counter ID stored in file->f_inode->i_private |
95 | */ |
96 | if ((long)file_inode(f: file)->i_private != LOCKEVENT_reset_cnts) |
97 | return count; |
98 | |
99 | for_each_possible_cpu(cpu) { |
100 | int i; |
101 | unsigned long *ptr = per_cpu_ptr(lockevents, cpu); |
102 | |
103 | for (i = 0 ; i < lockevent_num; i++) |
104 | WRITE_ONCE(ptr[i], 0); |
105 | } |
106 | return count; |
107 | } |
108 | |
109 | /* |
110 | * Debugfs data structures |
111 | */ |
112 | static const struct file_operations fops_lockevent = { |
113 | .read = lockevent_read, |
114 | .write = lockevent_write, |
115 | .llseek = default_llseek, |
116 | }; |
117 | |
118 | #ifdef CONFIG_PARAVIRT_SPINLOCKS |
119 | #include <asm/paravirt.h> |
120 | |
121 | static bool __init skip_lockevent(const char *name) |
122 | { |
123 | static int pv_on __initdata = -1; |
124 | |
125 | if (pv_on < 0) |
126 | pv_on = !pv_is_native_spin_unlock(); |
127 | /* |
128 | * Skip PV qspinlock events on bare metal. |
129 | */ |
130 | if (!pv_on && !memcmp(p: name, q: "pv_" , size: 3)) |
131 | return true; |
132 | return false; |
133 | } |
134 | #else |
135 | static inline bool skip_lockevent(const char *name) |
136 | { |
137 | return false; |
138 | } |
139 | #endif |
140 | |
141 | /* |
142 | * Initialize debugfs for the locking event counts. |
143 | */ |
144 | static int __init init_lockevent_counts(void) |
145 | { |
146 | struct dentry *d_counts = debugfs_create_dir(LOCK_EVENTS_DIR, NULL); |
147 | int i; |
148 | |
149 | if (IS_ERR(ptr: d_counts)) |
150 | goto out; |
151 | |
152 | /* |
153 | * Create the debugfs files |
154 | * |
155 | * As reading from and writing to the stat files can be slow, only |
156 | * root is allowed to do the read/write to limit impact to system |
157 | * performance. |
158 | */ |
159 | for (i = 0; i < lockevent_num; i++) { |
160 | if (skip_lockevent(name: lockevent_names[i])) |
161 | continue; |
162 | if (IS_ERR(ptr: debugfs_create_file(name: lockevent_names[i], mode: 0400, parent: d_counts, |
163 | data: (void *)(long)i, fops: &fops_lockevent))) |
164 | goto fail_undo; |
165 | } |
166 | |
167 | if (IS_ERR(ptr: debugfs_create_file(name: lockevent_names[LOCKEVENT_reset_cnts], mode: 0200, |
168 | parent: d_counts, data: (void *)(long)LOCKEVENT_reset_cnts, |
169 | fops: &fops_lockevent))) |
170 | goto fail_undo; |
171 | |
172 | return 0; |
173 | fail_undo: |
174 | debugfs_remove_recursive(dentry: d_counts); |
175 | out: |
176 | pr_warn("Could not create '%s' debugfs entries\n" , LOCK_EVENTS_DIR); |
177 | return -ENOMEM; |
178 | } |
179 | fs_initcall(init_lockevent_counts); |
180 | |