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 : [XFS_SCRUB_TYPE_RGSUPER] = "rgsuper",
84 : [XFS_SCRUB_TYPE_RGBITMAP] = "rgbitmap",
85 : [XFS_SCRUB_TYPE_RTRMAPBT] = "rtrmapbt",
86 : [XFS_SCRUB_TYPE_RTREFCBT] = "rtrefcountbt",
87 : };
88 :
89 : /* Format the scrub stats into a text buffer, similar to pcp style. */
90 : STATIC ssize_t
91 0 : xchk_stats_format(
92 : struct xchk_stats *cs,
93 : char *buf,
94 : size_t remaining)
95 : {
96 0 : struct xchk_scrub_stats *css = &cs->cs_stats[0];
97 0 : unsigned int i;
98 0 : ssize_t copied = 0;
99 0 : int ret = 0;
100 :
101 0 : for (i = 0; i < XFS_SCRUB_TYPE_NR; i++, css++) {
102 0 : if (!name_map[i])
103 0 : continue;
104 :
105 0 : ret = scnprintf(buf, remaining,
106 : "%s %u %u %u %u %u %u %u %u %u %llu %u %u %llu\n",
107 : name_map[i],
108 0 : (unsigned int)css->invocations,
109 0 : (unsigned int)css->clean,
110 0 : (unsigned int)css->corrupt,
111 0 : (unsigned int)css->preen,
112 0 : (unsigned int)css->xfail,
113 0 : (unsigned int)css->xcorrupt,
114 0 : (unsigned int)css->incomplete,
115 0 : (unsigned int)css->warning,
116 0 : (unsigned int)css->retries,
117 0 : (unsigned long long)css->checktime_us,
118 0 : (unsigned int)css->repair_invocations,
119 0 : (unsigned int)css->repair_success,
120 0 : (unsigned long long)css->repairtime_us);
121 0 : if (ret <= 0)
122 : break;
123 :
124 0 : remaining -= ret;
125 0 : copied += ret;
126 0 : buf += ret;
127 : }
128 :
129 0 : return copied > 0 ? copied : ret;
130 : }
131 :
132 : /* Estimate the worst case buffer size required to hold the whole report. */
133 : STATIC size_t
134 0 : xchk_stats_estimate_bufsize(
135 : struct xchk_stats *cs)
136 : {
137 0 : struct xchk_scrub_stats *css = &cs->cs_stats[0];
138 0 : unsigned int i;
139 0 : size_t field_width;
140 0 : size_t ret = 0;
141 :
142 : /* 4294967296 plus one space for each u32 field */
143 0 : field_width = 11 * (offsetof(struct xchk_scrub_stats, checktime_us) /
144 : sizeof(uint32_t));
145 :
146 : /* 18446744073709551615 plus one space for each u64 field */
147 0 : field_width += 21 * ((offsetof(struct xchk_scrub_stats, css_lock) -
148 : offsetof(struct xchk_scrub_stats, checktime_us)) /
149 : sizeof(uint64_t));
150 :
151 0 : for (i = 0; i < XFS_SCRUB_TYPE_NR; i++, css++) {
152 0 : if (!name_map[i])
153 0 : continue;
154 :
155 : /* name plus one space */
156 0 : ret += 1 + strlen(name_map[i]);
157 :
158 : /* all fields, plus newline */
159 0 : ret += field_width + 1;
160 : }
161 :
162 0 : return ret;
163 : }
164 :
165 : /* Clear all counters. */
166 : STATIC void
167 0 : xchk_stats_clearall(
168 : struct xchk_stats *cs)
169 : {
170 0 : struct xchk_scrub_stats *css = &cs->cs_stats[0];
171 0 : unsigned int i;
172 :
173 0 : for (i = 0; i < XFS_SCRUB_TYPE_NR; i++, css++) {
174 0 : spin_lock(&css->css_lock);
175 0 : memset(css, 0, offsetof(struct xchk_scrub_stats, css_lock));
176 0 : spin_unlock(&css->css_lock);
177 : }
178 0 : }
179 :
180 : #define XFS_SCRUB_OFLAG_UNCLEAN (XFS_SCRUB_OFLAG_CORRUPT | \
181 : XFS_SCRUB_OFLAG_PREEN | \
182 : XFS_SCRUB_OFLAG_XFAIL | \
183 : XFS_SCRUB_OFLAG_XCORRUPT | \
184 : XFS_SCRUB_OFLAG_INCOMPLETE | \
185 : XFS_SCRUB_OFLAG_WARNING)
186 :
187 : STATIC void
188 857070886 : xchk_stats_merge_one(
189 : struct xchk_stats *cs,
190 : const struct xfs_scrub_metadata *sm,
191 : const struct xchk_stats_run *run)
192 : {
193 857070886 : struct xchk_scrub_stats *css;
194 :
195 857070886 : ASSERT(sm->sm_type < XFS_SCRUB_TYPE_NR);
196 :
197 857070886 : css = &cs->cs_stats[sm->sm_type];
198 857070886 : spin_lock(&css->css_lock);
199 857065341 : css->invocations++;
200 857065341 : if (!(sm->sm_flags & XFS_SCRUB_OFLAG_UNCLEAN))
201 847213195 : css->clean++;
202 857065341 : if (sm->sm_flags & XFS_SCRUB_OFLAG_CORRUPT)
203 36 : css->corrupt++;
204 857065341 : if (sm->sm_flags & XFS_SCRUB_OFLAG_PREEN)
205 9867966 : css->preen++;
206 857065341 : if (sm->sm_flags & XFS_SCRUB_OFLAG_XFAIL)
207 0 : css->xfail++;
208 857065341 : if (sm->sm_flags & XFS_SCRUB_OFLAG_XCORRUPT)
209 4 : css->xcorrupt++;
210 857065341 : if (sm->sm_flags & XFS_SCRUB_OFLAG_INCOMPLETE)
211 58 : css->incomplete++;
212 857065341 : if (sm->sm_flags & XFS_SCRUB_OFLAG_WARNING)
213 54 : css->warning++;
214 857065341 : css->retries += run->retries;
215 857065341 : css->checktime_us += howmany_64(run->scrub_ns, NSEC_PER_USEC);
216 :
217 857065341 : if (run->repair_attempted)
218 3487116 : css->repair_invocations++;
219 857065341 : if (run->repair_succeeded)
220 3481647 : css->repair_success++;
221 857065341 : css->repairtime_us += howmany_64(run->repair_ns, NSEC_PER_USEC);
222 857065341 : spin_unlock(&css->css_lock);
223 857084639 : }
224 :
225 : /* Merge these scrub-run stats into the global and mount stat data. */
226 : void
227 428752544 : xchk_stats_merge(
228 : struct xfs_mount *mp,
229 : const struct xfs_scrub_metadata *sm,
230 : const struct xchk_stats_run *run)
231 : {
232 428752544 : xchk_stats_merge_one(&global_stats, sm, run);
233 428746199 : xchk_stats_merge_one(mp->m_scrub_stats, sm, run);
234 428745019 : }
235 :
236 : /* debugfs boilerplate */
237 :
238 : static ssize_t
239 0 : xchk_scrub_stats_read(
240 : struct file *file,
241 : char __user *ubuf,
242 : size_t count,
243 : loff_t *ppos)
244 : {
245 0 : struct xchk_stats *cs = file->private_data;
246 0 : char *buf;
247 0 : size_t bufsize;
248 0 : ssize_t avail, ret;
249 :
250 : /*
251 : * This generates stringly snapshot of all the scrub counters, so we
252 : * do not want userspace to receive garbled text from multiple calls.
253 : * If the file position is greater than 0, return a short read.
254 : */
255 0 : if (*ppos > 0)
256 : return 0;
257 :
258 0 : bufsize = xchk_stats_estimate_bufsize(cs);
259 :
260 0 : buf = kvmalloc(bufsize, XCHK_GFP_FLAGS);
261 0 : if (!buf)
262 : return -ENOMEM;
263 :
264 0 : avail = xchk_stats_format(cs, buf, bufsize);
265 0 : if (avail < 0) {
266 0 : ret = avail;
267 0 : goto out;
268 : }
269 :
270 0 : ret = simple_read_from_buffer(ubuf, count, ppos, buf, avail);
271 0 : out:
272 0 : kvfree(buf);
273 0 : return ret;
274 : }
275 :
276 : static const struct file_operations scrub_stats_fops = {
277 : .open = simple_open,
278 : .read = xchk_scrub_stats_read,
279 : };
280 :
281 : static ssize_t
282 0 : xchk_clear_scrub_stats_write(
283 : struct file *file,
284 : const char __user *ubuf,
285 : size_t count,
286 : loff_t *ppos)
287 : {
288 0 : struct xchk_stats *cs = file->private_data;
289 0 : unsigned int val;
290 0 : int ret;
291 :
292 0 : ret = kstrtouint_from_user(ubuf, count, 0, &val);
293 0 : if (ret)
294 0 : return ret;
295 :
296 0 : if (val != 1)
297 : return -EINVAL;
298 :
299 0 : xchk_stats_clearall(cs);
300 0 : return count;
301 : }
302 :
303 : static const struct file_operations clear_scrub_stats_fops = {
304 : .open = simple_open,
305 : .write = xchk_clear_scrub_stats_write,
306 : };
307 :
308 : /* Initialize the stats object. */
309 : STATIC int
310 24387 : xchk_stats_init(
311 : struct xchk_stats *cs,
312 : struct xfs_mount *mp)
313 : {
314 24387 : struct xchk_scrub_stats *css = &cs->cs_stats[0];
315 24387 : unsigned int i;
316 :
317 829158 : for (i = 0; i < XFS_SCRUB_TYPE_NR; i++, css++)
318 804771 : spin_lock_init(&css->css_lock);
319 :
320 24387 : return 0;
321 : }
322 :
323 : /* Connect the stats object to debugfs. */
324 : void
325 24349 : xchk_stats_register(
326 : struct xchk_stats *cs,
327 : struct dentry *parent)
328 : {
329 24349 : if (!parent)
330 : return;
331 :
332 24349 : cs->cs_debugfs = xfs_debugfs_mkdir("scrub", parent);
333 24349 : if (!cs->cs_debugfs)
334 : return;
335 :
336 24349 : debugfs_create_file("stats", 0644, cs->cs_debugfs, cs,
337 : &scrub_stats_fops);
338 24349 : debugfs_create_file("clear_stats", 0400, cs->cs_debugfs, cs,
339 : &clear_scrub_stats_fops);
340 : }
341 :
342 : /* Free all resources related to the stats object. */
343 : STATIC int
344 : xchk_stats_teardown(
345 : struct xchk_stats *cs)
346 : {
347 : return 0;
348 : }
349 :
350 : /* Disconnect the stats object from debugfs. */
351 : void
352 24343 : xchk_stats_unregister(
353 : struct xchk_stats *cs)
354 : {
355 24343 : debugfs_remove(cs->cs_debugfs);
356 24343 : }
357 :
358 : /* Initialize global stats and register them */
359 : int __init
360 12 : xchk_global_stats_setup(
361 : struct dentry *parent)
362 : {
363 12 : int error;
364 :
365 12 : error = xchk_stats_init(&global_stats, NULL);
366 12 : if (error)
367 : return error;
368 :
369 12 : xchk_stats_register(&global_stats, parent);
370 12 : return 0;
371 : }
372 :
373 : /* Unregister global stats and tear them down */
374 : void
375 12 : xchk_global_stats_teardown(void)
376 : {
377 12 : xchk_stats_unregister(&global_stats);
378 12 : xchk_stats_teardown(&global_stats);
379 12 : }
380 :
381 : /* Allocate per-mount stats */
382 : int
383 24375 : xchk_mount_stats_alloc(
384 : struct xfs_mount *mp)
385 : {
386 24375 : struct xchk_stats *cs;
387 24375 : int error;
388 :
389 24375 : cs = kvzalloc(sizeof(struct xchk_stats), GFP_KERNEL);
390 24375 : if (!cs)
391 : return -ENOMEM;
392 :
393 24375 : error = xchk_stats_init(cs, mp);
394 24375 : if (error)
395 0 : goto out_free;
396 :
397 24375 : mp->m_scrub_stats = cs;
398 24375 : return 0;
399 : out_free:
400 0 : kvfree(cs);
401 0 : return error;
402 : }
403 :
404 : /* Free per-mount stats */
405 : void
406 24381 : xchk_mount_stats_free(
407 : struct xfs_mount *mp)
408 : {
409 24381 : xchk_stats_teardown(mp->m_scrub_stats);
410 24381 : kvfree(mp->m_scrub_stats);
411 24381 : mp->m_scrub_stats = NULL;
412 24381 : }
|