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