xref: /linux/fs/xfs/scrub/stats.c (revision 546b928da0427b0d6c663cbb992bd7bfa9ac7971)
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