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 return ret; 172 } 173 174 /* Clear all counters. */ 175 STATIC void 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 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 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 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 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 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 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 370 xchk_stats_teardown( 371 struct xchk_stats *cs) 372 { 373 return 0; 374 } 375 376 /* Disconnect the stats object from debugfs. */ 377 void 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 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 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 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 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