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