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