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