Line data Source code
1 : // SPDX-License-Identifier: GPL-2.0-or-later
2 : /*
3 : * Copyright (C) 2023 Oracle. All Rights Reserved.
4 : * Author: Darrick J. Wong <djwong@kernel.org>
5 : */
6 : #include "xfs.h"
7 : #include "xfs_fs.h"
8 : #include "xfs_shared.h"
9 : #include "xfs_format.h"
10 : #include "xfs_trans_resv.h"
11 : #include "xfs_mount.h"
12 : #include "xfs_sysfs.h"
13 : #include "xfs_btree.h"
14 : #include "xfs_super.h"
15 : #include "scrub/scrub.h"
16 : #include "scrub/stats.h"
17 : #include "scrub/trace.h"
18 :
19 : struct xchk_scrub_stats {
20 : /* all 32-bit counters here */
21 :
22 : /* checking stats */
23 : uint32_t invocations;
24 : uint32_t clean;
25 : uint32_t corrupt;
26 : uint32_t preen;
27 : uint32_t xfail;
28 : uint32_t xcorrupt;
29 : uint32_t incomplete;
30 : uint32_t warning;
31 : uint32_t retries;
32 :
33 : /* repair stats */
34 : uint32_t repair_invocations;
35 : uint32_t repair_success;
36 :
37 : /* all 64-bit items here */
38 :
39 : /* runtimes */
40 : uint64_t checktime_us;
41 : uint64_t repairtime_us;
42 :
43 : /* non-counter state must go at the end for clearall */
44 : spinlock_t css_lock;
45 : };
46 :
47 : struct xchk_stats {
48 : struct dentry *cs_debugfs;
49 : struct xchk_scrub_stats cs_stats[XFS_SCRUB_TYPE_NR];
50 : };
51 :
52 :
53 : static struct xchk_stats global_stats;
54 :
55 : static const char *name_map[XFS_SCRUB_TYPE_NR] = {
56 : [XFS_SCRUB_TYPE_SB] = "sb",
57 : [XFS_SCRUB_TYPE_AGF] = "agf",
58 : [XFS_SCRUB_TYPE_AGFL] = "agfl",
59 : [XFS_SCRUB_TYPE_AGI] = "agi",
60 : [XFS_SCRUB_TYPE_BNOBT] = "bnobt",
61 : [XFS_SCRUB_TYPE_CNTBT] = "cntbt",
62 : [XFS_SCRUB_TYPE_INOBT] = "inobt",
63 : [XFS_SCRUB_TYPE_FINOBT] = "finobt",
64 : [XFS_SCRUB_TYPE_RMAPBT] = "rmapbt",
65 : [XFS_SCRUB_TYPE_REFCNTBT] = "refcountbt",
66 : [XFS_SCRUB_TYPE_INODE] = "inode",
67 : [XFS_SCRUB_TYPE_BMBTD] = "bmapbtd",
68 : [XFS_SCRUB_TYPE_BMBTA] = "bmapbta",
69 : [XFS_SCRUB_TYPE_BMBTC] = "bmapbtc",
70 : [XFS_SCRUB_TYPE_DIR] = "directory",
71 : [XFS_SCRUB_TYPE_XATTR] = "xattr",
72 : [XFS_SCRUB_TYPE_SYMLINK] = "symlink",
73 : [XFS_SCRUB_TYPE_PARENT] = "parent",
74 : [XFS_SCRUB_TYPE_RTBITMAP] = "rtbitmap",
75 : [XFS_SCRUB_TYPE_RTSUM] = "rtsummary",
76 : [XFS_SCRUB_TYPE_UQUOTA] = "usrquota",
77 : [XFS_SCRUB_TYPE_GQUOTA] = "grpquota",
78 : [XFS_SCRUB_TYPE_PQUOTA] = "prjquota",
79 : [XFS_SCRUB_TYPE_FSCOUNTERS] = "fscounters",
80 : [XFS_SCRUB_TYPE_QUOTACHECK] = "quotacheck",
81 : [XFS_SCRUB_TYPE_NLINKS] = "nlinks",
82 : [XFS_SCRUB_TYPE_DIRTREE] = "dirtree",
83 : };
84 :
85 : /* Format the scrub stats into a text buffer, similar to pcp style. */
86 : STATIC ssize_t
87 0 : xchk_stats_format(
88 : struct xchk_stats *cs,
89 : char *buf,
90 : size_t remaining)
91 : {
92 0 : struct xchk_scrub_stats *css = &cs->cs_stats[0];
93 0 : unsigned int i;
94 0 : ssize_t copied = 0;
95 0 : int ret = 0;
96 :
97 0 : for (i = 0; i < XFS_SCRUB_TYPE_NR; i++, css++) {
98 0 : if (!name_map[i])
99 0 : continue;
100 :
101 0 : ret = scnprintf(buf, remaining,
102 : "%s %u %u %u %u %u %u %u %u %u %llu %u %u %llu\n",
103 : name_map[i],
104 0 : (unsigned int)css->invocations,
105 0 : (unsigned int)css->clean,
106 0 : (unsigned int)css->corrupt,
107 0 : (unsigned int)css->preen,
108 0 : (unsigned int)css->xfail,
109 0 : (unsigned int)css->xcorrupt,
110 0 : (unsigned int)css->incomplete,
111 0 : (unsigned int)css->warning,
112 0 : (unsigned int)css->retries,
113 0 : (unsigned long long)css->checktime_us,
114 0 : (unsigned int)css->repair_invocations,
115 0 : (unsigned int)css->repair_success,
116 0 : (unsigned long long)css->repairtime_us);
117 0 : if (ret <= 0)
118 : break;
119 :
120 0 : remaining -= ret;
121 0 : copied += ret;
122 0 : buf += ret;
123 : }
124 :
125 0 : return copied > 0 ? copied : ret;
126 : }
127 :
128 : /* Estimate the worst case buffer size required to hold the whole report. */
129 : STATIC size_t
130 0 : xchk_stats_estimate_bufsize(
131 : struct xchk_stats *cs)
132 : {
133 0 : struct xchk_scrub_stats *css = &cs->cs_stats[0];
134 0 : unsigned int i;
135 0 : size_t field_width;
136 0 : size_t ret = 0;
137 :
138 : /* 4294967296 plus one space for each u32 field */
139 0 : field_width = 11 * (offsetof(struct xchk_scrub_stats, checktime_us) /
140 : sizeof(uint32_t));
141 :
142 : /* 18446744073709551615 plus one space for each u64 field */
143 0 : field_width += 21 * ((offsetof(struct xchk_scrub_stats, css_lock) -
144 : offsetof(struct xchk_scrub_stats, checktime_us)) /
145 : sizeof(uint64_t));
146 :
147 0 : for (i = 0; i < XFS_SCRUB_TYPE_NR; i++, css++) {
148 0 : if (!name_map[i])
149 0 : continue;
150 :
151 : /* name plus one space */
152 0 : ret += 1 + strlen(name_map[i]);
153 :
154 : /* all fields, plus newline */
155 0 : ret += field_width + 1;
156 : }
157 :
158 0 : return ret;
159 : }
160 :
161 : /* Clear all counters. */
162 : STATIC void
163 0 : xchk_stats_clearall(
164 : struct xchk_stats *cs)
165 : {
166 0 : struct xchk_scrub_stats *css = &cs->cs_stats[0];
167 0 : unsigned int i;
168 :
169 0 : for (i = 0; i < XFS_SCRUB_TYPE_NR; i++, css++) {
170 0 : spin_lock(&css->css_lock);
171 0 : memset(css, 0, offsetof(struct xchk_scrub_stats, css_lock));
172 0 : spin_unlock(&css->css_lock);
173 : }
174 0 : }
175 :
176 : #define XFS_SCRUB_OFLAG_UNCLEAN (XFS_SCRUB_OFLAG_CORRUPT | \
177 : XFS_SCRUB_OFLAG_PREEN | \
178 : XFS_SCRUB_OFLAG_XFAIL | \
179 : XFS_SCRUB_OFLAG_XCORRUPT | \
180 : XFS_SCRUB_OFLAG_INCOMPLETE | \
181 : XFS_SCRUB_OFLAG_WARNING)
182 :
183 : STATIC void
184 1096735042 : xchk_stats_merge_one(
185 : struct xchk_stats *cs,
186 : const struct xfs_scrub_metadata *sm,
187 : const struct xchk_stats_run *run)
188 : {
189 1096735042 : struct xchk_scrub_stats *css;
190 :
191 1096735042 : ASSERT(sm->sm_type < XFS_SCRUB_TYPE_NR);
192 :
193 1096735042 : css = &cs->cs_stats[sm->sm_type];
194 1096735042 : spin_lock(&css->css_lock);
195 1097014065 : css->invocations++;
196 1097014065 : if (!(sm->sm_flags & XFS_SCRUB_OFLAG_UNCLEAN))
197 1089575413 : css->clean++;
198 1097014065 : if (sm->sm_flags & XFS_SCRUB_OFLAG_CORRUPT)
199 36 : css->corrupt++;
200 1097014065 : if (sm->sm_flags & XFS_SCRUB_OFLAG_PREEN)
201 7437767 : css->preen++;
202 1097014065 : if (sm->sm_flags & XFS_SCRUB_OFLAG_XFAIL)
203 0 : css->xfail++;
204 1097014065 : if (sm->sm_flags & XFS_SCRUB_OFLAG_XCORRUPT)
205 4 : css->xcorrupt++;
206 1097014065 : if (sm->sm_flags & XFS_SCRUB_OFLAG_INCOMPLETE)
207 62 : css->incomplete++;
208 1097014065 : if (sm->sm_flags & XFS_SCRUB_OFLAG_WARNING)
209 48 : css->warning++;
210 1097014065 : css->retries += run->retries;
211 1097014065 : css->checktime_us += howmany_64(run->scrub_ns, NSEC_PER_USEC);
212 :
213 1097014065 : if (run->repair_attempted)
214 37100154 : css->repair_invocations++;
215 1097014065 : if (run->repair_succeeded)
216 8700118 : css->repair_success++;
217 1097014065 : css->repairtime_us += howmany_64(run->repair_ns, NSEC_PER_USEC);
218 1097014065 : spin_unlock(&css->css_lock);
219 1096886117 : }
220 :
221 : /* Merge these scrub-run stats into the global and mount stat data. */
222 : void
223 548610715 : xchk_stats_merge(
224 : struct xfs_mount *mp,
225 : const struct xfs_scrub_metadata *sm,
226 : const struct xchk_stats_run *run)
227 : {
228 548610715 : xchk_stats_merge_one(&global_stats, sm, run);
229 548627720 : xchk_stats_merge_one(mp->m_scrub_stats, sm, run);
230 548627864 : }
231 :
232 : /* debugfs boilerplate */
233 :
234 : static ssize_t
235 0 : xchk_scrub_stats_read(
236 : struct file *file,
237 : char __user *ubuf,
238 : size_t count,
239 : loff_t *ppos)
240 : {
241 0 : struct xchk_stats *cs = file->private_data;
242 0 : char *buf;
243 0 : size_t bufsize;
244 0 : ssize_t avail, ret;
245 :
246 : /*
247 : * This generates stringly snapshot of all the scrub counters, so we
248 : * do not want userspace to receive garbled text from multiple calls.
249 : * If the file position is greater than 0, return a short read.
250 : */
251 0 : if (*ppos > 0)
252 : return 0;
253 :
254 0 : bufsize = xchk_stats_estimate_bufsize(cs);
255 :
256 0 : buf = kvmalloc(bufsize, XCHK_GFP_FLAGS);
257 0 : if (!buf)
258 : return -ENOMEM;
259 :
260 0 : avail = xchk_stats_format(cs, buf, bufsize);
261 0 : if (avail < 0) {
262 0 : ret = avail;
263 0 : goto out;
264 : }
265 :
266 0 : ret = simple_read_from_buffer(ubuf, count, ppos, buf, avail);
267 0 : out:
268 0 : kvfree(buf);
269 0 : return ret;
270 : }
271 :
272 : static const struct file_operations scrub_stats_fops = {
273 : .open = simple_open,
274 : .read = xchk_scrub_stats_read,
275 : };
276 :
277 : static ssize_t
278 0 : xchk_clear_scrub_stats_write(
279 : struct file *file,
280 : const char __user *ubuf,
281 : size_t count,
282 : loff_t *ppos)
283 : {
284 0 : struct xchk_stats *cs = file->private_data;
285 0 : unsigned int val;
286 0 : int ret;
287 :
288 0 : ret = kstrtouint_from_user(ubuf, count, 0, &val);
289 0 : if (ret)
290 0 : return ret;
291 :
292 0 : if (val != 1)
293 : return -EINVAL;
294 :
295 0 : xchk_stats_clearall(cs);
296 0 : return count;
297 : }
298 :
299 : static const struct file_operations clear_scrub_stats_fops = {
300 : .open = simple_open,
301 : .write = xchk_clear_scrub_stats_write,
302 : };
303 :
304 : /* Initialize the stats object. */
305 : STATIC int
306 24169 : xchk_stats_init(
307 : struct xchk_stats *cs,
308 : struct xfs_mount *mp)
309 : {
310 24169 : struct xchk_scrub_stats *css = &cs->cs_stats[0];
311 24169 : unsigned int i;
312 :
313 725070 : for (i = 0; i < XFS_SCRUB_TYPE_NR; i++, css++)
314 700901 : spin_lock_init(&css->css_lock);
315 :
316 24169 : return 0;
317 : }
318 :
319 : /* Connect the stats object to debugfs. */
320 : void
321 24131 : xchk_stats_register(
322 : struct xchk_stats *cs,
323 : struct dentry *parent)
324 : {
325 24131 : if (!parent)
326 : return;
327 :
328 24131 : cs->cs_debugfs = xfs_debugfs_mkdir("scrub", parent);
329 24131 : if (!cs->cs_debugfs)
330 : return;
331 :
332 24131 : debugfs_create_file("stats", 0644, cs->cs_debugfs, cs,
333 : &scrub_stats_fops);
334 24131 : debugfs_create_file("clear_stats", 0400, cs->cs_debugfs, cs,
335 : &clear_scrub_stats_fops);
336 : }
337 :
338 : /* Free all resources related to the stats object. */
339 : STATIC int
340 : xchk_stats_teardown(
341 : struct xchk_stats *cs)
342 : {
343 : return 0;
344 : }
345 :
346 : /* Disconnect the stats object from debugfs. */
347 : void
348 24126 : xchk_stats_unregister(
349 : struct xchk_stats *cs)
350 : {
351 24126 : debugfs_remove(cs->cs_debugfs);
352 24126 : }
353 :
354 : /* Initialize global stats and register them */
355 : int __init
356 12 : xchk_global_stats_setup(
357 : struct dentry *parent)
358 : {
359 12 : int error;
360 :
361 12 : error = xchk_stats_init(&global_stats, NULL);
362 12 : if (error)
363 : return error;
364 :
365 12 : xchk_stats_register(&global_stats, parent);
366 12 : return 0;
367 : }
368 :
369 : /* Unregister global stats and tear them down */
370 : void
371 12 : xchk_global_stats_teardown(void)
372 : {
373 12 : xchk_stats_unregister(&global_stats);
374 12 : xchk_stats_teardown(&global_stats);
375 12 : }
376 :
377 : /* Allocate per-mount stats */
378 : int
379 24157 : xchk_mount_stats_alloc(
380 : struct xfs_mount *mp)
381 : {
382 24157 : struct xchk_stats *cs;
383 24157 : int error;
384 :
385 24157 : cs = kvzalloc(sizeof(struct xchk_stats), GFP_KERNEL);
386 24157 : if (!cs)
387 : return -ENOMEM;
388 :
389 24157 : error = xchk_stats_init(cs, mp);
390 24157 : if (error)
391 0 : goto out_free;
392 :
393 24157 : mp->m_scrub_stats = cs;
394 24157 : return 0;
395 : out_free:
396 0 : kvfree(cs);
397 0 : return error;
398 : }
399 :
400 : /* Free per-mount stats */
401 : void
402 24164 : xchk_mount_stats_free(
403 : struct xfs_mount *mp)
404 : {
405 24164 : xchk_stats_teardown(mp->m_scrub_stats);
406 24164 : kvfree(mp->m_scrub_stats);
407 24164 : mp->m_scrub_stats = NULL;
408 24164 : }
|