With CONFIG_FS_IOSTATS, filesystem can attach an array of counters to struct super_block by calling sb_iostats_init(). These counters will be used to collect per-sb I/O statistics and display them in /proc/<pid>/mountstats. Signed-off-by: Amir Goldstein <amir73il@xxxxxxxxx> --- fs/Kconfig | 8 +++ fs/super.c | 2 + include/linux/fs.h | 10 ++- include/linux/fs_iostats.h | 127 +++++++++++++++++++++++++++++++++++++ 4 files changed, 145 insertions(+), 2 deletions(-) create mode 100644 include/linux/fs_iostats.h diff --git a/fs/Kconfig b/fs/Kconfig index 6c7dc1387beb..394d9da6bda9 100644 --- a/fs/Kconfig +++ b/fs/Kconfig @@ -15,6 +15,14 @@ config VALIDATE_FS_PARSER Enable this to perform validation of the parameter description for a filesystem when it is registered. +config FS_IOSTATS + bool "Enable generic filesystem I/O statistics" + help + Enable this to allow collecting filesystem I/O statistics and display + them in /proc/<pid>/mountstats. + + Say N if unsure. + config FS_IOMAP bool diff --git a/fs/super.c b/fs/super.c index f1d4a193602d..c447cadb402b 100644 --- a/fs/super.c +++ b/fs/super.c @@ -36,6 +36,7 @@ #include <linux/lockdep.h> #include <linux/user_namespace.h> #include <linux/fs_context.h> +#include <linux/fs_iostats.h> #include <uapi/linux/mount.h> #include "internal.h" @@ -290,6 +291,7 @@ static void __put_super(struct super_block *s) WARN_ON(s->s_dentry_lru.node); WARN_ON(s->s_inode_lru.node); WARN_ON(!list_empty(&s->s_mounts)); + sb_iostats_destroy(s); security_sb_free(s); fscrypt_sb_free(s); put_user_ns(s->s_user_ns); diff --git a/include/linux/fs.h b/include/linux/fs.h index e2d892b201b0..a71c94cbb6c1 100644 --- a/include/linux/fs.h +++ b/include/linux/fs.h @@ -1454,6 +1454,8 @@ struct sb_writers { struct percpu_rw_semaphore rw_sem[SB_FREEZE_LEVELS]; }; +struct sb_iostats; + struct super_block { struct list_head s_list; /* Keep this first */ dev_t s_dev; /* search index; _not_ kdev_t */ @@ -1508,8 +1510,12 @@ struct super_block { /* Granularity of c/m/atime in ns (cannot be worse than a second) */ u32 s_time_gran; /* Time limits for c/m/atime in seconds */ - time64_t s_time_min; - time64_t s_time_max; + time64_t s_time_min; + time64_t s_time_max; +#ifdef CONFIG_FS_IOSTATS + /* Optional per-sb I/O stats */ + struct sb_iostats *s_iostats; +#endif #ifdef CONFIG_FSNOTIFY __u32 s_fsnotify_mask; struct fsnotify_mark_connector __rcu *s_fsnotify_marks; diff --git a/include/linux/fs_iostats.h b/include/linux/fs_iostats.h new file mode 100644 index 000000000000..60d1efbea7d9 --- /dev/null +++ b/include/linux/fs_iostats.h @@ -0,0 +1,127 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef _LINUX_FS_IOSTATS_H +#define _LINUX_FS_IOSTATS_H + +#include <linux/fs.h> +#include <linux/percpu_counter.h> +#include <linux/slab.h> + +/* Similar to task_io_accounting members */ +enum { + SB_IOSTATS_CHARS_RD, /* bytes read via syscalls */ + SB_IOSTATS_CHARS_WR, /* bytes written via syscalls */ + SB_IOSTATS_SYSCALLS_RD, /* # of read syscalls */ + SB_IOSTATS_SYSCALLS_WR, /* # of write syscalls */ + SB_IOSTATS_COUNTERS_NUM +}; + +struct sb_iostats { + time64_t start_time; + struct percpu_counter counter[SB_IOSTATS_COUNTERS_NUM]; +}; + +#ifdef CONFIG_FS_IOSTATS +static inline struct sb_iostats *sb_iostats(struct super_block *sb) +{ + return sb->s_iostats; +} + +static inline bool sb_has_iostats(struct super_block *sb) +{ + return !!sb->s_iostats; +} + +static inline void sb_iostats_destroy(struct super_block *sb) +{ + if (!sb->s_iostats) + return; + + percpu_counters_destroy(sb->s_iostats->counter, + SB_IOSTATS_COUNTERS_NUM); + kfree(sb->s_iostats); + sb->s_iostats = NULL; +} + +/* Initialize per-sb I/O stats */ +static inline int sb_iostats_init(struct super_block *sb) +{ + int err; + + sb->s_iostats = kmalloc(sizeof(struct sb_iostats), GFP_KERNEL); + if (!sb->s_iostats) + return -ENOMEM; + + err = percpu_counters_init(sb->s_iostats->counter, + SB_IOSTATS_COUNTERS_NUM, 0, GFP_KERNEL); + if (err) { + kfree(sb->s_iostats); + sb->s_iostats = NULL; + return err; + } + + sb->s_iostats->start_time = ktime_get_seconds(); + return 0; +} + +static inline void sb_iostats_counter_inc(struct super_block *sb, int id) +{ + if (!sb->s_iostats) + return; + + percpu_counter_inc(&sb->s_iostats->counter[id]); +} + +static inline void sb_iostats_counter_add(struct super_block *sb, int id, + s64 amt) +{ + if (!sb->s_iostats) + return; + + percpu_counter_add(&sb->s_iostats->counter[id], amt); +} + +static inline s64 sb_iostats_counter_read(struct super_block *sb, int id) +{ + if (!sb->s_iostats) + return 0; + + return percpu_counter_read_positive(&sb->s_iostats->counter[id]); +} + +#else /* !CONFIG_FS_IOSTATS */ + +static inline struct sb_iostats *sb_iostats(struct super_block *sb) +{ + return NULL; +} + +static inline bool sb_has_iostats(struct super_block *sb) +{ + return false; +} + +static inline int sb_iostats_init(struct super_block *sb) +{ + return -EOPNOTSUPP; +} + +static inline void sb_iostats_destroy(struct super_block *sb) +{ +} + +static inline void sb_iostats_counter_inc(struct super_block *sb, int id) +{ +} + +static inline void sb_iostats_counter_add(struct super_block *sb, int id, + s64 amt) +{ +} + +static inline s64 sb_iostats_counter_read(struct super_block *sb, int id) +{ + return 0; +} +#endif + +#endif /* _LINUX_FS_IOSTATS_H */ -- 2.25.1