2023-08-10 07:48:07 -07:00
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright ( C ) 2023 Oracle . All Rights Reserved .
* Author : Darrick J . Wong < djwong @ kernel . org >
*/
# include "xfs.h"
# include "xfs_fs.h"
# include "xfs_shared.h"
# include "xfs_format.h"
# include "xfs_trans_resv.h"
# include "xfs_mount.h"
# include "xfs_sysfs.h"
# include "xfs_btree.h"
# include "xfs_super.h"
# include "scrub/scrub.h"
# include "scrub/stats.h"
# include "scrub/trace.h"
struct xchk_scrub_stats {
/* all 32-bit counters here */
/* checking stats */
uint32_t invocations ;
uint32_t clean ;
uint32_t corrupt ;
uint32_t preen ;
uint32_t xfail ;
uint32_t xcorrupt ;
uint32_t incomplete ;
uint32_t warning ;
uint32_t retries ;
/* repair stats */
uint32_t repair_invocations ;
uint32_t repair_success ;
/* all 64-bit items here */
/* runtimes */
uint64_t checktime_us ;
uint64_t repairtime_us ;
/* non-counter state must go at the end for clearall */
spinlock_t css_lock ;
} ;
struct xchk_stats {
struct dentry * cs_debugfs ;
struct xchk_scrub_stats cs_stats [ XFS_SCRUB_TYPE_NR ] ;
} ;
static struct xchk_stats global_stats ;
static const char * name_map [ XFS_SCRUB_TYPE_NR ] = {
[ XFS_SCRUB_TYPE_SB ] = " sb " ,
[ XFS_SCRUB_TYPE_AGF ] = " agf " ,
[ XFS_SCRUB_TYPE_AGFL ] = " agfl " ,
[ XFS_SCRUB_TYPE_AGI ] = " agi " ,
[ XFS_SCRUB_TYPE_BNOBT ] = " bnobt " ,
[ XFS_SCRUB_TYPE_CNTBT ] = " cntbt " ,
[ XFS_SCRUB_TYPE_INOBT ] = " inobt " ,
[ XFS_SCRUB_TYPE_FINOBT ] = " finobt " ,
[ XFS_SCRUB_TYPE_RMAPBT ] = " rmapbt " ,
[ XFS_SCRUB_TYPE_REFCNTBT ] = " refcountbt " ,
[ XFS_SCRUB_TYPE_INODE ] = " inode " ,
[ XFS_SCRUB_TYPE_BMBTD ] = " bmapbtd " ,
[ XFS_SCRUB_TYPE_BMBTA ] = " bmapbta " ,
[ XFS_SCRUB_TYPE_BMBTC ] = " bmapbtc " ,
[ XFS_SCRUB_TYPE_DIR ] = " directory " ,
[ XFS_SCRUB_TYPE_XATTR ] = " xattr " ,
[ XFS_SCRUB_TYPE_SYMLINK ] = " symlink " ,
[ XFS_SCRUB_TYPE_PARENT ] = " parent " ,
[ XFS_SCRUB_TYPE_RTBITMAP ] = " rtbitmap " ,
[ XFS_SCRUB_TYPE_RTSUM ] = " rtsummary " ,
[ XFS_SCRUB_TYPE_UQUOTA ] = " usrquota " ,
[ XFS_SCRUB_TYPE_GQUOTA ] = " grpquota " ,
[ XFS_SCRUB_TYPE_PQUOTA ] = " prjquota " ,
[ XFS_SCRUB_TYPE_FSCOUNTERS ] = " fscounters " ,
} ;
/* Format the scrub stats into a text buffer, similar to pcp style. */
STATIC ssize_t
xchk_stats_format (
struct xchk_stats * cs ,
char * buf ,
size_t remaining )
{
struct xchk_scrub_stats * css = & cs - > cs_stats [ 0 ] ;
unsigned int i ;
ssize_t copied = 0 ;
int ret = 0 ;
for ( i = 0 ; i < XFS_SCRUB_TYPE_NR ; i + + , css + + ) {
if ( ! name_map [ i ] )
continue ;
ret = scnprintf ( buf , remaining ,
" %s %u %u %u %u %u %u %u %u %u %llu %u %u %llu \n " ,
name_map [ i ] ,
( unsigned int ) css - > invocations ,
( unsigned int ) css - > clean ,
( unsigned int ) css - > corrupt ,
( unsigned int ) css - > preen ,
( unsigned int ) css - > xfail ,
( unsigned int ) css - > xcorrupt ,
( unsigned int ) css - > incomplete ,
( unsigned int ) css - > warning ,
( unsigned int ) css - > retries ,
( unsigned long long ) css - > checktime_us ,
( unsigned int ) css - > repair_invocations ,
( unsigned int ) css - > repair_success ,
( unsigned long long ) css - > repairtime_us ) ;
if ( ret < = 0 )
break ;
remaining - = ret ;
copied + = ret ;
buf + = ret ;
}
return copied > 0 ? copied : ret ;
}
/* Estimate the worst case buffer size required to hold the whole report. */
STATIC size_t
xchk_stats_estimate_bufsize (
struct xchk_stats * cs )
{
struct xchk_scrub_stats * css = & cs - > cs_stats [ 0 ] ;
unsigned int i ;
size_t field_width ;
size_t ret = 0 ;
/* 4294967296 plus one space for each u32 field */
field_width = 11 * ( offsetof ( struct xchk_scrub_stats , checktime_us ) /
sizeof ( uint32_t ) ) ;
/* 18446744073709551615 plus one space for each u64 field */
field_width + = 21 * ( ( offsetof ( struct xchk_scrub_stats , css_lock ) -
offsetof ( struct xchk_scrub_stats , checktime_us ) ) /
sizeof ( uint64_t ) ) ;
for ( i = 0 ; i < XFS_SCRUB_TYPE_NR ; i + + , css + + ) {
if ( ! name_map [ i ] )
continue ;
/* name plus one space */
ret + = 1 + strlen ( name_map [ i ] ) ;
/* all fields, plus newline */
ret + = field_width + 1 ;
}
return ret ;
}
/* Clear all counters. */
STATIC void
xchk_stats_clearall (
struct xchk_stats * cs )
{
struct xchk_scrub_stats * css = & cs - > cs_stats [ 0 ] ;
unsigned int i ;
for ( i = 0 ; i < XFS_SCRUB_TYPE_NR ; i + + , css + + ) {
spin_lock ( & css - > css_lock ) ;
memset ( css , 0 , offsetof ( struct xchk_scrub_stats , css_lock ) ) ;
spin_unlock ( & css - > css_lock ) ;
}
}
# define XFS_SCRUB_OFLAG_UNCLEAN (XFS_SCRUB_OFLAG_CORRUPT | \
XFS_SCRUB_OFLAG_PREEN | \
XFS_SCRUB_OFLAG_XFAIL | \
XFS_SCRUB_OFLAG_XCORRUPT | \
XFS_SCRUB_OFLAG_INCOMPLETE | \
XFS_SCRUB_OFLAG_WARNING )
STATIC void
xchk_stats_merge_one (
struct xchk_stats * cs ,
const struct xfs_scrub_metadata * sm ,
const struct xchk_stats_run * run )
{
struct xchk_scrub_stats * css ;
2023-09-11 08:39:06 -07:00
if ( sm - > sm_type > = XFS_SCRUB_TYPE_NR ) {
ASSERT ( sm - > sm_type < XFS_SCRUB_TYPE_NR ) ;
return ;
}
2023-08-10 07:48:07 -07:00
css = & cs - > cs_stats [ sm - > sm_type ] ;
spin_lock ( & css - > css_lock ) ;
css - > invocations + + ;
if ( ! ( sm - > sm_flags & XFS_SCRUB_OFLAG_UNCLEAN ) )
css - > clean + + ;
if ( sm - > sm_flags & XFS_SCRUB_OFLAG_CORRUPT )
css - > corrupt + + ;
if ( sm - > sm_flags & XFS_SCRUB_OFLAG_PREEN )
css - > preen + + ;
if ( sm - > sm_flags & XFS_SCRUB_OFLAG_XFAIL )
css - > xfail + + ;
if ( sm - > sm_flags & XFS_SCRUB_OFLAG_XCORRUPT )
css - > xcorrupt + + ;
if ( sm - > sm_flags & XFS_SCRUB_OFLAG_INCOMPLETE )
css - > incomplete + + ;
if ( sm - > sm_flags & XFS_SCRUB_OFLAG_WARNING )
css - > warning + + ;
css - > retries + = run - > retries ;
css - > checktime_us + = howmany_64 ( run - > scrub_ns , NSEC_PER_USEC ) ;
if ( run - > repair_attempted )
css - > repair_invocations + + ;
if ( run - > repair_succeeded )
css - > repair_success + + ;
css - > repairtime_us + = howmany_64 ( run - > repair_ns , NSEC_PER_USEC ) ;
spin_unlock ( & css - > css_lock ) ;
}
/* Merge these scrub-run stats into the global and mount stat data. */
void
xchk_stats_merge (
struct xfs_mount * mp ,
const struct xfs_scrub_metadata * sm ,
const struct xchk_stats_run * run )
{
xchk_stats_merge_one ( & global_stats , sm , run ) ;
xchk_stats_merge_one ( mp - > m_scrub_stats , sm , run ) ;
}
/* debugfs boilerplate */
static ssize_t
xchk_scrub_stats_read (
struct file * file ,
char __user * ubuf ,
size_t count ,
loff_t * ppos )
{
struct xchk_stats * cs = file - > private_data ;
char * buf ;
size_t bufsize ;
ssize_t avail , ret ;
/*
* This generates stringly snapshot of all the scrub counters , so we
* do not want userspace to receive garbled text from multiple calls .
* If the file position is greater than 0 , return a short read .
*/
if ( * ppos > 0 )
return 0 ;
bufsize = xchk_stats_estimate_bufsize ( cs ) ;
buf = kvmalloc ( bufsize , XCHK_GFP_FLAGS ) ;
if ( ! buf )
return - ENOMEM ;
avail = xchk_stats_format ( cs , buf , bufsize ) ;
if ( avail < 0 ) {
ret = avail ;
goto out ;
}
ret = simple_read_from_buffer ( ubuf , count , ppos , buf , avail ) ;
out :
kvfree ( buf ) ;
return ret ;
}
static const struct file_operations scrub_stats_fops = {
. open = simple_open ,
. read = xchk_scrub_stats_read ,
} ;
static ssize_t
xchk_clear_scrub_stats_write (
struct file * file ,
const char __user * ubuf ,
size_t count ,
loff_t * ppos )
{
struct xchk_stats * cs = file - > private_data ;
unsigned int val ;
int ret ;
ret = kstrtouint_from_user ( ubuf , count , 0 , & val ) ;
if ( ret )
return ret ;
if ( val ! = 1 )
return - EINVAL ;
xchk_stats_clearall ( cs ) ;
return count ;
}
static const struct file_operations clear_scrub_stats_fops = {
. open = simple_open ,
. write = xchk_clear_scrub_stats_write ,
} ;
/* Initialize the stats object. */
STATIC int
xchk_stats_init (
struct xchk_stats * cs ,
struct xfs_mount * mp )
{
struct xchk_scrub_stats * css = & cs - > cs_stats [ 0 ] ;
unsigned int i ;
for ( i = 0 ; i < XFS_SCRUB_TYPE_NR ; i + + , css + + )
spin_lock_init ( & css - > css_lock ) ;
return 0 ;
}
/* Connect the stats object to debugfs. */
void
xchk_stats_register (
struct xchk_stats * cs ,
struct dentry * parent )
{
if ( ! parent )
return ;
cs - > cs_debugfs = xfs_debugfs_mkdir ( " scrub " , parent ) ;
if ( ! cs - > cs_debugfs )
return ;
debugfs_create_file ( " stats " , 0644 , cs - > cs_debugfs , cs ,
& scrub_stats_fops ) ;
debugfs_create_file ( " clear_stats " , 0400 , cs - > cs_debugfs , cs ,
& clear_scrub_stats_fops ) ;
}
/* Free all resources related to the stats object. */
STATIC int
xchk_stats_teardown (
struct xchk_stats * cs )
{
return 0 ;
}
/* Disconnect the stats object from debugfs. */
void
xchk_stats_unregister (
struct xchk_stats * cs )
{
debugfs_remove ( cs - > cs_debugfs ) ;
}
/* Initialize global stats and register them */
int __init
xchk_global_stats_setup (
struct dentry * parent )
{
int error ;
error = xchk_stats_init ( & global_stats , NULL ) ;
if ( error )
return error ;
xchk_stats_register ( & global_stats , parent ) ;
return 0 ;
}
/* Unregister global stats and tear them down */
void
xchk_global_stats_teardown ( void )
{
xchk_stats_unregister ( & global_stats ) ;
xchk_stats_teardown ( & global_stats ) ;
}
/* Allocate per-mount stats */
int
xchk_mount_stats_alloc (
struct xfs_mount * mp )
{
struct xchk_stats * cs ;
int error ;
cs = kvzalloc ( sizeof ( struct xchk_stats ) , GFP_KERNEL ) ;
if ( ! cs )
return - ENOMEM ;
error = xchk_stats_init ( cs , mp ) ;
if ( error )
goto out_free ;
mp - > m_scrub_stats = cs ;
return 0 ;
out_free :
kvfree ( cs ) ;
return error ;
}
/* Free per-mount stats */
void
xchk_mount_stats_free (
struct xfs_mount * mp )
{
xchk_stats_teardown ( mp - > m_scrub_stats ) ;
kvfree ( mp - > m_scrub_stats ) ;
mp - > m_scrub_stats = NULL ;
}