xref: /linux/fs/xfs/scrub/stats.c (revision fe67a493c7a76cf75556152af24285a114f69d34)
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
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
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 	/* null byte for scnprintf */
172 	return ret + 1;
173 }
174 
175 /* Clear all counters. */
176 STATIC void
177 xchk_stats_clearall(
178 	struct xchk_stats	*cs)
179 {
180 	struct xchk_scrub_stats	*css = &cs->cs_stats[0];
181 	unsigned int		i;
182 
183 	for (i = 0; i < XFS_SCRUB_TYPE_NR; i++, css++) {
184 		spin_lock(&css->css_lock);
185 		memset(css, 0, offsetof(struct xchk_scrub_stats, css_lock));
186 		spin_unlock(&css->css_lock);
187 	}
188 }
189 
190 #define XFS_SCRUB_OFLAG_UNCLEAN	(XFS_SCRUB_OFLAG_CORRUPT | \
191 				 XFS_SCRUB_OFLAG_PREEN | \
192 				 XFS_SCRUB_OFLAG_XFAIL | \
193 				 XFS_SCRUB_OFLAG_XCORRUPT | \
194 				 XFS_SCRUB_OFLAG_INCOMPLETE | \
195 				 XFS_SCRUB_OFLAG_WARNING)
196 
197 STATIC void
198 xchk_stats_merge_one(
199 	struct xchk_stats		*cs,
200 	const struct xfs_scrub_metadata	*sm,
201 	int				error,
202 	const struct xchk_stats_run	*run)
203 {
204 	struct xchk_scrub_stats		*css;
205 	unsigned int			sm_flags = sm->sm_flags;
206 
207 	if (sm->sm_type >= XFS_SCRUB_TYPE_NR) {
208 		ASSERT(sm->sm_type < XFS_SCRUB_TYPE_NR);
209 		return;
210 	}
211 
212 	/* caller applies this same transformation after we return */
213 	if (error == -EFSCORRUPTED || error == -EFSBADCRC) {
214 		sm_flags |= XFS_SCRUB_OFLAG_CORRUPT;
215 		error = 0;
216 	}
217 
218 	css = &cs->cs_stats[sm->sm_type];
219 	spin_lock(&css->css_lock);
220 	css->invocations++;
221 	if (error)
222 		css->runtime_errors++;
223 	else if (!(sm_flags & XFS_SCRUB_OFLAG_UNCLEAN))
224 		css->clean++;
225 	if (sm_flags & XFS_SCRUB_OFLAG_CORRUPT)
226 		css->corrupt++;
227 	if (sm_flags & XFS_SCRUB_OFLAG_PREEN)
228 		css->preen++;
229 	if (sm_flags & XFS_SCRUB_OFLAG_XFAIL)
230 		css->xfail++;
231 	if (sm_flags & XFS_SCRUB_OFLAG_XCORRUPT)
232 		css->xcorrupt++;
233 	if (sm_flags & XFS_SCRUB_OFLAG_INCOMPLETE)
234 		css->incomplete++;
235 	if (sm_flags & XFS_SCRUB_OFLAG_WARNING)
236 		css->warning++;
237 	css->retries += run->retries;
238 	css->checktime_us += howmany_64(run->scrub_ns, NSEC_PER_USEC);
239 
240 	if (run->repair_attempted)
241 		css->repair_invocations++;
242 	if (run->repair_succeeded)
243 		css->repair_success++;
244 	css->repairtime_us += howmany_64(run->repair_ns, NSEC_PER_USEC);
245 	spin_unlock(&css->css_lock);
246 }
247 
248 /* Merge these scrub-run stats into the global and mount stat data. */
249 void
250 xchk_stats_merge(
251 	struct xfs_mount		*mp,
252 	const struct xfs_scrub_metadata	*sm,
253 	int				error,
254 	const struct xchk_stats_run	*run)
255 {
256 	if (error == -ENOENT)
257 		return;
258 
259 	xchk_stats_merge_one(&global_stats, sm, error, run);
260 	xchk_stats_merge_one(mp->m_scrub_stats, sm, error, run);
261 }
262 
263 /* debugfs boilerplate */
264 
265 static ssize_t
266 xchk_scrub_stats_read(
267 	struct file		*file,
268 	char __user		*ubuf,
269 	size_t			count,
270 	loff_t			*ppos)
271 {
272 	struct xchk_stats	*cs = file->private_data;
273 	char			*buf;
274 	size_t			bufsize;
275 	ssize_t			avail, ret;
276 
277 	/*
278 	 * This generates stringly snapshot of all the scrub counters, so we
279 	 * do not want userspace to receive garbled text from multiple calls.
280 	 * If the file position is greater than 0, return a short read.
281 	 */
282 	if (*ppos > 0)
283 		return 0;
284 
285 	bufsize = xchk_stats_estimate_bufsize(cs);
286 
287 	buf = kvmalloc(bufsize, XCHK_GFP_FLAGS);
288 	if (!buf)
289 		return -ENOMEM;
290 
291 	avail = xchk_stats_format(cs, buf, bufsize);
292 	if (avail < 0) {
293 		ret = avail;
294 		goto out;
295 	}
296 
297 	ret = simple_read_from_buffer(ubuf, count, ppos, buf, avail);
298 out:
299 	kvfree(buf);
300 	return ret;
301 }
302 
303 static const struct file_operations scrub_stats_fops = {
304 	.owner			= THIS_MODULE,
305 	.open			= simple_open,
306 	.read			= xchk_scrub_stats_read,
307 };
308 
309 static ssize_t
310 xchk_clear_scrub_stats_write(
311 	struct file		*file,
312 	const char __user	*ubuf,
313 	size_t			count,
314 	loff_t			*ppos)
315 {
316 	struct xchk_stats	*cs = file->private_data;
317 	unsigned int		val;
318 	int			ret;
319 
320 	ret = kstrtouint_from_user(ubuf, count, 0, &val);
321 	if (ret)
322 		return ret;
323 
324 	if (val != 1)
325 		return -EINVAL;
326 
327 	xchk_stats_clearall(cs);
328 	return count;
329 }
330 
331 static const struct file_operations clear_scrub_stats_fops = {
332 	.owner			= THIS_MODULE,
333 	.open			= simple_open,
334 	.write			= xchk_clear_scrub_stats_write,
335 };
336 
337 /* Initialize the stats object. */
338 STATIC int
339 xchk_stats_init(
340 	struct xchk_stats	*cs,
341 	struct xfs_mount	*mp)
342 {
343 	struct xchk_scrub_stats	*css = &cs->cs_stats[0];
344 	unsigned int		i;
345 
346 	for (i = 0; i < XFS_SCRUB_TYPE_NR; i++, css++)
347 		spin_lock_init(&css->css_lock);
348 
349 	return 0;
350 }
351 
352 /* Connect the stats object to debugfs. */
353 void
354 xchk_stats_register(
355 	struct xchk_stats	*cs,
356 	struct dentry		*parent)
357 {
358 	if (!parent)
359 		return;
360 
361 	cs->cs_debugfs = xfs_debugfs_mkdir("scrub", parent);
362 	if (!cs->cs_debugfs)
363 		return;
364 
365 	debugfs_create_file("stats", 0444, cs->cs_debugfs, cs,
366 			&scrub_stats_fops);
367 	debugfs_create_file("clear_stats", 0200, cs->cs_debugfs, cs,
368 			&clear_scrub_stats_fops);
369 }
370 
371 /* Free all resources related to the stats object. */
372 STATIC int
373 xchk_stats_teardown(
374 	struct xchk_stats	*cs)
375 {
376 	return 0;
377 }
378 
379 /* Disconnect the stats object from debugfs. */
380 void
381 xchk_stats_unregister(
382 	struct xchk_stats	*cs)
383 {
384 	debugfs_remove(cs->cs_debugfs);
385 }
386 
387 /* Initialize global stats and register them */
388 int __init
389 xchk_global_stats_setup(
390 	struct dentry		*parent)
391 {
392 	int			error;
393 
394 	error = xchk_stats_init(&global_stats, NULL);
395 	if (error)
396 		return error;
397 
398 	xchk_stats_register(&global_stats, parent);
399 	return 0;
400 }
401 
402 /* Unregister global stats and tear them down */
403 void
404 xchk_global_stats_teardown(void)
405 {
406 	xchk_stats_unregister(&global_stats);
407 	xchk_stats_teardown(&global_stats);
408 }
409 
410 /* Allocate per-mount stats */
411 int
412 xchk_mount_stats_alloc(
413 	struct xfs_mount	*mp)
414 {
415 	struct xchk_stats	*cs;
416 	int			error;
417 
418 	cs = kvzalloc_obj(struct xchk_stats);
419 	if (!cs)
420 		return -ENOMEM;
421 
422 	error = xchk_stats_init(cs, mp);
423 	if (error)
424 		goto out_free;
425 
426 	mp->m_scrub_stats = cs;
427 	return 0;
428 out_free:
429 	kvfree(cs);
430 	return error;
431 }
432 
433 /* Free per-mount stats */
434 void
435 xchk_mount_stats_free(
436 	struct xfs_mount	*mp)
437 {
438 	xchk_stats_teardown(mp->m_scrub_stats);
439 	kvfree(mp->m_scrub_stats);
440 	mp->m_scrub_stats = NULL;
441 }
442