* [PATCH v6 0/9] fs: multigrain timestamp redux
@ 2024-07-15 12:48 Jeff Layton
2024-07-15 12:48 ` [PATCH v6 1/9] fs: add infrastructure for multigrain timestamps Jeff Layton
` (9 more replies)
0 siblings, 10 replies; 32+ messages in thread
From: Jeff Layton @ 2024-07-15 12:48 UTC (permalink / raw)
To: Alexander Viro, Christian Brauner, Jan Kara, Steven Rostedt,
Masami Hiramatsu, Mathieu Desnoyers, Chandan Babu R,
Darrick J. Wong, Theodore Ts'o, Andreas Dilger, Chris Mason,
Josef Bacik, David Sterba, Hugh Dickins, Andrew Morton,
Jonathan Corbet
Cc: Dave Chinner, Andi Kleen, Christoph Hellwig, Uros Bizjak,
Kent Overstreet, Arnd Bergmann, Randy Dunlap, linux-fsdevel,
linux-kernel, linux-trace-kernel, linux-xfs, linux-ext4,
linux-btrfs, linux-mm, linux-nfs, linux-doc, Jeff Layton
I think this is pretty much ready for linux-next now. Since the latest
changes are pretty minimal, I've left the Reviewed-by's intact. It would
be nice to have acks or reviews from maintainers for ext4 and tmpfs too.
I did try to plumb this into bcachefs too, but the way it handles
timestamps makes that pretty difficult. It keeps the active copies in an
internal representation of the on-disk inode and periodically copies
them to struct inode. This is backward from the way most blockdev
filesystems do this.
Christian, would you be willing to pick these up with an eye toward
v6.12 after the merge window settles?
Thanks!
Signed-off-by: Jeff Layton <jlayton@kernel.org>
---
Changes in v6:
- Normalize timespec64 in inode_set_ctime_to_ts
- use DEFINE_PER_CPU counters for better vfs consistency
- skip ctime cmpxchg if the result means nothing will change
- add trace_ctime_xchg_skip to track skipped ctime updates
- use __print_flags in ctime_ns_xchg tracepoint
- Link to v5: https://lore.kernel.org/r/20240711-mgtime-v5-0-37bb5b465feb@kernel.org
Changes in v5:
- refetch coarse time in coarse_ctime if not returning floor
- timestamp_truncate before swapping new ctime value into place
- track floor value as atomic64_t
- cleanups to Documentation file
- Link to v4: https://lore.kernel.org/r/20240708-mgtime-v4-0-a0f3c6fb57f3@kernel.org
Changes in v4:
- reordered tracepoint fields for better packing
- rework percpu counters again to also count fine grained timestamps
- switch to try_cmpxchg for better efficiency
- Link to v3: https://lore.kernel.org/r/20240705-mgtime-v3-0-85b2daa9b335@kernel.org
Changes in v3:
- Drop the conversion of i_ctime fields to ktime_t, and use an unused bit
of the i_ctime_nsec field as QUERIED flag.
- Better tracepoints for tracking floor and ctime updates
- Reworked percpu counters to be more useful
- Track floor as monotonic value, which eliminates clock-jump problem
Changes in v2:
- Added Documentation file
- Link to v1: https://lore.kernel.org/r/20240626-mgtime-v1-0-a189352d0f8f@kernel.org
---
Jeff Layton (9):
fs: add infrastructure for multigrain timestamps
fs: tracepoints around multigrain timestamp events
fs: add percpu counters for significant multigrain timestamp events
fs: have setattr_copy handle multigrain timestamps appropriately
Documentation: add a new file documenting multigrain timestamps
xfs: switch to multigrain timestamps
ext4: switch to multigrain timestamps
btrfs: convert to multigrain timestamps
tmpfs: add support for multigrain timestamps
Documentation/filesystems/multigrain-ts.rst | 120 +++++++++++++
fs/attr.c | 52 +++++-
fs/btrfs/file.c | 25 +--
fs/btrfs/super.c | 3 +-
fs/ext4/super.c | 2 +-
fs/inode.c | 251 +++++++++++++++++++++++++---
fs/stat.c | 39 ++++-
fs/xfs/libxfs/xfs_trans_inode.c | 6 +-
fs/xfs/xfs_iops.c | 10 +-
fs/xfs/xfs_super.c | 2 +-
include/linux/fs.h | 34 +++-
include/trace/events/timestamp.h | 124 ++++++++++++++
mm/shmem.c | 2 +-
13 files changed, 592 insertions(+), 78 deletions(-)
---
base-commit: bb83a76c647a96db4c9ae77b0577170da4d7bd77
change-id: 20240626-mgtime-5cd80b18d810
Best regards,
--
Jeff Layton <jlayton@kernel.org>
^ permalink raw reply [flat|nested] 32+ messages in thread
* [PATCH v6 1/9] fs: add infrastructure for multigrain timestamps
2024-07-15 12:48 [PATCH v6 0/9] fs: multigrain timestamp redux Jeff Layton
@ 2024-07-15 12:48 ` Jeff Layton
2024-07-17 10:40 ` Jan Kara
` (2 more replies)
2024-07-15 12:48 ` [PATCH v6 2/9] fs: tracepoints around multigrain timestamp events Jeff Layton
` (8 subsequent siblings)
9 siblings, 3 replies; 32+ messages in thread
From: Jeff Layton @ 2024-07-15 12:48 UTC (permalink / raw)
To: Alexander Viro, Christian Brauner, Jan Kara, Steven Rostedt,
Masami Hiramatsu, Mathieu Desnoyers, Chandan Babu R,
Darrick J. Wong, Theodore Ts'o, Andreas Dilger, Chris Mason,
Josef Bacik, David Sterba, Hugh Dickins, Andrew Morton,
Jonathan Corbet
Cc: Dave Chinner, Andi Kleen, Christoph Hellwig, Uros Bizjak,
Kent Overstreet, Arnd Bergmann, Randy Dunlap, linux-fsdevel,
linux-kernel, linux-trace-kernel, linux-xfs, linux-ext4,
linux-btrfs, linux-mm, linux-nfs, linux-doc, Jeff Layton
The VFS has always used coarse-grained timestamps when updating the
ctime and mtime after a change. This has the benefit of allowing
filesystems to optimize away a lot metadata updates, down to around 1
per jiffy, even when a file is under heavy writes.
Unfortunately, this has always been an issue when we're exporting via
NFSv3, which relies on timestamps to validate caches. A lot of changes
can happen in a jiffy, so timestamps aren't sufficient to help the
client decide when to invalidate the cache. Even with NFSv4, a lot of
exported filesystems don't properly support a change attribute and are
subject to the same problems with timestamp granularity. Other
applications have similar issues with timestamps (e.g backup
applications).
If we were to always use fine-grained timestamps, that would improve the
situation, but that becomes rather expensive, as the underlying
filesystem would have to log a lot more metadata updates.
What we need is a way to only use fine-grained timestamps when they are
being actively queried. Use the (unused) top bit in inode->i_ctime_nsec
as a flag that indicates whether the current timestamps have been
queried via stat() or the like. When it's set, we allow the kernel to
use a fine-grained timestamp iff it's necessary to make the ctime show
a different value.
This solves the problem of being able to distinguish the timestamp
between updates, but introduces a new problem: it's now possible for a
file being changed to get a fine-grained timestamp. A file that is
altered just a bit later can then get a coarse-grained one that appears
older than the earlier fine-grained time. This violates timestamp
ordering guarantees.
To remedy this, keep a global monotonic atomic64_t value that acts as a
timestamp floor. When we go to stamp a file, we first get the latter of
the current floor value and the current coarse-grained time. If the
inode ctime hasn't been queried then we just attempt to stamp it with
that value.
If it has been queried, then first see whether the current coarse time
is later than the existing ctime. If it is, then we accept that value.
If it isn't, then we get a fine-grained time and try to swap that into
the global floor. Whether that succeeds or fails, we take the resulting
floor time, convert it to realtime and try to swap that into the ctime.
We take the result of the ctime swap whether it succeeds or fails, since
either is just as valid.
Filesystems can opt into this by setting the FS_MGTIME fstype flag.
Others should be unaffected (other than being subject to the same floor
value as multigrain filesystems).
Reviewed-by: Darrick J. Wong <djwong@kernel.org>
Reviewed-by: Josef Bacik <josef@toxicpanda.com>
Signed-off-by: Jeff Layton <jlayton@kernel.org>
---
fs/inode.c | 176 +++++++++++++++++++++++++++++++++++++++++++++--------
fs/stat.c | 36 ++++++++++-
include/linux/fs.h | 34 ++++++++---
3 files changed, 209 insertions(+), 37 deletions(-)
diff --git a/fs/inode.c b/fs/inode.c
index f356fe2ec2b6..417acbeabef3 100644
--- a/fs/inode.c
+++ b/fs/inode.c
@@ -60,6 +60,13 @@ static unsigned int i_hash_shift __ro_after_init;
static struct hlist_head *inode_hashtable __ro_after_init;
static __cacheline_aligned_in_smp DEFINE_SPINLOCK(inode_hash_lock);
+/*
+ * This represents the latest fine-grained time that we have handed out as a
+ * timestamp on the system. Tracked as a monotonic value, and converted to the
+ * realtime clock on an as-needed basis.
+ */
+static __cacheline_aligned_in_smp atomic64_t ctime_floor;
+
/*
* Empty aops. Can be used for the cases where the user does not
* define any of the address_space operations.
@@ -2127,19 +2134,72 @@ int file_remove_privs(struct file *file)
}
EXPORT_SYMBOL(file_remove_privs);
+/**
+ * coarse_ctime - return the current coarse-grained time
+ * @floor: current (monotonic) ctime_floor value
+ *
+ * Get the coarse-grained time, and then determine whether to
+ * return it or the current floor value. Returns the later of the
+ * floor and coarse grained timestamps, converted to realtime
+ * clock value.
+ */
+static ktime_t coarse_ctime(ktime_t floor)
+{
+ ktime_t coarse = ktime_get_coarse();
+
+ /* If coarse time is already newer, return that */
+ if (!ktime_after(floor, coarse))
+ return ktime_get_coarse_real();
+ return ktime_mono_to_real(floor);
+}
+
+/**
+ * current_time - Return FS time (possibly fine-grained)
+ * @inode: inode.
+ *
+ * Return the current time truncated to the time granularity supported by
+ * the fs, as suitable for a ctime/mtime change. If the ctime is flagged
+ * as having been QUERIED, get a fine-grained timestamp.
+ */
+struct timespec64 current_time(struct inode *inode)
+{
+ ktime_t floor = atomic64_read(&ctime_floor);
+ ktime_t now = coarse_ctime(floor);
+ struct timespec64 now_ts = ktime_to_timespec64(now);
+ u32 cns;
+
+ if (!is_mgtime(inode))
+ goto out;
+
+ /* If nothing has queried it, then coarse time is fine */
+ cns = smp_load_acquire(&inode->i_ctime_nsec);
+ if (cns & I_CTIME_QUERIED) {
+ /*
+ * If there is no apparent change, then
+ * get a fine-grained timestamp.
+ */
+ if (now_ts.tv_nsec == (cns & ~I_CTIME_QUERIED))
+ ktime_get_real_ts64(&now_ts);
+ }
+out:
+ return timestamp_truncate(now_ts, inode);
+}
+EXPORT_SYMBOL(current_time);
+
static int inode_needs_update_time(struct inode *inode)
{
+ struct timespec64 now, ts;
int sync_it = 0;
- struct timespec64 now = current_time(inode);
- struct timespec64 ts;
/* First try to exhaust all avenues to not sync */
if (IS_NOCMTIME(inode))
return 0;
+ now = current_time(inode);
+
ts = inode_get_mtime(inode);
if (!timespec64_equal(&ts, &now))
- sync_it = S_MTIME;
+ sync_it |= S_MTIME;
ts = inode_get_ctime(inode);
if (!timespec64_equal(&ts, &now))
@@ -2507,6 +2567,15 @@ void inode_nohighmem(struct inode *inode)
}
EXPORT_SYMBOL(inode_nohighmem);
+struct timespec64 inode_set_ctime_to_ts(struct inode *inode, struct timespec64 ts)
+{
+ set_normalized_timespec64(&ts, ts.tv_sec, ts.tv_nsec);
+ inode->i_ctime_sec = ts.tv_sec;
+ inode->i_ctime_nsec = ts.tv_nsec;
+ return ts;
+}
+EXPORT_SYMBOL(inode_set_ctime_to_ts);
+
/**
* timestamp_truncate - Truncate timespec to a granularity
* @t: Timespec
@@ -2538,38 +2607,91 @@ struct timespec64 timestamp_truncate(struct timespec64 t, struct inode *inode)
}
EXPORT_SYMBOL(timestamp_truncate);
-/**
- * current_time - Return FS time
- * @inode: inode.
- *
- * Return the current time truncated to the time granularity supported by
- * the fs.
- *
- * Note that inode and inode->sb cannot be NULL.
- * Otherwise, the function warns and returns time without truncation.
- */
-struct timespec64 current_time(struct inode *inode)
-{
- struct timespec64 now;
-
- ktime_get_coarse_real_ts64(&now);
- return timestamp_truncate(now, inode);
-}
-EXPORT_SYMBOL(current_time);
-
/**
* inode_set_ctime_current - set the ctime to current_time
* @inode: inode
*
- * Set the inode->i_ctime to the current value for the inode. Returns
- * the current value that was assigned to i_ctime.
+ * Set the inode's ctime to the current value for the inode. Returns the
+ * current value that was assigned. If this is not a multigrain inode, then we
+ * just set it to whatever the coarse_ctime is.
+ *
+ * If it is multigrain, then we first see if the coarse-grained timestamp is
+ * distinct from what we have. If so, then we'll just use that. If we have to
+ * get a fine-grained timestamp, then do so, and try to swap it into the floor.
+ * We accept the new floor value regardless of the outcome of the cmpxchg.
+ * After that, we try to swap the new value into i_ctime_nsec. Again, we take
+ * the resulting ctime, regardless of the outcome of the swap.
*/
struct timespec64 inode_set_ctime_current(struct inode *inode)
{
- struct timespec64 now = current_time(inode);
+ ktime_t now, floor = atomic64_read(&ctime_floor);
+ struct timespec64 now_ts;
+ u32 cns, cur;
+
+ now = coarse_ctime(floor);
+
+ /* Just return that if this is not a multigrain fs */
+ if (!is_mgtime(inode)) {
+ now_ts = timestamp_truncate(ktime_to_timespec64(now), inode);
+ inode_set_ctime_to_ts(inode, now_ts);
+ goto out;
+ }
+
+ /*
+ * We only need a fine-grained time if someone has queried it,
+ * and the current coarse grained time isn't later than what's
+ * already there.
+ */
+ cns = smp_load_acquire(&inode->i_ctime_nsec);
+ if (cns & I_CTIME_QUERIED) {
+ ktime_t ctime = ktime_set(inode->i_ctime_sec, cns & ~I_CTIME_QUERIED);
+
+ if (!ktime_after(now, ctime)) {
+ ktime_t old, fine;
+
+ /* Get a fine-grained time */
+ fine = ktime_get();
- inode_set_ctime_to_ts(inode, now);
- return now;
+ /*
+ * If the cmpxchg works, we take the new floor value. If
+ * not, then that means that someone else changed it after we
+ * fetched it but before we got here. That value is just
+ * as good, so keep it.
+ */
+ old = floor;
+ if (!atomic64_try_cmpxchg(&ctime_floor, &old, fine))
+ fine = old;
+ now = ktime_mono_to_real(fine);
+ }
+ }
+ now_ts = timestamp_truncate(ktime_to_timespec64(now), inode);
+ cur = cns;
+
+ /* No need to cmpxchg if it's exactly the same */
+ if (cns == now_ts.tv_nsec && inode->i_ctime_sec == now_ts.tv_sec)
+ goto out;
+retry:
+ /* Try to swap the nsec value into place. */
+ if (try_cmpxchg(&inode->i_ctime_nsec, &cur, now_ts.tv_nsec)) {
+ /* If swap occurred, then we're (mostly) done */
+ inode->i_ctime_sec = now_ts.tv_sec;
+ } else {
+ /*
+ * Was the change due to someone marking the old ctime QUERIED?
+ * If so then retry the swap. This can only happen once since
+ * the only way to clear I_CTIME_QUERIED is to stamp the inode
+ * with a new ctime.
+ */
+ if (!(cns & I_CTIME_QUERIED) && (cns | I_CTIME_QUERIED) == cur) {
+ cns = cur;
+ goto retry;
+ }
+ /* Otherwise, keep the existing ctime */
+ now_ts.tv_sec = inode->i_ctime_sec;
+ now_ts.tv_nsec = cur & ~I_CTIME_QUERIED;
+ }
+out:
+ return now_ts;
}
EXPORT_SYMBOL(inode_set_ctime_current);
diff --git a/fs/stat.c b/fs/stat.c
index 6f65b3456cad..df7fdd3afed9 100644
--- a/fs/stat.c
+++ b/fs/stat.c
@@ -26,6 +26,32 @@
#include "internal.h"
#include "mount.h"
+/**
+ * fill_mg_cmtime - Fill in the mtime and ctime and flag ctime as QUERIED
+ * @stat: where to store the resulting values
+ * @request_mask: STATX_* values requested
+ * @inode: inode from which to grab the c/mtime
+ *
+ * Given @inode, grab the ctime and mtime out if it and store the result
+ * in @stat. When fetching the value, flag it as queried so the next write
+ * will ensure a distinct timestamp.
+ */
+void fill_mg_cmtime(struct kstat *stat, u32 request_mask, struct inode *inode)
+{
+ atomic_t *pcn = (atomic_t *)&inode->i_ctime_nsec;
+
+ /* If neither time was requested, then don't report them */
+ if (!(request_mask & (STATX_CTIME|STATX_MTIME))) {
+ stat->result_mask &= ~(STATX_CTIME|STATX_MTIME);
+ return;
+ }
+
+ stat->mtime = inode_get_mtime(inode);
+ stat->ctime.tv_sec = inode->i_ctime_sec;
+ stat->ctime.tv_nsec = ((u32)atomic_fetch_or(I_CTIME_QUERIED, pcn)) & ~I_CTIME_QUERIED;
+}
+EXPORT_SYMBOL(fill_mg_cmtime);
+
/**
* generic_fillattr - Fill in the basic attributes from the inode struct
* @idmap: idmap of the mount the inode was found from
@@ -58,8 +84,14 @@ void generic_fillattr(struct mnt_idmap *idmap, u32 request_mask,
stat->rdev = inode->i_rdev;
stat->size = i_size_read(inode);
stat->atime = inode_get_atime(inode);
- stat->mtime = inode_get_mtime(inode);
- stat->ctime = inode_get_ctime(inode);
+
+ if (is_mgtime(inode)) {
+ fill_mg_cmtime(stat, request_mask, inode);
+ } else {
+ stat->ctime = inode_get_ctime(inode);
+ stat->mtime = inode_get_mtime(inode);
+ }
+
stat->blksize = i_blocksize(inode);
stat->blocks = inode->i_blocks;
diff --git a/include/linux/fs.h b/include/linux/fs.h
index dc9f9c4b2572..f873f6c58669 100644
--- a/include/linux/fs.h
+++ b/include/linux/fs.h
@@ -1608,6 +1608,17 @@ static inline struct timespec64 inode_set_mtime(struct inode *inode,
return inode_set_mtime_to_ts(inode, ts);
}
+/*
+ * Multigrain timestamps
+ *
+ * Conditionally use fine-grained ctime and mtime timestamps when there
+ * are users actively observing them via getattr. The primary use-case
+ * for this is NFS clients that use the ctime to distinguish between
+ * different states of the file, and that are often fooled by multiple
+ * operations that occur in the same coarse-grained timer tick.
+ */
+#define I_CTIME_QUERIED ((u32)BIT(31))
+
static inline time64_t inode_get_ctime_sec(const struct inode *inode)
{
return inode->i_ctime_sec;
@@ -1615,7 +1626,7 @@ static inline time64_t inode_get_ctime_sec(const struct inode *inode)
static inline long inode_get_ctime_nsec(const struct inode *inode)
{
- return inode->i_ctime_nsec;
+ return inode->i_ctime_nsec & ~I_CTIME_QUERIED;
}
static inline struct timespec64 inode_get_ctime(const struct inode *inode)
@@ -1626,13 +1637,7 @@ static inline struct timespec64 inode_get_ctime(const struct inode *inode)
return ts;
}
-static inline struct timespec64 inode_set_ctime_to_ts(struct inode *inode,
- struct timespec64 ts)
-{
- inode->i_ctime_sec = ts.tv_sec;
- inode->i_ctime_nsec = ts.tv_nsec;
- return ts;
-}
+struct timespec64 inode_set_ctime_to_ts(struct inode *inode, struct timespec64 ts);
/**
* inode_set_ctime - set the ctime in the inode
@@ -2490,6 +2495,7 @@ struct file_system_type {
#define FS_USERNS_MOUNT 8 /* Can be mounted by userns root */
#define FS_DISALLOW_NOTIFY_PERM 16 /* Disable fanotify permission events */
#define FS_ALLOW_IDMAP 32 /* FS has been updated to handle vfs idmappings. */
+#define FS_MGTIME 64 /* FS uses multigrain timestamps */
#define FS_RENAME_DOES_D_MOVE 32768 /* FS will handle d_move() during rename() internally. */
int (*init_fs_context)(struct fs_context *);
const struct fs_parameter_spec *parameters;
@@ -2513,6 +2519,17 @@ struct file_system_type {
#define MODULE_ALIAS_FS(NAME) MODULE_ALIAS("fs-" NAME)
+/**
+ * is_mgtime: is this inode using multigrain timestamps
+ * @inode: inode to test for multigrain timestamps
+ *
+ * Return true if the inode uses multigrain timestamps, false otherwise.
+ */
+static inline bool is_mgtime(const struct inode *inode)
+{
+ return inode->i_sb->s_type->fs_flags & FS_MGTIME;
+}
+
extern struct dentry *mount_bdev(struct file_system_type *fs_type,
int flags, const char *dev_name, void *data,
int (*fill_super)(struct super_block *, void *, int));
@@ -3252,6 +3269,7 @@ extern void page_put_link(void *);
extern int page_symlink(struct inode *inode, const char *symname, int len);
extern const struct inode_operations page_symlink_inode_operations;
extern void kfree_link(void *);
+void fill_mg_cmtime(struct kstat *stat, u32 request_mask, struct inode *inode);
void generic_fillattr(struct mnt_idmap *, u32, struct inode *, struct kstat *);
void generic_fill_statx_attr(struct inode *inode, struct kstat *stat);
extern int vfs_getattr_nosec(const struct path *, struct kstat *, u32, unsigned int);
--
2.45.2
^ permalink raw reply related [flat|nested] 32+ messages in thread
* [PATCH v6 2/9] fs: tracepoints around multigrain timestamp events
2024-07-15 12:48 [PATCH v6 0/9] fs: multigrain timestamp redux Jeff Layton
2024-07-15 12:48 ` [PATCH v6 1/9] fs: add infrastructure for multigrain timestamps Jeff Layton
@ 2024-07-15 12:48 ` Jeff Layton
2024-07-15 18:29 ` Darrick J. Wong
2024-07-17 10:41 ` Jan Kara
2024-07-15 12:48 ` [PATCH v6 3/9] fs: add percpu counters for significant " Jeff Layton
` (7 subsequent siblings)
9 siblings, 2 replies; 32+ messages in thread
From: Jeff Layton @ 2024-07-15 12:48 UTC (permalink / raw)
To: Alexander Viro, Christian Brauner, Jan Kara, Steven Rostedt,
Masami Hiramatsu, Mathieu Desnoyers, Chandan Babu R,
Darrick J. Wong, Theodore Ts'o, Andreas Dilger, Chris Mason,
Josef Bacik, David Sterba, Hugh Dickins, Andrew Morton,
Jonathan Corbet
Cc: Dave Chinner, Andi Kleen, Christoph Hellwig, Uros Bizjak,
Kent Overstreet, Arnd Bergmann, Randy Dunlap, linux-fsdevel,
linux-kernel, linux-trace-kernel, linux-xfs, linux-ext4,
linux-btrfs, linux-mm, linux-nfs, linux-doc, Jeff Layton
Add some tracepoints around various multigrain timestamp events.
Reviewed-by: Josef Bacik <josef@toxicpanda.com>
Signed-off-by: Jeff Layton <jlayton@kernel.org>
---
fs/inode.c | 9 ++-
fs/stat.c | 3 +
include/trace/events/timestamp.h | 124 +++++++++++++++++++++++++++++++++++++++
3 files changed, 135 insertions(+), 1 deletion(-)
diff --git a/fs/inode.c b/fs/inode.c
index 417acbeabef3..869994285e87 100644
--- a/fs/inode.c
+++ b/fs/inode.c
@@ -22,6 +22,9 @@
#include <linux/iversion.h>
#include <linux/rw_hint.h>
#include <trace/events/writeback.h>
+#define CREATE_TRACE_POINTS
+#include <trace/events/timestamp.h>
+
#include "internal.h"
/*
@@ -2569,6 +2572,7 @@ EXPORT_SYMBOL(inode_nohighmem);
struct timespec64 inode_set_ctime_to_ts(struct inode *inode, struct timespec64 ts)
{
+ trace_inode_set_ctime_to_ts(inode, &ts);
set_normalized_timespec64(&ts, ts.tv_sec, ts.tv_nsec);
inode->i_ctime_sec = ts.tv_sec;
inode->i_ctime_nsec = ts.tv_nsec;
@@ -2668,13 +2672,16 @@ struct timespec64 inode_set_ctime_current(struct inode *inode)
cur = cns;
/* No need to cmpxchg if it's exactly the same */
- if (cns == now_ts.tv_nsec && inode->i_ctime_sec == now_ts.tv_sec)
+ if (cns == now_ts.tv_nsec && inode->i_ctime_sec == now_ts.tv_sec) {
+ trace_ctime_xchg_skip(inode, &now_ts);
goto out;
+ }
retry:
/* Try to swap the nsec value into place. */
if (try_cmpxchg(&inode->i_ctime_nsec, &cur, now_ts.tv_nsec)) {
/* If swap occurred, then we're (mostly) done */
inode->i_ctime_sec = now_ts.tv_sec;
+ trace_ctime_ns_xchg(inode, cns, now_ts.tv_nsec, cur);
} else {
/*
* Was the change due to someone marking the old ctime QUERIED?
diff --git a/fs/stat.c b/fs/stat.c
index df7fdd3afed9..552dfd67688b 100644
--- a/fs/stat.c
+++ b/fs/stat.c
@@ -23,6 +23,8 @@
#include <linux/uaccess.h>
#include <asm/unistd.h>
+#include <trace/events/timestamp.h>
+
#include "internal.h"
#include "mount.h"
@@ -49,6 +51,7 @@ void fill_mg_cmtime(struct kstat *stat, u32 request_mask, struct inode *inode)
stat->mtime = inode_get_mtime(inode);
stat->ctime.tv_sec = inode->i_ctime_sec;
stat->ctime.tv_nsec = ((u32)atomic_fetch_or(I_CTIME_QUERIED, pcn)) & ~I_CTIME_QUERIED;
+ trace_fill_mg_cmtime(inode, &stat->ctime, &stat->mtime);
}
EXPORT_SYMBOL(fill_mg_cmtime);
diff --git a/include/trace/events/timestamp.h b/include/trace/events/timestamp.h
new file mode 100644
index 000000000000..c9e5ec930054
--- /dev/null
+++ b/include/trace/events/timestamp.h
@@ -0,0 +1,124 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#undef TRACE_SYSTEM
+#define TRACE_SYSTEM timestamp
+
+#if !defined(_TRACE_TIMESTAMP_H) || defined(TRACE_HEADER_MULTI_READ)
+#define _TRACE_TIMESTAMP_H
+
+#include <linux/tracepoint.h>
+#include <linux/fs.h>
+
+#define CTIME_QUERIED_FLAGS \
+ { I_CTIME_QUERIED, "Q" }
+
+DECLARE_EVENT_CLASS(ctime,
+ TP_PROTO(struct inode *inode,
+ struct timespec64 *ctime),
+
+ TP_ARGS(inode, ctime),
+
+ TP_STRUCT__entry(
+ __field(dev_t, dev)
+ __field(ino_t, ino)
+ __field(time64_t, ctime_s)
+ __field(u32, ctime_ns)
+ __field(u32, gen)
+ ),
+
+ TP_fast_assign(
+ __entry->dev = inode->i_sb->s_dev;
+ __entry->ino = inode->i_ino;
+ __entry->gen = inode->i_generation;
+ __entry->ctime_s = ctime->tv_sec;
+ __entry->ctime_ns = ctime->tv_nsec;
+ ),
+
+ TP_printk("ino=%d:%d:%ld:%u ctime=%lld.%u",
+ MAJOR(__entry->dev), MINOR(__entry->dev), __entry->ino, __entry->gen,
+ __entry->ctime_s, __entry->ctime_ns
+ )
+);
+
+DEFINE_EVENT(ctime, inode_set_ctime_to_ts,
+ TP_PROTO(struct inode *inode,
+ struct timespec64 *ctime),
+ TP_ARGS(inode, ctime));
+
+DEFINE_EVENT(ctime, ctime_xchg_skip,
+ TP_PROTO(struct inode *inode,
+ struct timespec64 *ctime),
+ TP_ARGS(inode, ctime));
+
+TRACE_EVENT(ctime_ns_xchg,
+ TP_PROTO(struct inode *inode,
+ u32 old,
+ u32 new,
+ u32 cur),
+
+ TP_ARGS(inode, old, new, cur),
+
+ TP_STRUCT__entry(
+ __field(dev_t, dev)
+ __field(ino_t, ino)
+ __field(u32, gen)
+ __field(u32, old)
+ __field(u32, new)
+ __field(u32, cur)
+ ),
+
+ TP_fast_assign(
+ __entry->dev = inode->i_sb->s_dev;
+ __entry->ino = inode->i_ino;
+ __entry->gen = inode->i_generation;
+ __entry->old = old;
+ __entry->new = new;
+ __entry->cur = cur;
+ ),
+
+ TP_printk("ino=%d:%d:%ld:%u old=%u:%s new=%u cur=%u:%s",
+ MAJOR(__entry->dev), MINOR(__entry->dev), __entry->ino, __entry->gen,
+ __entry->old & ~I_CTIME_QUERIED,
+ __print_flags(__entry->old & I_CTIME_QUERIED, "|", CTIME_QUERIED_FLAGS),
+ __entry->new,
+ __entry->cur & ~I_CTIME_QUERIED,
+ __print_flags(__entry->cur & I_CTIME_QUERIED, "|", CTIME_QUERIED_FLAGS)
+ )
+);
+
+TRACE_EVENT(fill_mg_cmtime,
+ TP_PROTO(struct inode *inode,
+ struct timespec64 *ctime,
+ struct timespec64 *mtime),
+
+ TP_ARGS(inode, ctime, mtime),
+
+ TP_STRUCT__entry(
+ __field(dev_t, dev)
+ __field(ino_t, ino)
+ __field(time64_t, ctime_s)
+ __field(time64_t, mtime_s)
+ __field(u32, ctime_ns)
+ __field(u32, mtime_ns)
+ __field(u32, gen)
+ ),
+
+ TP_fast_assign(
+ __entry->dev = inode->i_sb->s_dev;
+ __entry->ino = inode->i_ino;
+ __entry->gen = inode->i_generation;
+ __entry->ctime_s = ctime->tv_sec;
+ __entry->mtime_s = mtime->tv_sec;
+ __entry->ctime_ns = ctime->tv_nsec;
+ __entry->mtime_ns = mtime->tv_nsec;
+ ),
+
+ TP_printk("ino=%d:%d:%ld:%u ctime=%lld.%u mtime=%lld.%u",
+ MAJOR(__entry->dev), MINOR(__entry->dev), __entry->ino, __entry->gen,
+ __entry->ctime_s, __entry->ctime_ns,
+ __entry->mtime_s, __entry->mtime_ns
+ )
+);
+#endif /* _TRACE_TIMESTAMP_H */
+
+/* This part must be outside protection */
+#include <trace/define_trace.h>
--
2.45.2
^ permalink raw reply related [flat|nested] 32+ messages in thread
* [PATCH v6 3/9] fs: add percpu counters for significant multigrain timestamp events
2024-07-15 12:48 [PATCH v6 0/9] fs: multigrain timestamp redux Jeff Layton
2024-07-15 12:48 ` [PATCH v6 1/9] fs: add infrastructure for multigrain timestamps Jeff Layton
2024-07-15 12:48 ` [PATCH v6 2/9] fs: tracepoints around multigrain timestamp events Jeff Layton
@ 2024-07-15 12:48 ` Jeff Layton
2024-07-15 18:32 ` Darrick J. Wong
2024-07-15 12:48 ` [PATCH v6 4/9] fs: have setattr_copy handle multigrain timestamps appropriately Jeff Layton
` (6 subsequent siblings)
9 siblings, 1 reply; 32+ messages in thread
From: Jeff Layton @ 2024-07-15 12:48 UTC (permalink / raw)
To: Alexander Viro, Christian Brauner, Jan Kara, Steven Rostedt,
Masami Hiramatsu, Mathieu Desnoyers, Chandan Babu R,
Darrick J. Wong, Theodore Ts'o, Andreas Dilger, Chris Mason,
Josef Bacik, David Sterba, Hugh Dickins, Andrew Morton,
Jonathan Corbet
Cc: Dave Chinner, Andi Kleen, Christoph Hellwig, Uros Bizjak,
Kent Overstreet, Arnd Bergmann, Randy Dunlap, linux-fsdevel,
linux-kernel, linux-trace-kernel, linux-xfs, linux-ext4,
linux-btrfs, linux-mm, linux-nfs, linux-doc, Jeff Layton
Four percpu counters for counting various stats around mgtimes, and a
new debugfs file for displaying them:
- number of attempted ctime updates
- number of successful i_ctime_nsec swaps
- number of fine-grained timestamp fetches
- number of floor value swaps
Reviewed-by: Josef Bacik <josef@toxicpanda.com>
Signed-off-by: Jeff Layton <jlayton@kernel.org>
---
fs/inode.c | 70 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
1 file changed, 69 insertions(+), 1 deletion(-)
diff --git a/fs/inode.c b/fs/inode.c
index 869994285e87..fff844345c35 100644
--- a/fs/inode.c
+++ b/fs/inode.c
@@ -21,6 +21,8 @@
#include <linux/list_lru.h>
#include <linux/iversion.h>
#include <linux/rw_hint.h>
+#include <linux/seq_file.h>
+#include <linux/debugfs.h>
#include <trace/events/writeback.h>
#define CREATE_TRACE_POINTS
#include <trace/events/timestamp.h>
@@ -80,6 +82,10 @@ EXPORT_SYMBOL(empty_aops);
static DEFINE_PER_CPU(unsigned long, nr_inodes);
static DEFINE_PER_CPU(unsigned long, nr_unused);
+static DEFINE_PER_CPU(unsigned long, mg_ctime_updates);
+static DEFINE_PER_CPU(unsigned long, mg_fine_stamps);
+static DEFINE_PER_CPU(unsigned long, mg_floor_swaps);
+static DEFINE_PER_CPU(unsigned long, mg_ctime_swaps);
static struct kmem_cache *inode_cachep __ro_after_init;
@@ -101,6 +107,42 @@ static inline long get_nr_inodes_unused(void)
return sum < 0 ? 0 : sum;
}
+static long get_mg_ctime_updates(void)
+{
+ int i;
+ long sum = 0;
+ for_each_possible_cpu(i)
+ sum += per_cpu(mg_ctime_updates, i);
+ return sum < 0 ? 0 : sum;
+}
+
+static long get_mg_fine_stamps(void)
+{
+ int i;
+ long sum = 0;
+ for_each_possible_cpu(i)
+ sum += per_cpu(mg_fine_stamps, i);
+ return sum < 0 ? 0 : sum;
+}
+
+static long get_mg_floor_swaps(void)
+{
+ int i;
+ long sum = 0;
+ for_each_possible_cpu(i)
+ sum += per_cpu(mg_floor_swaps, i);
+ return sum < 0 ? 0 : sum;
+}
+
+static long get_mg_ctime_swaps(void)
+{
+ int i;
+ long sum = 0;
+ for_each_possible_cpu(i)
+ sum += per_cpu(mg_ctime_swaps, i);
+ return sum < 0 ? 0 : sum;
+}
+
long get_nr_dirty_inodes(void)
{
/* not actually dirty inodes, but a wild approximation */
@@ -2655,6 +2697,7 @@ struct timespec64 inode_set_ctime_current(struct inode *inode)
/* Get a fine-grained time */
fine = ktime_get();
+ this_cpu_inc(mg_fine_stamps);
/*
* If the cmpxchg works, we take the new floor value. If
@@ -2663,11 +2706,14 @@ struct timespec64 inode_set_ctime_current(struct inode *inode)
* as good, so keep it.
*/
old = floor;
- if (!atomic64_try_cmpxchg(&ctime_floor, &old, fine))
+ if (atomic64_try_cmpxchg(&ctime_floor, &old, fine))
+ this_cpu_inc(mg_floor_swaps);
+ else
fine = old;
now = ktime_mono_to_real(fine);
}
}
+ this_cpu_inc(mg_ctime_updates);
now_ts = timestamp_truncate(ktime_to_timespec64(now), inode);
cur = cns;
@@ -2682,6 +2728,7 @@ struct timespec64 inode_set_ctime_current(struct inode *inode)
/* If swap occurred, then we're (mostly) done */
inode->i_ctime_sec = now_ts.tv_sec;
trace_ctime_ns_xchg(inode, cns, now_ts.tv_nsec, cur);
+ this_cpu_inc(mg_ctime_swaps);
} else {
/*
* Was the change due to someone marking the old ctime QUERIED?
@@ -2751,3 +2798,24 @@ umode_t mode_strip_sgid(struct mnt_idmap *idmap,
return mode & ~S_ISGID;
}
EXPORT_SYMBOL(mode_strip_sgid);
+
+static int mgts_show(struct seq_file *s, void *p)
+{
+ long ctime_updates = get_mg_ctime_updates();
+ long ctime_swaps = get_mg_ctime_swaps();
+ long fine_stamps = get_mg_fine_stamps();
+ long floor_swaps = get_mg_floor_swaps();
+
+ seq_printf(s, "%lu %lu %lu %lu\n",
+ ctime_updates, ctime_swaps, fine_stamps, floor_swaps);
+ return 0;
+}
+
+DEFINE_SHOW_ATTRIBUTE(mgts);
+
+static int __init mg_debugfs_init(void)
+{
+ debugfs_create_file("multigrain_timestamps", S_IFREG | S_IRUGO, NULL, NULL, &mgts_fops);
+ return 0;
+}
+late_initcall(mg_debugfs_init);
--
2.45.2
^ permalink raw reply related [flat|nested] 32+ messages in thread
* [PATCH v6 4/9] fs: have setattr_copy handle multigrain timestamps appropriately
2024-07-15 12:48 [PATCH v6 0/9] fs: multigrain timestamp redux Jeff Layton
` (2 preceding siblings ...)
2024-07-15 12:48 ` [PATCH v6 3/9] fs: add percpu counters for significant " Jeff Layton
@ 2024-07-15 12:48 ` Jeff Layton
2024-07-17 11:24 ` Jan Kara
2024-07-15 12:48 ` [PATCH v6 5/9] Documentation: add a new file documenting multigrain timestamps Jeff Layton
` (5 subsequent siblings)
9 siblings, 1 reply; 32+ messages in thread
From: Jeff Layton @ 2024-07-15 12:48 UTC (permalink / raw)
To: Alexander Viro, Christian Brauner, Jan Kara, Steven Rostedt,
Masami Hiramatsu, Mathieu Desnoyers, Chandan Babu R,
Darrick J. Wong, Theodore Ts'o, Andreas Dilger, Chris Mason,
Josef Bacik, David Sterba, Hugh Dickins, Andrew Morton,
Jonathan Corbet
Cc: Dave Chinner, Andi Kleen, Christoph Hellwig, Uros Bizjak,
Kent Overstreet, Arnd Bergmann, Randy Dunlap, linux-fsdevel,
linux-kernel, linux-trace-kernel, linux-xfs, linux-ext4,
linux-btrfs, linux-mm, linux-nfs, linux-doc, Jeff Layton
The setattr codepath is still using coarse-grained timestamps, even on
multigrain filesystems. To fix this, we need to fetch the timestamp for
ctime updates later, at the point where the assignment occurs in
setattr_copy.
On a multigrain inode, ignore the ia_ctime in the attrs, and always
update the ctime to the current clock value. Update the atime and mtime
with the same value (if needed) unless they are being set to other
specific values, a'la utimes().
Note that we don't want to do this universally however, as some
filesystems (e.g. most networked fs) want to do an explicit update
elsewhere before updating the local inode.
Reviewed-by: Darrick J. Wong <djwong@kernel.org>
Reviewed-by: Josef Bacik <josef@toxicpanda.com>
Signed-off-by: Jeff Layton <jlayton@kernel.org>
---
fs/attr.c | 52 ++++++++++++++++++++++++++++++++++++++++++++++------
1 file changed, 46 insertions(+), 6 deletions(-)
diff --git a/fs/attr.c b/fs/attr.c
index 825007d5cda4..e03ea6951864 100644
--- a/fs/attr.c
+++ b/fs/attr.c
@@ -271,6 +271,42 @@ int inode_newsize_ok(const struct inode *inode, loff_t offset)
}
EXPORT_SYMBOL(inode_newsize_ok);
+/**
+ * setattr_copy_mgtime - update timestamps for mgtime inodes
+ * @inode: inode timestamps to be updated
+ * @attr: attrs for the update
+ *
+ * With multigrain timestamps, we need to take more care to prevent races
+ * when updating the ctime. Always update the ctime to the very latest
+ * using the standard mechanism, and use that to populate the atime and
+ * mtime appropriately (unless we're setting those to specific values).
+ */
+static void setattr_copy_mgtime(struct inode *inode, const struct iattr *attr)
+{
+ unsigned int ia_valid = attr->ia_valid;
+ struct timespec64 now;
+
+ /*
+ * If the ctime isn't being updated then nothing else should be
+ * either.
+ */
+ if (!(ia_valid & ATTR_CTIME)) {
+ WARN_ON_ONCE(ia_valid & (ATTR_ATIME|ATTR_MTIME));
+ return;
+ }
+
+ now = inode_set_ctime_current(inode);
+ if (ia_valid & ATTR_ATIME_SET)
+ inode_set_atime_to_ts(inode, attr->ia_atime);
+ else if (ia_valid & ATTR_ATIME)
+ inode_set_atime_to_ts(inode, now);
+
+ if (ia_valid & ATTR_MTIME_SET)
+ inode_set_mtime_to_ts(inode, attr->ia_mtime);
+ else if (ia_valid & ATTR_MTIME)
+ inode_set_mtime_to_ts(inode, now);
+}
+
/**
* setattr_copy - copy simple metadata updates into the generic inode
* @idmap: idmap of the mount the inode was found from
@@ -303,12 +339,6 @@ void setattr_copy(struct mnt_idmap *idmap, struct inode *inode,
i_uid_update(idmap, attr, inode);
i_gid_update(idmap, attr, inode);
- if (ia_valid & ATTR_ATIME)
- inode_set_atime_to_ts(inode, attr->ia_atime);
- if (ia_valid & ATTR_MTIME)
- inode_set_mtime_to_ts(inode, attr->ia_mtime);
- if (ia_valid & ATTR_CTIME)
- inode_set_ctime_to_ts(inode, attr->ia_ctime);
if (ia_valid & ATTR_MODE) {
umode_t mode = attr->ia_mode;
if (!in_group_or_capable(idmap, inode,
@@ -316,6 +346,16 @@ void setattr_copy(struct mnt_idmap *idmap, struct inode *inode,
mode &= ~S_ISGID;
inode->i_mode = mode;
}
+
+ if (is_mgtime(inode))
+ return setattr_copy_mgtime(inode, attr);
+
+ if (ia_valid & ATTR_ATIME)
+ inode_set_atime_to_ts(inode, attr->ia_atime);
+ if (ia_valid & ATTR_MTIME)
+ inode_set_mtime_to_ts(inode, attr->ia_mtime);
+ if (ia_valid & ATTR_CTIME)
+ inode_set_ctime_to_ts(inode, attr->ia_ctime);
}
EXPORT_SYMBOL(setattr_copy);
--
2.45.2
^ permalink raw reply related [flat|nested] 32+ messages in thread
* [PATCH v6 5/9] Documentation: add a new file documenting multigrain timestamps
2024-07-15 12:48 [PATCH v6 0/9] fs: multigrain timestamp redux Jeff Layton
` (3 preceding siblings ...)
2024-07-15 12:48 ` [PATCH v6 4/9] fs: have setattr_copy handle multigrain timestamps appropriately Jeff Layton
@ 2024-07-15 12:48 ` Jeff Layton
2024-07-15 18:45 ` Darrick J. Wong
` (2 more replies)
2024-07-15 12:48 ` [PATCH v6 6/9] xfs: switch to " Jeff Layton
` (4 subsequent siblings)
9 siblings, 3 replies; 32+ messages in thread
From: Jeff Layton @ 2024-07-15 12:48 UTC (permalink / raw)
To: Alexander Viro, Christian Brauner, Jan Kara, Steven Rostedt,
Masami Hiramatsu, Mathieu Desnoyers, Chandan Babu R,
Darrick J. Wong, Theodore Ts'o, Andreas Dilger, Chris Mason,
Josef Bacik, David Sterba, Hugh Dickins, Andrew Morton,
Jonathan Corbet
Cc: Dave Chinner, Andi Kleen, Christoph Hellwig, Uros Bizjak,
Kent Overstreet, Arnd Bergmann, Randy Dunlap, linux-fsdevel,
linux-kernel, linux-trace-kernel, linux-xfs, linux-ext4,
linux-btrfs, linux-mm, linux-nfs, linux-doc, Jeff Layton
Add a high-level document that describes how multigrain timestamps work,
rationale for them, and some info about implementation and tradeoffs.
Reviewed-by: Josef Bacik <josef@toxicpanda.com>
Signed-off-by: Jeff Layton <jlayton@kernel.org>
---
Documentation/filesystems/multigrain-ts.rst | 120 ++++++++++++++++++++++++++++
1 file changed, 120 insertions(+)
diff --git a/Documentation/filesystems/multigrain-ts.rst b/Documentation/filesystems/multigrain-ts.rst
new file mode 100644
index 000000000000..5cefc204ecec
--- /dev/null
+++ b/Documentation/filesystems/multigrain-ts.rst
@@ -0,0 +1,120 @@
+.. SPDX-License-Identifier: GPL-2.0
+
+=====================
+Multigrain Timestamps
+=====================
+
+Introduction
+============
+Historically, the kernel has always used coarse time values to stamp
+inodes. This value is updated on every jiffy, so any change that happens
+within that jiffy will end up with the same timestamp.
+
+When the kernel goes to stamp an inode (due to a read or write), it first gets
+the current time and then compares it to the existing timestamp(s) to see
+whether anything will change. If nothing changed, then it can avoid updating
+the inode's metadata.
+
+Coarse timestamps are therefore good from a performance standpoint, since they
+reduce the need for metadata updates, but bad from the standpoint of
+determining whether anything has changed, since a lot of things can happen in a
+jiffy.
+
+They are particularly troublesome with NFSv3, where unchanging timestamps can
+make it difficult to tell whether to invalidate caches. NFSv4 provides a
+dedicated change attribute that should always show a visible change, but not
+all filesystems implement this properly, causing the NFS server to substitute
+the ctime in many cases.
+
+Multigrain timestamps aim to remedy this by selectively using fine-grained
+timestamps when a file has had its timestamps queried recently, and the current
+coarse-grained time does not cause a change.
+
+Inode Timestamps
+================
+There are currently 3 timestamps in the inode that are updated to the current
+wallclock time on different activity:
+
+ctime:
+ The inode change time. This is stamped with the current time whenever
+ the inode's metadata is changed. Note that this value is not settable
+ from userland.
+
+mtime:
+ The inode modification time. This is stamped with the current time
+ any time a file's contents change.
+
+atime:
+ The inode access time. This is stamped whenever an inode's contents are
+ read. Widely considered to be a terrible mistake. Usually avoided with
+ options like noatime or relatime.
+
+Updating the mtime always implies a change to the ctime, but updating the
+atime due to a read request does not.
+
+Multigrain timestamps are only tracked for the ctime and the mtime. atimes are
+not affected and always use the coarse-grained value (subject to the floor).
+
+Inode Timestamp Ordering
+========================
+
+In addition to just providing info about changes to individual files, file
+timestamps also serve an important purpose in applications like "make". These
+programs measure timestamps in order to determine whether source files might be
+newer than cached objects.
+
+Userland applications like make can only determine ordering based on
+operational boundaries. For a syscall those are the syscall entry and exit
+points. For io_uring or nfsd operations, that's the request submission and
+response. In the case of concurrent operations, userland can make no
+determination about the order in which things will occur.
+
+For instance, if a single thread modifies one file, and then another file in
+sequence, the second file must show an equal or later mtime than the first. The
+same is true if two threads are issuing similar operations that do not overlap
+in time.
+
+If however, two threads have racing syscalls that overlap in time, then there
+is no such guarantee, and the second file may appear to have been modified
+before, after or at the same time as the first, regardless of which one was
+submitted first.
+
+Multigrain Timestamps
+=====================
+Multigrain timestamps are aimed at ensuring that changes to a single file are
+always recognizable, without violating the ordering guarantees when multiple
+different files are modified. This affects the mtime and the ctime, but the
+atime will always use coarse-grained timestamps.
+
+It uses an unused bit in the i_ctime_nsec field to indicate whether the mtime
+or ctime has been queried. If either or both have, then the kernel takes
+special care to ensure the next timestamp update will display a visible change.
+This ensures tight cache coherency for use-cases like NFS, without sacrificing
+the benefits of reduced metadata updates when files aren't being watched.
+
+The Ctime Floor Value
+=====================
+It's not sufficient to simply use fine or coarse-grained timestamps based on
+whether the mtime or ctime has been queried. A file could get a fine grained
+timestamp, and then a second file modified later could get a coarse-grained one
+that appears earlier than the first, which would break the kernel's timestamp
+ordering guarantees.
+
+To mitigate this problem, we maintain a global floor value that ensures that
+this can't happen. The two files in the above example may appear to have been
+modified at the same time in such a case, but they will never show the reverse
+order. To avoid problems with realtime clock jumps, the floor is managed as a
+monotonic ktime_t, and the values are converted to realtime clock values as
+needed.
+
+Implementation Notes
+====================
+Multigrain timestamps are intended for use by local filesystems that get
+ctime values from the local clock. This is in contrast to network filesystems
+and the like that just mirror timestamp values from a server.
+
+For most filesystems, it's sufficient to just set the FS_MGTIME flag in the
+fstype->fs_flags in order to opt-in, providing the ctime is only ever set via
+inode_set_ctime_current(). If the filesystem has a ->getattr routine that
+doesn't call generic_fillattr, then you should have it call fill_mg_cmtime to
+fill those values.
--
2.45.2
^ permalink raw reply related [flat|nested] 32+ messages in thread
* [PATCH v6 6/9] xfs: switch to multigrain timestamps
2024-07-15 12:48 [PATCH v6 0/9] fs: multigrain timestamp redux Jeff Layton
` (4 preceding siblings ...)
2024-07-15 12:48 ` [PATCH v6 5/9] Documentation: add a new file documenting multigrain timestamps Jeff Layton
@ 2024-07-15 12:48 ` Jeff Layton
2024-07-15 18:47 ` Darrick J. Wong
2024-07-15 12:48 ` [PATCH v6 7/9] ext4: " Jeff Layton
` (3 subsequent siblings)
9 siblings, 1 reply; 32+ messages in thread
From: Jeff Layton @ 2024-07-15 12:48 UTC (permalink / raw)
To: Alexander Viro, Christian Brauner, Jan Kara, Steven Rostedt,
Masami Hiramatsu, Mathieu Desnoyers, Chandan Babu R,
Darrick J. Wong, Theodore Ts'o, Andreas Dilger, Chris Mason,
Josef Bacik, David Sterba, Hugh Dickins, Andrew Morton,
Jonathan Corbet
Cc: Dave Chinner, Andi Kleen, Christoph Hellwig, Uros Bizjak,
Kent Overstreet, Arnd Bergmann, Randy Dunlap, linux-fsdevel,
linux-kernel, linux-trace-kernel, linux-xfs, linux-ext4,
linux-btrfs, linux-mm, linux-nfs, linux-doc, Jeff Layton
Enable multigrain timestamps, which should ensure that there is an
apparent change to the timestamp whenever it has been written after
being actively observed via getattr.
Also, anytime the mtime changes, the ctime must also change, and those
are now the only two options for xfs_trans_ichgtime. Have that function
unconditionally bump the ctime, and ASSERT that XFS_ICHGTIME_CHG is
always set.
Finally, stop setting STATX_CHANGE_COOKIE in getattr, since the ctime
should give us better semantics now.
Reviewed-by: Josef Bacik <josef@toxicpanda.com>
Signed-off-by: Jeff Layton <jlayton@kernel.org>
---
fs/xfs/libxfs/xfs_trans_inode.c | 6 +++---
fs/xfs/xfs_iops.c | 10 +++-------
fs/xfs/xfs_super.c | 2 +-
3 files changed, 7 insertions(+), 11 deletions(-)
diff --git a/fs/xfs/libxfs/xfs_trans_inode.c b/fs/xfs/libxfs/xfs_trans_inode.c
index 69fc5b981352..1f3639bbf5f0 100644
--- a/fs/xfs/libxfs/xfs_trans_inode.c
+++ b/fs/xfs/libxfs/xfs_trans_inode.c
@@ -62,12 +62,12 @@ xfs_trans_ichgtime(
ASSERT(tp);
xfs_assert_ilocked(ip, XFS_ILOCK_EXCL);
- tv = current_time(inode);
+ /* If the mtime changes, then ctime must also change */
+ ASSERT(flags & XFS_ICHGTIME_CHG);
+ tv = inode_set_ctime_current(inode);
if (flags & XFS_ICHGTIME_MOD)
inode_set_mtime_to_ts(inode, tv);
- if (flags & XFS_ICHGTIME_CHG)
- inode_set_ctime_to_ts(inode, tv);
if (flags & XFS_ICHGTIME_CREATE)
ip->i_crtime = tv;
}
diff --git a/fs/xfs/xfs_iops.c b/fs/xfs/xfs_iops.c
index a00dcbc77e12..d25872f818fa 100644
--- a/fs/xfs/xfs_iops.c
+++ b/fs/xfs/xfs_iops.c
@@ -592,8 +592,9 @@ xfs_vn_getattr(
stat->gid = vfsgid_into_kgid(vfsgid);
stat->ino = ip->i_ino;
stat->atime = inode_get_atime(inode);
- stat->mtime = inode_get_mtime(inode);
- stat->ctime = inode_get_ctime(inode);
+
+ fill_mg_cmtime(stat, request_mask, inode);
+
stat->blocks = XFS_FSB_TO_BB(mp, ip->i_nblocks + ip->i_delayed_blks);
if (xfs_has_v3inodes(mp)) {
@@ -603,11 +604,6 @@ xfs_vn_getattr(
}
}
- if ((request_mask & STATX_CHANGE_COOKIE) && IS_I_VERSION(inode)) {
- stat->change_cookie = inode_query_iversion(inode);
- stat->result_mask |= STATX_CHANGE_COOKIE;
- }
-
/*
* Note: If you add another clause to set an attribute flag, please
* update attributes_mask below.
diff --git a/fs/xfs/xfs_super.c b/fs/xfs/xfs_super.c
index 27e9f749c4c7..210481b03fdb 100644
--- a/fs/xfs/xfs_super.c
+++ b/fs/xfs/xfs_super.c
@@ -2052,7 +2052,7 @@ static struct file_system_type xfs_fs_type = {
.init_fs_context = xfs_init_fs_context,
.parameters = xfs_fs_parameters,
.kill_sb = xfs_kill_sb,
- .fs_flags = FS_REQUIRES_DEV | FS_ALLOW_IDMAP,
+ .fs_flags = FS_REQUIRES_DEV | FS_ALLOW_IDMAP | FS_MGTIME,
};
MODULE_ALIAS_FS("xfs");
--
2.45.2
^ permalink raw reply related [flat|nested] 32+ messages in thread
* [PATCH v6 7/9] ext4: switch to multigrain timestamps
2024-07-15 12:48 [PATCH v6 0/9] fs: multigrain timestamp redux Jeff Layton
` (5 preceding siblings ...)
2024-07-15 12:48 ` [PATCH v6 6/9] xfs: switch to " Jeff Layton
@ 2024-07-15 12:48 ` Jeff Layton
2024-07-17 11:32 ` Jan Kara
2024-07-15 12:48 ` [PATCH v6 8/9] btrfs: convert " Jeff Layton
` (2 subsequent siblings)
9 siblings, 1 reply; 32+ messages in thread
From: Jeff Layton @ 2024-07-15 12:48 UTC (permalink / raw)
To: Alexander Viro, Christian Brauner, Jan Kara, Steven Rostedt,
Masami Hiramatsu, Mathieu Desnoyers, Chandan Babu R,
Darrick J. Wong, Theodore Ts'o, Andreas Dilger, Chris Mason,
Josef Bacik, David Sterba, Hugh Dickins, Andrew Morton,
Jonathan Corbet
Cc: Dave Chinner, Andi Kleen, Christoph Hellwig, Uros Bizjak,
Kent Overstreet, Arnd Bergmann, Randy Dunlap, linux-fsdevel,
linux-kernel, linux-trace-kernel, linux-xfs, linux-ext4,
linux-btrfs, linux-mm, linux-nfs, linux-doc, Jeff Layton
Enable multigrain timestamps, which should ensure that there is an
apparent change to the timestamp whenever it has been written after
being actively observed via getattr.
For ext4, we only need to enable the FS_MGTIME flag.
Reviewed-by: Josef Bacik <josef@toxicpanda.com>
Signed-off-by: Jeff Layton <jlayton@kernel.org>
---
fs/ext4/super.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/fs/ext4/super.c b/fs/ext4/super.c
index eb899628e121..95d4d7c0957a 100644
--- a/fs/ext4/super.c
+++ b/fs/ext4/super.c
@@ -7294,7 +7294,7 @@ static struct file_system_type ext4_fs_type = {
.init_fs_context = ext4_init_fs_context,
.parameters = ext4_param_specs,
.kill_sb = ext4_kill_sb,
- .fs_flags = FS_REQUIRES_DEV | FS_ALLOW_IDMAP,
+ .fs_flags = FS_REQUIRES_DEV | FS_ALLOW_IDMAP | FS_MGTIME,
};
MODULE_ALIAS_FS("ext4");
--
2.45.2
^ permalink raw reply related [flat|nested] 32+ messages in thread
* [PATCH v6 8/9] btrfs: convert to multigrain timestamps
2024-07-15 12:48 [PATCH v6 0/9] fs: multigrain timestamp redux Jeff Layton
` (6 preceding siblings ...)
2024-07-15 12:48 ` [PATCH v6 7/9] ext4: " Jeff Layton
@ 2024-07-15 12:48 ` Jeff Layton
2024-07-15 12:49 ` [PATCH v6 9/9] tmpfs: add support for " Jeff Layton
2024-07-16 7:37 ` [PATCH v6 0/9] fs: multigrain timestamp redux Christian Brauner
9 siblings, 0 replies; 32+ messages in thread
From: Jeff Layton @ 2024-07-15 12:48 UTC (permalink / raw)
To: Alexander Viro, Christian Brauner, Jan Kara, Steven Rostedt,
Masami Hiramatsu, Mathieu Desnoyers, Chandan Babu R,
Darrick J. Wong, Theodore Ts'o, Andreas Dilger, Chris Mason,
Josef Bacik, David Sterba, Hugh Dickins, Andrew Morton,
Jonathan Corbet
Cc: Dave Chinner, Andi Kleen, Christoph Hellwig, Uros Bizjak,
Kent Overstreet, Arnd Bergmann, Randy Dunlap, linux-fsdevel,
linux-kernel, linux-trace-kernel, linux-xfs, linux-ext4,
linux-btrfs, linux-mm, linux-nfs, linux-doc, Jeff Layton
Enable multigrain timestamps, which should ensure that there is an
apparent change to the timestamp whenever it has been written after
being actively observed via getattr.
Beyond enabling the FS_MGTIME flag, this patch eliminates
update_time_for_write, which goes to great pains to avoid in-memory
stores. Just have it overwrite the timestamps unconditionally.
Note that this also drops the IS_I_VERSION check and unconditionally
bumps the change attribute, since SB_I_VERSION is always set on btrfs.
Reviewed-by: Josef Bacik <josef@toxicpanda.com>
Signed-off-by: Jeff Layton <jlayton@kernel.org>
---
fs/btrfs/file.c | 25 ++++---------------------
fs/btrfs/super.c | 3 ++-
2 files changed, 6 insertions(+), 22 deletions(-)
diff --git a/fs/btrfs/file.c b/fs/btrfs/file.c
index d90138683a0a..409628c0c3cc 100644
--- a/fs/btrfs/file.c
+++ b/fs/btrfs/file.c
@@ -1120,26 +1120,6 @@ void btrfs_check_nocow_unlock(struct btrfs_inode *inode)
btrfs_drew_write_unlock(&inode->root->snapshot_lock);
}
-static void update_time_for_write(struct inode *inode)
-{
- struct timespec64 now, ts;
-
- if (IS_NOCMTIME(inode))
- return;
-
- now = current_time(inode);
- ts = inode_get_mtime(inode);
- if (!timespec64_equal(&ts, &now))
- inode_set_mtime_to_ts(inode, now);
-
- ts = inode_get_ctime(inode);
- if (!timespec64_equal(&ts, &now))
- inode_set_ctime_to_ts(inode, now);
-
- if (IS_I_VERSION(inode))
- inode_inc_iversion(inode);
-}
-
static int btrfs_write_check(struct kiocb *iocb, struct iov_iter *from,
size_t count)
{
@@ -1171,7 +1151,10 @@ static int btrfs_write_check(struct kiocb *iocb, struct iov_iter *from,
* need to start yet another transaction to update the inode as we will
* update the inode when we finish writing whatever data we write.
*/
- update_time_for_write(inode);
+ if (!IS_NOCMTIME(inode)) {
+ inode_set_mtime_to_ts(inode, inode_set_ctime_current(inode));
+ inode_inc_iversion(inode);
+ }
start_pos = round_down(pos, fs_info->sectorsize);
oldsize = i_size_read(inode);
diff --git a/fs/btrfs/super.c b/fs/btrfs/super.c
index f05cce7c8b8d..1cd50293b98d 100644
--- a/fs/btrfs/super.c
+++ b/fs/btrfs/super.c
@@ -2173,7 +2173,8 @@ static struct file_system_type btrfs_fs_type = {
.init_fs_context = btrfs_init_fs_context,
.parameters = btrfs_fs_parameters,
.kill_sb = btrfs_kill_super,
- .fs_flags = FS_REQUIRES_DEV | FS_BINARY_MOUNTDATA | FS_ALLOW_IDMAP,
+ .fs_flags = FS_REQUIRES_DEV | FS_BINARY_MOUNTDATA |
+ FS_ALLOW_IDMAP | FS_MGTIME,
};
MODULE_ALIAS_FS("btrfs");
--
2.45.2
^ permalink raw reply related [flat|nested] 32+ messages in thread
* [PATCH v6 9/9] tmpfs: add support for multigrain timestamps
2024-07-15 12:48 [PATCH v6 0/9] fs: multigrain timestamp redux Jeff Layton
` (7 preceding siblings ...)
2024-07-15 12:48 ` [PATCH v6 8/9] btrfs: convert " Jeff Layton
@ 2024-07-15 12:49 ` Jeff Layton
2024-07-17 11:34 ` Jan Kara
2024-07-16 7:37 ` [PATCH v6 0/9] fs: multigrain timestamp redux Christian Brauner
9 siblings, 1 reply; 32+ messages in thread
From: Jeff Layton @ 2024-07-15 12:49 UTC (permalink / raw)
To: Alexander Viro, Christian Brauner, Jan Kara, Steven Rostedt,
Masami Hiramatsu, Mathieu Desnoyers, Chandan Babu R,
Darrick J. Wong, Theodore Ts'o, Andreas Dilger, Chris Mason,
Josef Bacik, David Sterba, Hugh Dickins, Andrew Morton,
Jonathan Corbet
Cc: Dave Chinner, Andi Kleen, Christoph Hellwig, Uros Bizjak,
Kent Overstreet, Arnd Bergmann, Randy Dunlap, linux-fsdevel,
linux-kernel, linux-trace-kernel, linux-xfs, linux-ext4,
linux-btrfs, linux-mm, linux-nfs, linux-doc, Jeff Layton
Enable multigrain timestamps, which should ensure that there is an
apparent change to the timestamp whenever it has been written after
being actively observed via getattr.
tmpfs only requires the FS_MGTIME flag.
Reviewed-by: Josef Bacik <josef@toxicpanda.com>
Signed-off-by: Jeff Layton <jlayton@kernel.org>
---
mm/shmem.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/mm/shmem.c b/mm/shmem.c
index 7f2b609945a5..75a9a73a769f 100644
--- a/mm/shmem.c
+++ b/mm/shmem.c
@@ -4660,7 +4660,7 @@ static struct file_system_type shmem_fs_type = {
.parameters = shmem_fs_parameters,
#endif
.kill_sb = kill_litter_super,
- .fs_flags = FS_USERNS_MOUNT | FS_ALLOW_IDMAP,
+ .fs_flags = FS_USERNS_MOUNT | FS_ALLOW_IDMAP | FS_MGTIME,
};
void __init shmem_init(void)
--
2.45.2
^ permalink raw reply related [flat|nested] 32+ messages in thread
* Re: [PATCH v6 2/9] fs: tracepoints around multigrain timestamp events
2024-07-15 12:48 ` [PATCH v6 2/9] fs: tracepoints around multigrain timestamp events Jeff Layton
@ 2024-07-15 18:29 ` Darrick J. Wong
2024-07-17 10:41 ` Jan Kara
1 sibling, 0 replies; 32+ messages in thread
From: Darrick J. Wong @ 2024-07-15 18:29 UTC (permalink / raw)
To: Jeff Layton
Cc: Alexander Viro, Christian Brauner, Jan Kara, Steven Rostedt,
Masami Hiramatsu, Mathieu Desnoyers, Chandan Babu R,
Theodore Ts'o, Andreas Dilger, Chris Mason, Josef Bacik,
David Sterba, Hugh Dickins, Andrew Morton, Jonathan Corbet,
Dave Chinner, Andi Kleen, Christoph Hellwig, Uros Bizjak,
Kent Overstreet, Arnd Bergmann, Randy Dunlap, linux-fsdevel,
linux-kernel, linux-trace-kernel, linux-xfs, linux-ext4,
linux-btrfs, linux-mm, linux-nfs, linux-doc
On Mon, Jul 15, 2024 at 08:48:53AM -0400, Jeff Layton wrote:
> Add some tracepoints around various multigrain timestamp events.
>
> Reviewed-by: Josef Bacik <josef@toxicpanda.com>
> Signed-off-by: Jeff Layton <jlayton@kernel.org>
Woot!
Reviewed-by: Darrick J. Wong <djwong@kernel.org>
--D
> ---
> fs/inode.c | 9 ++-
> fs/stat.c | 3 +
> include/trace/events/timestamp.h | 124 +++++++++++++++++++++++++++++++++++++++
> 3 files changed, 135 insertions(+), 1 deletion(-)
>
> diff --git a/fs/inode.c b/fs/inode.c
> index 417acbeabef3..869994285e87 100644
> --- a/fs/inode.c
> +++ b/fs/inode.c
> @@ -22,6 +22,9 @@
> #include <linux/iversion.h>
> #include <linux/rw_hint.h>
> #include <trace/events/writeback.h>
> +#define CREATE_TRACE_POINTS
> +#include <trace/events/timestamp.h>
> +
> #include "internal.h"
>
> /*
> @@ -2569,6 +2572,7 @@ EXPORT_SYMBOL(inode_nohighmem);
>
> struct timespec64 inode_set_ctime_to_ts(struct inode *inode, struct timespec64 ts)
> {
> + trace_inode_set_ctime_to_ts(inode, &ts);
> set_normalized_timespec64(&ts, ts.tv_sec, ts.tv_nsec);
> inode->i_ctime_sec = ts.tv_sec;
> inode->i_ctime_nsec = ts.tv_nsec;
> @@ -2668,13 +2672,16 @@ struct timespec64 inode_set_ctime_current(struct inode *inode)
> cur = cns;
>
> /* No need to cmpxchg if it's exactly the same */
> - if (cns == now_ts.tv_nsec && inode->i_ctime_sec == now_ts.tv_sec)
> + if (cns == now_ts.tv_nsec && inode->i_ctime_sec == now_ts.tv_sec) {
> + trace_ctime_xchg_skip(inode, &now_ts);
> goto out;
> + }
> retry:
> /* Try to swap the nsec value into place. */
> if (try_cmpxchg(&inode->i_ctime_nsec, &cur, now_ts.tv_nsec)) {
> /* If swap occurred, then we're (mostly) done */
> inode->i_ctime_sec = now_ts.tv_sec;
> + trace_ctime_ns_xchg(inode, cns, now_ts.tv_nsec, cur);
> } else {
> /*
> * Was the change due to someone marking the old ctime QUERIED?
> diff --git a/fs/stat.c b/fs/stat.c
> index df7fdd3afed9..552dfd67688b 100644
> --- a/fs/stat.c
> +++ b/fs/stat.c
> @@ -23,6 +23,8 @@
> #include <linux/uaccess.h>
> #include <asm/unistd.h>
>
> +#include <trace/events/timestamp.h>
> +
> #include "internal.h"
> #include "mount.h"
>
> @@ -49,6 +51,7 @@ void fill_mg_cmtime(struct kstat *stat, u32 request_mask, struct inode *inode)
> stat->mtime = inode_get_mtime(inode);
> stat->ctime.tv_sec = inode->i_ctime_sec;
> stat->ctime.tv_nsec = ((u32)atomic_fetch_or(I_CTIME_QUERIED, pcn)) & ~I_CTIME_QUERIED;
> + trace_fill_mg_cmtime(inode, &stat->ctime, &stat->mtime);
> }
> EXPORT_SYMBOL(fill_mg_cmtime);
>
> diff --git a/include/trace/events/timestamp.h b/include/trace/events/timestamp.h
> new file mode 100644
> index 000000000000..c9e5ec930054
> --- /dev/null
> +++ b/include/trace/events/timestamp.h
> @@ -0,0 +1,124 @@
> +/* SPDX-License-Identifier: GPL-2.0 */
> +#undef TRACE_SYSTEM
> +#define TRACE_SYSTEM timestamp
> +
> +#if !defined(_TRACE_TIMESTAMP_H) || defined(TRACE_HEADER_MULTI_READ)
> +#define _TRACE_TIMESTAMP_H
> +
> +#include <linux/tracepoint.h>
> +#include <linux/fs.h>
> +
> +#define CTIME_QUERIED_FLAGS \
> + { I_CTIME_QUERIED, "Q" }
> +
> +DECLARE_EVENT_CLASS(ctime,
> + TP_PROTO(struct inode *inode,
> + struct timespec64 *ctime),
> +
> + TP_ARGS(inode, ctime),
> +
> + TP_STRUCT__entry(
> + __field(dev_t, dev)
> + __field(ino_t, ino)
> + __field(time64_t, ctime_s)
> + __field(u32, ctime_ns)
> + __field(u32, gen)
> + ),
> +
> + TP_fast_assign(
> + __entry->dev = inode->i_sb->s_dev;
> + __entry->ino = inode->i_ino;
> + __entry->gen = inode->i_generation;
> + __entry->ctime_s = ctime->tv_sec;
> + __entry->ctime_ns = ctime->tv_nsec;
> + ),
> +
> + TP_printk("ino=%d:%d:%ld:%u ctime=%lld.%u",
> + MAJOR(__entry->dev), MINOR(__entry->dev), __entry->ino, __entry->gen,
> + __entry->ctime_s, __entry->ctime_ns
> + )
> +);
> +
> +DEFINE_EVENT(ctime, inode_set_ctime_to_ts,
> + TP_PROTO(struct inode *inode,
> + struct timespec64 *ctime),
> + TP_ARGS(inode, ctime));
> +
> +DEFINE_EVENT(ctime, ctime_xchg_skip,
> + TP_PROTO(struct inode *inode,
> + struct timespec64 *ctime),
> + TP_ARGS(inode, ctime));
> +
> +TRACE_EVENT(ctime_ns_xchg,
> + TP_PROTO(struct inode *inode,
> + u32 old,
> + u32 new,
> + u32 cur),
> +
> + TP_ARGS(inode, old, new, cur),
> +
> + TP_STRUCT__entry(
> + __field(dev_t, dev)
> + __field(ino_t, ino)
> + __field(u32, gen)
> + __field(u32, old)
> + __field(u32, new)
> + __field(u32, cur)
> + ),
> +
> + TP_fast_assign(
> + __entry->dev = inode->i_sb->s_dev;
> + __entry->ino = inode->i_ino;
> + __entry->gen = inode->i_generation;
> + __entry->old = old;
> + __entry->new = new;
> + __entry->cur = cur;
> + ),
> +
> + TP_printk("ino=%d:%d:%ld:%u old=%u:%s new=%u cur=%u:%s",
> + MAJOR(__entry->dev), MINOR(__entry->dev), __entry->ino, __entry->gen,
> + __entry->old & ~I_CTIME_QUERIED,
> + __print_flags(__entry->old & I_CTIME_QUERIED, "|", CTIME_QUERIED_FLAGS),
> + __entry->new,
> + __entry->cur & ~I_CTIME_QUERIED,
> + __print_flags(__entry->cur & I_CTIME_QUERIED, "|", CTIME_QUERIED_FLAGS)
> + )
> +);
> +
> +TRACE_EVENT(fill_mg_cmtime,
> + TP_PROTO(struct inode *inode,
> + struct timespec64 *ctime,
> + struct timespec64 *mtime),
> +
> + TP_ARGS(inode, ctime, mtime),
> +
> + TP_STRUCT__entry(
> + __field(dev_t, dev)
> + __field(ino_t, ino)
> + __field(time64_t, ctime_s)
> + __field(time64_t, mtime_s)
> + __field(u32, ctime_ns)
> + __field(u32, mtime_ns)
> + __field(u32, gen)
> + ),
> +
> + TP_fast_assign(
> + __entry->dev = inode->i_sb->s_dev;
> + __entry->ino = inode->i_ino;
> + __entry->gen = inode->i_generation;
> + __entry->ctime_s = ctime->tv_sec;
> + __entry->mtime_s = mtime->tv_sec;
> + __entry->ctime_ns = ctime->tv_nsec;
> + __entry->mtime_ns = mtime->tv_nsec;
> + ),
> +
> + TP_printk("ino=%d:%d:%ld:%u ctime=%lld.%u mtime=%lld.%u",
> + MAJOR(__entry->dev), MINOR(__entry->dev), __entry->ino, __entry->gen,
> + __entry->ctime_s, __entry->ctime_ns,
> + __entry->mtime_s, __entry->mtime_ns
> + )
> +);
> +#endif /* _TRACE_TIMESTAMP_H */
> +
> +/* This part must be outside protection */
> +#include <trace/define_trace.h>
>
> --
> 2.45.2
>
>
^ permalink raw reply [flat|nested] 32+ messages in thread
* Re: [PATCH v6 3/9] fs: add percpu counters for significant multigrain timestamp events
2024-07-15 12:48 ` [PATCH v6 3/9] fs: add percpu counters for significant " Jeff Layton
@ 2024-07-15 18:32 ` Darrick J. Wong
2024-07-15 19:53 ` Jeff Layton
0 siblings, 1 reply; 32+ messages in thread
From: Darrick J. Wong @ 2024-07-15 18:32 UTC (permalink / raw)
To: Jeff Layton
Cc: Alexander Viro, Christian Brauner, Jan Kara, Steven Rostedt,
Masami Hiramatsu, Mathieu Desnoyers, Chandan Babu R,
Theodore Ts'o, Andreas Dilger, Chris Mason, Josef Bacik,
David Sterba, Hugh Dickins, Andrew Morton, Jonathan Corbet,
Dave Chinner, Andi Kleen, Christoph Hellwig, Uros Bizjak,
Kent Overstreet, Arnd Bergmann, Randy Dunlap, linux-fsdevel,
linux-kernel, linux-trace-kernel, linux-xfs, linux-ext4,
linux-btrfs, linux-mm, linux-nfs, linux-doc
On Mon, Jul 15, 2024 at 08:48:54AM -0400, Jeff Layton wrote:
> Four percpu counters for counting various stats around mgtimes, and a
> new debugfs file for displaying them:
>
> - number of attempted ctime updates
> - number of successful i_ctime_nsec swaps
> - number of fine-grained timestamp fetches
> - number of floor value swaps
>
> Reviewed-by: Josef Bacik <josef@toxicpanda.com>
> Signed-off-by: Jeff Layton <jlayton@kernel.org>
> ---
> fs/inode.c | 70 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
> 1 file changed, 69 insertions(+), 1 deletion(-)
>
> diff --git a/fs/inode.c b/fs/inode.c
> index 869994285e87..fff844345c35 100644
> --- a/fs/inode.c
> +++ b/fs/inode.c
> @@ -21,6 +21,8 @@
> #include <linux/list_lru.h>
> #include <linux/iversion.h>
> #include <linux/rw_hint.h>
> +#include <linux/seq_file.h>
> +#include <linux/debugfs.h>
> #include <trace/events/writeback.h>
> #define CREATE_TRACE_POINTS
> #include <trace/events/timestamp.h>
> @@ -80,6 +82,10 @@ EXPORT_SYMBOL(empty_aops);
>
> static DEFINE_PER_CPU(unsigned long, nr_inodes);
> static DEFINE_PER_CPU(unsigned long, nr_unused);
> +static DEFINE_PER_CPU(unsigned long, mg_ctime_updates);
> +static DEFINE_PER_CPU(unsigned long, mg_fine_stamps);
> +static DEFINE_PER_CPU(unsigned long, mg_floor_swaps);
> +static DEFINE_PER_CPU(unsigned long, mg_ctime_swaps);
Should this all get switched off if CONFIG_DEBUG_FS=n?
--D
>
> static struct kmem_cache *inode_cachep __ro_after_init;
>
> @@ -101,6 +107,42 @@ static inline long get_nr_inodes_unused(void)
> return sum < 0 ? 0 : sum;
> }
>
> +static long get_mg_ctime_updates(void)
> +{
> + int i;
> + long sum = 0;
> + for_each_possible_cpu(i)
> + sum += per_cpu(mg_ctime_updates, i);
> + return sum < 0 ? 0 : sum;
> +}
> +
> +static long get_mg_fine_stamps(void)
> +{
> + int i;
> + long sum = 0;
> + for_each_possible_cpu(i)
> + sum += per_cpu(mg_fine_stamps, i);
> + return sum < 0 ? 0 : sum;
> +}
> +
> +static long get_mg_floor_swaps(void)
> +{
> + int i;
> + long sum = 0;
> + for_each_possible_cpu(i)
> + sum += per_cpu(mg_floor_swaps, i);
> + return sum < 0 ? 0 : sum;
> +}
> +
> +static long get_mg_ctime_swaps(void)
> +{
> + int i;
> + long sum = 0;
> + for_each_possible_cpu(i)
> + sum += per_cpu(mg_ctime_swaps, i);
> + return sum < 0 ? 0 : sum;
> +}
> +
> long get_nr_dirty_inodes(void)
> {
> /* not actually dirty inodes, but a wild approximation */
> @@ -2655,6 +2697,7 @@ struct timespec64 inode_set_ctime_current(struct inode *inode)
>
> /* Get a fine-grained time */
> fine = ktime_get();
> + this_cpu_inc(mg_fine_stamps);
>
> /*
> * If the cmpxchg works, we take the new floor value. If
> @@ -2663,11 +2706,14 @@ struct timespec64 inode_set_ctime_current(struct inode *inode)
> * as good, so keep it.
> */
> old = floor;
> - if (!atomic64_try_cmpxchg(&ctime_floor, &old, fine))
> + if (atomic64_try_cmpxchg(&ctime_floor, &old, fine))
> + this_cpu_inc(mg_floor_swaps);
> + else
> fine = old;
> now = ktime_mono_to_real(fine);
> }
> }
> + this_cpu_inc(mg_ctime_updates);
> now_ts = timestamp_truncate(ktime_to_timespec64(now), inode);
> cur = cns;
>
> @@ -2682,6 +2728,7 @@ struct timespec64 inode_set_ctime_current(struct inode *inode)
> /* If swap occurred, then we're (mostly) done */
> inode->i_ctime_sec = now_ts.tv_sec;
> trace_ctime_ns_xchg(inode, cns, now_ts.tv_nsec, cur);
> + this_cpu_inc(mg_ctime_swaps);
> } else {
> /*
> * Was the change due to someone marking the old ctime QUERIED?
> @@ -2751,3 +2798,24 @@ umode_t mode_strip_sgid(struct mnt_idmap *idmap,
> return mode & ~S_ISGID;
> }
> EXPORT_SYMBOL(mode_strip_sgid);
> +
> +static int mgts_show(struct seq_file *s, void *p)
> +{
> + long ctime_updates = get_mg_ctime_updates();
> + long ctime_swaps = get_mg_ctime_swaps();
> + long fine_stamps = get_mg_fine_stamps();
> + long floor_swaps = get_mg_floor_swaps();
> +
> + seq_printf(s, "%lu %lu %lu %lu\n",
> + ctime_updates, ctime_swaps, fine_stamps, floor_swaps);
> + return 0;
> +}
> +
> +DEFINE_SHOW_ATTRIBUTE(mgts);
> +
> +static int __init mg_debugfs_init(void)
> +{
> + debugfs_create_file("multigrain_timestamps", S_IFREG | S_IRUGO, NULL, NULL, &mgts_fops);
> + return 0;
> +}
> +late_initcall(mg_debugfs_init);
>
> --
> 2.45.2
>
>
^ permalink raw reply [flat|nested] 32+ messages in thread
* Re: [PATCH v6 5/9] Documentation: add a new file documenting multigrain timestamps
2024-07-15 12:48 ` [PATCH v6 5/9] Documentation: add a new file documenting multigrain timestamps Jeff Layton
@ 2024-07-15 18:45 ` Darrick J. Wong
2024-07-17 6:00 ` Randy Dunlap
2024-07-17 11:31 ` Jan Kara
2 siblings, 0 replies; 32+ messages in thread
From: Darrick J. Wong @ 2024-07-15 18:45 UTC (permalink / raw)
To: Jeff Layton
Cc: Alexander Viro, Christian Brauner, Jan Kara, Steven Rostedt,
Masami Hiramatsu, Mathieu Desnoyers, Chandan Babu R,
Theodore Ts'o, Andreas Dilger, Chris Mason, Josef Bacik,
David Sterba, Hugh Dickins, Andrew Morton, Jonathan Corbet,
Dave Chinner, Andi Kleen, Christoph Hellwig, Uros Bizjak,
Kent Overstreet, Arnd Bergmann, Randy Dunlap, linux-fsdevel,
linux-kernel, linux-trace-kernel, linux-xfs, linux-ext4,
linux-btrfs, linux-mm, linux-nfs, linux-doc
On Mon, Jul 15, 2024 at 08:48:56AM -0400, Jeff Layton wrote:
> Add a high-level document that describes how multigrain timestamps work,
> rationale for them, and some info about implementation and tradeoffs.
>
> Reviewed-by: Josef Bacik <josef@toxicpanda.com>
> Signed-off-by: Jeff Layton <jlayton@kernel.org>
Seems fine to me,
Reviewed-by: Darrick J. Wong <djwong@kernel.org>
--D
> ---
> Documentation/filesystems/multigrain-ts.rst | 120 ++++++++++++++++++++++++++++
> 1 file changed, 120 insertions(+)
>
> diff --git a/Documentation/filesystems/multigrain-ts.rst b/Documentation/filesystems/multigrain-ts.rst
> new file mode 100644
> index 000000000000..5cefc204ecec
> --- /dev/null
> +++ b/Documentation/filesystems/multigrain-ts.rst
> @@ -0,0 +1,120 @@
> +.. SPDX-License-Identifier: GPL-2.0
> +
> +=====================
> +Multigrain Timestamps
> +=====================
> +
> +Introduction
> +============
> +Historically, the kernel has always used coarse time values to stamp
> +inodes. This value is updated on every jiffy, so any change that happens
> +within that jiffy will end up with the same timestamp.
> +
> +When the kernel goes to stamp an inode (due to a read or write), it first gets
> +the current time and then compares it to the existing timestamp(s) to see
> +whether anything will change. If nothing changed, then it can avoid updating
> +the inode's metadata.
> +
> +Coarse timestamps are therefore good from a performance standpoint, since they
> +reduce the need for metadata updates, but bad from the standpoint of
> +determining whether anything has changed, since a lot of things can happen in a
> +jiffy.
> +
> +They are particularly troublesome with NFSv3, where unchanging timestamps can
> +make it difficult to tell whether to invalidate caches. NFSv4 provides a
> +dedicated change attribute that should always show a visible change, but not
> +all filesystems implement this properly, causing the NFS server to substitute
> +the ctime in many cases.
> +
> +Multigrain timestamps aim to remedy this by selectively using fine-grained
> +timestamps when a file has had its timestamps queried recently, and the current
> +coarse-grained time does not cause a change.
> +
> +Inode Timestamps
> +================
> +There are currently 3 timestamps in the inode that are updated to the current
> +wallclock time on different activity:
> +
> +ctime:
> + The inode change time. This is stamped with the current time whenever
> + the inode's metadata is changed. Note that this value is not settable
> + from userland.
> +
> +mtime:
> + The inode modification time. This is stamped with the current time
> + any time a file's contents change.
> +
> +atime:
> + The inode access time. This is stamped whenever an inode's contents are
> + read. Widely considered to be a terrible mistake. Usually avoided with
> + options like noatime or relatime.
> +
> +Updating the mtime always implies a change to the ctime, but updating the
> +atime due to a read request does not.
> +
> +Multigrain timestamps are only tracked for the ctime and the mtime. atimes are
> +not affected and always use the coarse-grained value (subject to the floor).
> +
> +Inode Timestamp Ordering
> +========================
> +
> +In addition to just providing info about changes to individual files, file
> +timestamps also serve an important purpose in applications like "make". These
> +programs measure timestamps in order to determine whether source files might be
> +newer than cached objects.
> +
> +Userland applications like make can only determine ordering based on
> +operational boundaries. For a syscall those are the syscall entry and exit
> +points. For io_uring or nfsd operations, that's the request submission and
> +response. In the case of concurrent operations, userland can make no
> +determination about the order in which things will occur.
> +
> +For instance, if a single thread modifies one file, and then another file in
> +sequence, the second file must show an equal or later mtime than the first. The
> +same is true if two threads are issuing similar operations that do not overlap
> +in time.
> +
> +If however, two threads have racing syscalls that overlap in time, then there
> +is no such guarantee, and the second file may appear to have been modified
> +before, after or at the same time as the first, regardless of which one was
> +submitted first.
> +
> +Multigrain Timestamps
> +=====================
> +Multigrain timestamps are aimed at ensuring that changes to a single file are
> +always recognizable, without violating the ordering guarantees when multiple
> +different files are modified. This affects the mtime and the ctime, but the
> +atime will always use coarse-grained timestamps.
> +
> +It uses an unused bit in the i_ctime_nsec field to indicate whether the mtime
> +or ctime has been queried. If either or both have, then the kernel takes
> +special care to ensure the next timestamp update will display a visible change.
> +This ensures tight cache coherency for use-cases like NFS, without sacrificing
> +the benefits of reduced metadata updates when files aren't being watched.
> +
> +The Ctime Floor Value
> +=====================
> +It's not sufficient to simply use fine or coarse-grained timestamps based on
> +whether the mtime or ctime has been queried. A file could get a fine grained
> +timestamp, and then a second file modified later could get a coarse-grained one
> +that appears earlier than the first, which would break the kernel's timestamp
> +ordering guarantees.
> +
> +To mitigate this problem, we maintain a global floor value that ensures that
> +this can't happen. The two files in the above example may appear to have been
> +modified at the same time in such a case, but they will never show the reverse
> +order. To avoid problems with realtime clock jumps, the floor is managed as a
> +monotonic ktime_t, and the values are converted to realtime clock values as
> +needed.
> +
> +Implementation Notes
> +====================
> +Multigrain timestamps are intended for use by local filesystems that get
> +ctime values from the local clock. This is in contrast to network filesystems
> +and the like that just mirror timestamp values from a server.
> +
> +For most filesystems, it's sufficient to just set the FS_MGTIME flag in the
> +fstype->fs_flags in order to opt-in, providing the ctime is only ever set via
> +inode_set_ctime_current(). If the filesystem has a ->getattr routine that
> +doesn't call generic_fillattr, then you should have it call fill_mg_cmtime to
> +fill those values.
>
> --
> 2.45.2
>
>
^ permalink raw reply [flat|nested] 32+ messages in thread
* Re: [PATCH v6 6/9] xfs: switch to multigrain timestamps
2024-07-15 12:48 ` [PATCH v6 6/9] xfs: switch to " Jeff Layton
@ 2024-07-15 18:47 ` Darrick J. Wong
0 siblings, 0 replies; 32+ messages in thread
From: Darrick J. Wong @ 2024-07-15 18:47 UTC (permalink / raw)
To: Jeff Layton
Cc: Alexander Viro, Christian Brauner, Jan Kara, Steven Rostedt,
Masami Hiramatsu, Mathieu Desnoyers, Chandan Babu R,
Theodore Ts'o, Andreas Dilger, Chris Mason, Josef Bacik,
David Sterba, Hugh Dickins, Andrew Morton, Jonathan Corbet,
Dave Chinner, Andi Kleen, Christoph Hellwig, Uros Bizjak,
Kent Overstreet, Arnd Bergmann, Randy Dunlap, linux-fsdevel,
linux-kernel, linux-trace-kernel, linux-xfs, linux-ext4,
linux-btrfs, linux-mm, linux-nfs, linux-doc
On Mon, Jul 15, 2024 at 08:48:57AM -0400, Jeff Layton wrote:
> Enable multigrain timestamps, which should ensure that there is an
> apparent change to the timestamp whenever it has been written after
> being actively observed via getattr.
>
> Also, anytime the mtime changes, the ctime must also change, and those
> are now the only two options for xfs_trans_ichgtime. Have that function
> unconditionally bump the ctime, and ASSERT that XFS_ICHGTIME_CHG is
> always set.
>
> Finally, stop setting STATX_CHANGE_COOKIE in getattr, since the ctime
> should give us better semantics now.
>
> Reviewed-by: Josef Bacik <josef@toxicpanda.com>
> Signed-off-by: Jeff Layton <jlayton@kernel.org>
Looks good,
Reviewed-by: Darrick J. Wong <djwong@kernel.org>
--D
> ---
> fs/xfs/libxfs/xfs_trans_inode.c | 6 +++---
> fs/xfs/xfs_iops.c | 10 +++-------
> fs/xfs/xfs_super.c | 2 +-
> 3 files changed, 7 insertions(+), 11 deletions(-)
>
> diff --git a/fs/xfs/libxfs/xfs_trans_inode.c b/fs/xfs/libxfs/xfs_trans_inode.c
> index 69fc5b981352..1f3639bbf5f0 100644
> --- a/fs/xfs/libxfs/xfs_trans_inode.c
> +++ b/fs/xfs/libxfs/xfs_trans_inode.c
> @@ -62,12 +62,12 @@ xfs_trans_ichgtime(
> ASSERT(tp);
> xfs_assert_ilocked(ip, XFS_ILOCK_EXCL);
>
> - tv = current_time(inode);
> + /* If the mtime changes, then ctime must also change */
> + ASSERT(flags & XFS_ICHGTIME_CHG);
>
> + tv = inode_set_ctime_current(inode);
> if (flags & XFS_ICHGTIME_MOD)
> inode_set_mtime_to_ts(inode, tv);
> - if (flags & XFS_ICHGTIME_CHG)
> - inode_set_ctime_to_ts(inode, tv);
> if (flags & XFS_ICHGTIME_CREATE)
> ip->i_crtime = tv;
> }
> diff --git a/fs/xfs/xfs_iops.c b/fs/xfs/xfs_iops.c
> index a00dcbc77e12..d25872f818fa 100644
> --- a/fs/xfs/xfs_iops.c
> +++ b/fs/xfs/xfs_iops.c
> @@ -592,8 +592,9 @@ xfs_vn_getattr(
> stat->gid = vfsgid_into_kgid(vfsgid);
> stat->ino = ip->i_ino;
> stat->atime = inode_get_atime(inode);
> - stat->mtime = inode_get_mtime(inode);
> - stat->ctime = inode_get_ctime(inode);
> +
> + fill_mg_cmtime(stat, request_mask, inode);
> +
> stat->blocks = XFS_FSB_TO_BB(mp, ip->i_nblocks + ip->i_delayed_blks);
>
> if (xfs_has_v3inodes(mp)) {
> @@ -603,11 +604,6 @@ xfs_vn_getattr(
> }
> }
>
> - if ((request_mask & STATX_CHANGE_COOKIE) && IS_I_VERSION(inode)) {
> - stat->change_cookie = inode_query_iversion(inode);
> - stat->result_mask |= STATX_CHANGE_COOKIE;
> - }
> -
> /*
> * Note: If you add another clause to set an attribute flag, please
> * update attributes_mask below.
> diff --git a/fs/xfs/xfs_super.c b/fs/xfs/xfs_super.c
> index 27e9f749c4c7..210481b03fdb 100644
> --- a/fs/xfs/xfs_super.c
> +++ b/fs/xfs/xfs_super.c
> @@ -2052,7 +2052,7 @@ static struct file_system_type xfs_fs_type = {
> .init_fs_context = xfs_init_fs_context,
> .parameters = xfs_fs_parameters,
> .kill_sb = xfs_kill_sb,
> - .fs_flags = FS_REQUIRES_DEV | FS_ALLOW_IDMAP,
> + .fs_flags = FS_REQUIRES_DEV | FS_ALLOW_IDMAP | FS_MGTIME,
> };
> MODULE_ALIAS_FS("xfs");
>
>
> --
> 2.45.2
>
>
^ permalink raw reply [flat|nested] 32+ messages in thread
* Re: [PATCH v6 3/9] fs: add percpu counters for significant multigrain timestamp events
2024-07-15 18:32 ` Darrick J. Wong
@ 2024-07-15 19:53 ` Jeff Layton
2024-07-15 20:03 ` Darrick J. Wong
2024-07-17 10:45 ` Jan Kara
0 siblings, 2 replies; 32+ messages in thread
From: Jeff Layton @ 2024-07-15 19:53 UTC (permalink / raw)
To: Darrick J. Wong
Cc: Alexander Viro, Christian Brauner, Jan Kara, Steven Rostedt,
Masami Hiramatsu, Mathieu Desnoyers, Chandan Babu R,
Theodore Ts'o, Andreas Dilger, Chris Mason, Josef Bacik,
David Sterba, Hugh Dickins, Andrew Morton, Jonathan Corbet,
Dave Chinner, Andi Kleen, Christoph Hellwig, Uros Bizjak,
Kent Overstreet, Arnd Bergmann, Randy Dunlap, linux-fsdevel,
linux-kernel, linux-trace-kernel, linux-xfs, linux-ext4,
linux-btrfs, linux-mm, linux-nfs, linux-doc
On Mon, 2024-07-15 at 11:32 -0700, Darrick J. Wong wrote:
> On Mon, Jul 15, 2024 at 08:48:54AM -0400, Jeff Layton wrote:
> > Four percpu counters for counting various stats around mgtimes, and
> > a
> > new debugfs file for displaying them:
> >
> > - number of attempted ctime updates
> > - number of successful i_ctime_nsec swaps
> > - number of fine-grained timestamp fetches
> > - number of floor value swaps
> >
> > Reviewed-by: Josef Bacik <josef@toxicpanda.com>
> > Signed-off-by: Jeff Layton <jlayton@kernel.org>
> > ---
> > fs/inode.c | 70
> > +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
> > 1 file changed, 69 insertions(+), 1 deletion(-)
> >
> > diff --git a/fs/inode.c b/fs/inode.c
> > index 869994285e87..fff844345c35 100644
> > --- a/fs/inode.c
> > +++ b/fs/inode.c
> > @@ -21,6 +21,8 @@
> > #include <linux/list_lru.h>
> > #include <linux/iversion.h>
> > #include <linux/rw_hint.h>
> > +#include <linux/seq_file.h>
> > +#include <linux/debugfs.h>
> > #include <trace/events/writeback.h>
> > #define CREATE_TRACE_POINTS
> > #include <trace/events/timestamp.h>
> > @@ -80,6 +82,10 @@ EXPORT_SYMBOL(empty_aops);
> >
> > static DEFINE_PER_CPU(unsigned long, nr_inodes);
> > static DEFINE_PER_CPU(unsigned long, nr_unused);
> > +static DEFINE_PER_CPU(unsigned long, mg_ctime_updates);
> > +static DEFINE_PER_CPU(unsigned long, mg_fine_stamps);
> > +static DEFINE_PER_CPU(unsigned long, mg_floor_swaps);
> > +static DEFINE_PER_CPU(unsigned long, mg_ctime_swaps);
>
> Should this all get switched off if CONFIG_DEBUG_FS=n?
>
> --D
>
Sure, why not. That's simple enough to do.
I pushed an updated mgtime branch to my git tree. Here's the updated
patch that's the only difference:
https://git.kernel.org/pub/scm/linux/kernel/git/jlayton/linux.git/commit/?h=mgtime&id=ee7fe6e9c0598754861c8620230f15f3de538ca5
Seems to build OK both with and without CONFIG_DEBUG_FS.
> >
> > static struct kmem_cache *inode_cachep __ro_after_init;
> >
> > @@ -101,6 +107,42 @@ static inline long get_nr_inodes_unused(void)
> > return sum < 0 ? 0 : sum;
> > }
> >
> > +static long get_mg_ctime_updates(void)
> > +{
> > + int i;
> > + long sum = 0;
> > + for_each_possible_cpu(i)
> > + sum += per_cpu(mg_ctime_updates, i);
> > + return sum < 0 ? 0 : sum;
> > +}
> > +
> > +static long get_mg_fine_stamps(void)
> > +{
> > + int i;
> > + long sum = 0;
> > + for_each_possible_cpu(i)
> > + sum += per_cpu(mg_fine_stamps, i);
> > + return sum < 0 ? 0 : sum;
> > +}
> > +
> > +static long get_mg_floor_swaps(void)
> > +{
> > + int i;
> > + long sum = 0;
> > + for_each_possible_cpu(i)
> > + sum += per_cpu(mg_floor_swaps, i);
> > + return sum < 0 ? 0 : sum;
> > +}
> > +
> > +static long get_mg_ctime_swaps(void)
> > +{
> > + int i;
> > + long sum = 0;
> > + for_each_possible_cpu(i)
> > + sum += per_cpu(mg_ctime_swaps, i);
> > + return sum < 0 ? 0 : sum;
> > +}
> > +
> > long get_nr_dirty_inodes(void)
> > {
> > /* not actually dirty inodes, but a wild approximation */
> > @@ -2655,6 +2697,7 @@ struct timespec64
> > inode_set_ctime_current(struct inode *inode)
> >
> > /* Get a fine-grained time */
> > fine = ktime_get();
> > + this_cpu_inc(mg_fine_stamps);
> >
> > /*
> > * If the cmpxchg works, we take the new
> > floor value. If
> > @@ -2663,11 +2706,14 @@ struct timespec64
> > inode_set_ctime_current(struct inode *inode)
> > * as good, so keep it.
> > */
> > old = floor;
> > - if (!atomic64_try_cmpxchg(&ctime_floor,
> > &old, fine))
> > + if (atomic64_try_cmpxchg(&ctime_floor,
> > &old, fine))
> > + this_cpu_inc(mg_floor_swaps);
> > + else
> > fine = old;
> > now = ktime_mono_to_real(fine);
> > }
> > }
> > + this_cpu_inc(mg_ctime_updates);
> > now_ts = timestamp_truncate(ktime_to_timespec64(now),
> > inode);
> > cur = cns;
> >
> > @@ -2682,6 +2728,7 @@ struct timespec64
> > inode_set_ctime_current(struct inode *inode)
> > /* If swap occurred, then we're (mostly) done */
> > inode->i_ctime_sec = now_ts.tv_sec;
> > trace_ctime_ns_xchg(inode, cns, now_ts.tv_nsec,
> > cur);
> > + this_cpu_inc(mg_ctime_swaps);
> > } else {
> > /*
> > * Was the change due to someone marking the old
> > ctime QUERIED?
> > @@ -2751,3 +2798,24 @@ umode_t mode_strip_sgid(struct mnt_idmap
> > *idmap,
> > return mode & ~S_ISGID;
> > }
> > EXPORT_SYMBOL(mode_strip_sgid);
> > +
> > +static int mgts_show(struct seq_file *s, void *p)
> > +{
> > + long ctime_updates = get_mg_ctime_updates();
> > + long ctime_swaps = get_mg_ctime_swaps();
> > + long fine_stamps = get_mg_fine_stamps();
> > + long floor_swaps = get_mg_floor_swaps();
> > +
> > + seq_printf(s, "%lu %lu %lu %lu\n",
> > + ctime_updates, ctime_swaps, fine_stamps,
> > floor_swaps);
> > + return 0;
> > +}
> > +
> > +DEFINE_SHOW_ATTRIBUTE(mgts);
> > +
> > +static int __init mg_debugfs_init(void)
> > +{
> > + debugfs_create_file("multigrain_timestamps", S_IFREG |
> > S_IRUGO, NULL, NULL, &mgts_fops);
> > + return 0;
> > +}
> > +late_initcall(mg_debugfs_init);
> >
> > --
> > 2.45.2
> >
> >
--
Jeff Layton <jlayton@kernel.org>
^ permalink raw reply [flat|nested] 32+ messages in thread
* Re: [PATCH v6 3/9] fs: add percpu counters for significant multigrain timestamp events
2024-07-15 19:53 ` Jeff Layton
@ 2024-07-15 20:03 ` Darrick J. Wong
2024-07-17 10:45 ` Jan Kara
1 sibling, 0 replies; 32+ messages in thread
From: Darrick J. Wong @ 2024-07-15 20:03 UTC (permalink / raw)
To: Jeff Layton
Cc: Alexander Viro, Christian Brauner, Jan Kara, Steven Rostedt,
Masami Hiramatsu, Mathieu Desnoyers, Chandan Babu R,
Theodore Ts'o, Andreas Dilger, Chris Mason, Josef Bacik,
David Sterba, Hugh Dickins, Andrew Morton, Jonathan Corbet,
Dave Chinner, Andi Kleen, Christoph Hellwig, Uros Bizjak,
Kent Overstreet, Arnd Bergmann, Randy Dunlap, linux-fsdevel,
linux-kernel, linux-trace-kernel, linux-xfs, linux-ext4,
linux-btrfs, linux-mm, linux-nfs, linux-doc
On Mon, Jul 15, 2024 at 03:53:42PM -0400, Jeff Layton wrote:
> On Mon, 2024-07-15 at 11:32 -0700, Darrick J. Wong wrote:
> > On Mon, Jul 15, 2024 at 08:48:54AM -0400, Jeff Layton wrote:
> > > Four percpu counters for counting various stats around mgtimes, and
> > > a
> > > new debugfs file for displaying them:
> > >
> > > - number of attempted ctime updates
> > > - number of successful i_ctime_nsec swaps
> > > - number of fine-grained timestamp fetches
> > > - number of floor value swaps
> > >
> > > Reviewed-by: Josef Bacik <josef@toxicpanda.com>
> > > Signed-off-by: Jeff Layton <jlayton@kernel.org>
> > > ---
> > > fs/inode.c | 70
> > > +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
> > > 1 file changed, 69 insertions(+), 1 deletion(-)
> > >
> > > diff --git a/fs/inode.c b/fs/inode.c
> > > index 869994285e87..fff844345c35 100644
> > > --- a/fs/inode.c
> > > +++ b/fs/inode.c
> > > @@ -21,6 +21,8 @@
> > > #include <linux/list_lru.h>
> > > #include <linux/iversion.h>
> > > #include <linux/rw_hint.h>
> > > +#include <linux/seq_file.h>
> > > +#include <linux/debugfs.h>
> > > #include <trace/events/writeback.h>
> > > #define CREATE_TRACE_POINTS
> > > #include <trace/events/timestamp.h>
> > > @@ -80,6 +82,10 @@ EXPORT_SYMBOL(empty_aops);
> > >
> > > static DEFINE_PER_CPU(unsigned long, nr_inodes);
> > > static DEFINE_PER_CPU(unsigned long, nr_unused);
> > > +static DEFINE_PER_CPU(unsigned long, mg_ctime_updates);
> > > +static DEFINE_PER_CPU(unsigned long, mg_fine_stamps);
> > > +static DEFINE_PER_CPU(unsigned long, mg_floor_swaps);
> > > +static DEFINE_PER_CPU(unsigned long, mg_ctime_swaps);
> >
> > Should this all get switched off if CONFIG_DEBUG_FS=n?
> >
> > --D
> >
>
> Sure, why not. That's simple enough to do.
>
> I pushed an updated mgtime branch to my git tree. Here's the updated
> patch that's the only difference:
>
> https://git.kernel.org/pub/scm/linux/kernel/git/jlayton/linux.git/commit/?h=mgtime&id=ee7fe6e9c0598754861c8620230f15f3de538ca5
>
> Seems to build OK both with and without CONFIG_DEBUG_FS.
LGTM,
Reviewed-by: Darrick J. Wong <djwong@kernel.org>
Thank you for your work on all this multigrain stuff. :)
--D
>
> > >
> > > static struct kmem_cache *inode_cachep __ro_after_init;
> > >
> > > @@ -101,6 +107,42 @@ static inline long get_nr_inodes_unused(void)
> > > return sum < 0 ? 0 : sum;
> > > }
> > >
> > > +static long get_mg_ctime_updates(void)
> > > +{
> > > + int i;
> > > + long sum = 0;
> > > + for_each_possible_cpu(i)
> > > + sum += per_cpu(mg_ctime_updates, i);
> > > + return sum < 0 ? 0 : sum;
> > > +}
> > > +
> > > +static long get_mg_fine_stamps(void)
> > > +{
> > > + int i;
> > > + long sum = 0;
> > > + for_each_possible_cpu(i)
> > > + sum += per_cpu(mg_fine_stamps, i);
> > > + return sum < 0 ? 0 : sum;
> > > +}
> > > +
> > > +static long get_mg_floor_swaps(void)
> > > +{
> > > + int i;
> > > + long sum = 0;
> > > + for_each_possible_cpu(i)
> > > + sum += per_cpu(mg_floor_swaps, i);
> > > + return sum < 0 ? 0 : sum;
> > > +}
> > > +
> > > +static long get_mg_ctime_swaps(void)
> > > +{
> > > + int i;
> > > + long sum = 0;
> > > + for_each_possible_cpu(i)
> > > + sum += per_cpu(mg_ctime_swaps, i);
> > > + return sum < 0 ? 0 : sum;
> > > +}
> > > +
> > > long get_nr_dirty_inodes(void)
> > > {
> > > /* not actually dirty inodes, but a wild approximation */
> > > @@ -2655,6 +2697,7 @@ struct timespec64
> > > inode_set_ctime_current(struct inode *inode)
> > >
> > > /* Get a fine-grained time */
> > > fine = ktime_get();
> > > + this_cpu_inc(mg_fine_stamps);
> > >
> > > /*
> > > * If the cmpxchg works, we take the new
> > > floor value. If
> > > @@ -2663,11 +2706,14 @@ struct timespec64
> > > inode_set_ctime_current(struct inode *inode)
> > > * as good, so keep it.
> > > */
> > > old = floor;
> > > - if (!atomic64_try_cmpxchg(&ctime_floor,
> > > &old, fine))
> > > + if (atomic64_try_cmpxchg(&ctime_floor,
> > > &old, fine))
> > > + this_cpu_inc(mg_floor_swaps);
> > > + else
> > > fine = old;
> > > now = ktime_mono_to_real(fine);
> > > }
> > > }
> > > + this_cpu_inc(mg_ctime_updates);
> > > now_ts = timestamp_truncate(ktime_to_timespec64(now),
> > > inode);
> > > cur = cns;
> > >
> > > @@ -2682,6 +2728,7 @@ struct timespec64
> > > inode_set_ctime_current(struct inode *inode)
> > > /* If swap occurred, then we're (mostly) done */
> > > inode->i_ctime_sec = now_ts.tv_sec;
> > > trace_ctime_ns_xchg(inode, cns, now_ts.tv_nsec,
> > > cur);
> > > + this_cpu_inc(mg_ctime_swaps);
> > > } else {
> > > /*
> > > * Was the change due to someone marking the old
> > > ctime QUERIED?
> > > @@ -2751,3 +2798,24 @@ umode_t mode_strip_sgid(struct mnt_idmap
> > > *idmap,
> > > return mode & ~S_ISGID;
> > > }
> > > EXPORT_SYMBOL(mode_strip_sgid);
> > > +
> > > +static int mgts_show(struct seq_file *s, void *p)
> > > +{
> > > + long ctime_updates = get_mg_ctime_updates();
> > > + long ctime_swaps = get_mg_ctime_swaps();
> > > + long fine_stamps = get_mg_fine_stamps();
> > > + long floor_swaps = get_mg_floor_swaps();
> > > +
> > > + seq_printf(s, "%lu %lu %lu %lu\n",
> > > + ctime_updates, ctime_swaps, fine_stamps,
> > > floor_swaps);
> > > + return 0;
> > > +}
> > > +
> > > +DEFINE_SHOW_ATTRIBUTE(mgts);
> > > +
> > > +static int __init mg_debugfs_init(void)
> > > +{
> > > + debugfs_create_file("multigrain_timestamps", S_IFREG |
> > > S_IRUGO, NULL, NULL, &mgts_fops);
> > > + return 0;
> > > +}
> > > +late_initcall(mg_debugfs_init);
> > >
> > > --
> > > 2.45.2
> > >
> > >
>
> --
> Jeff Layton <jlayton@kernel.org>
>
^ permalink raw reply [flat|nested] 32+ messages in thread
* Re: [PATCH v6 0/9] fs: multigrain timestamp redux
2024-07-15 12:48 [PATCH v6 0/9] fs: multigrain timestamp redux Jeff Layton
` (8 preceding siblings ...)
2024-07-15 12:49 ` [PATCH v6 9/9] tmpfs: add support for " Jeff Layton
@ 2024-07-16 7:37 ` Christian Brauner
2024-07-16 12:45 ` Jeff Layton
9 siblings, 1 reply; 32+ messages in thread
From: Christian Brauner @ 2024-07-16 7:37 UTC (permalink / raw)
To: Jeff Layton
Cc: Alexander Viro, Jan Kara, Steven Rostedt, Masami Hiramatsu,
Mathieu Desnoyers, Chandan Babu R, Darrick J. Wong,
Theodore Ts'o, Andreas Dilger, Chris Mason, Josef Bacik,
David Sterba, Hugh Dickins, Andrew Morton, Jonathan Corbet,
Dave Chinner, Andi Kleen, Christoph Hellwig, Uros Bizjak,
Kent Overstreet, Arnd Bergmann, Randy Dunlap, linux-fsdevel,
linux-kernel, linux-trace-kernel, linux-xfs, linux-ext4,
linux-btrfs, linux-mm, linux-nfs, linux-doc
On Mon, Jul 15, 2024 at 08:48:51AM GMT, Jeff Layton wrote:
> I think this is pretty much ready for linux-next now. Since the latest
> changes are pretty minimal, I've left the Reviewed-by's intact. It would
> be nice to have acks or reviews from maintainers for ext4 and tmpfs too.
>
> I did try to plumb this into bcachefs too, but the way it handles
> timestamps makes that pretty difficult. It keeps the active copies in an
> internal representation of the on-disk inode and periodically copies
> them to struct inode. This is backward from the way most blockdev
> filesystems do this.
>
> Christian, would you be willing to pick these up with an eye toward
> v6.12 after the merge window settles?
Yup. About to queue it up. I'll try to find some time to go through it
so I might have some replies later but that shouldn't hold up linux-next
at all.
^ permalink raw reply [flat|nested] 32+ messages in thread
* Re: [PATCH v6 0/9] fs: multigrain timestamp redux
2024-07-16 7:37 ` [PATCH v6 0/9] fs: multigrain timestamp redux Christian Brauner
@ 2024-07-16 12:45 ` Jeff Layton
2024-07-22 15:30 ` Christian Brauner
0 siblings, 1 reply; 32+ messages in thread
From: Jeff Layton @ 2024-07-16 12:45 UTC (permalink / raw)
To: Christian Brauner
Cc: Alexander Viro, Jan Kara, Steven Rostedt, Masami Hiramatsu,
Mathieu Desnoyers, Chandan Babu R, Darrick J. Wong,
Theodore Ts'o, Andreas Dilger, Chris Mason, Josef Bacik,
David Sterba, Hugh Dickins, Andrew Morton, Jonathan Corbet,
Dave Chinner, Andi Kleen, Christoph Hellwig, Uros Bizjak,
Kent Overstreet, Arnd Bergmann, Randy Dunlap, linux-fsdevel,
linux-kernel, linux-trace-kernel, linux-xfs, linux-ext4,
linux-btrfs, linux-mm, linux-nfs, linux-doc
On Tue, 2024-07-16 at 09:37 +0200, Christian Brauner wrote:
> On Mon, Jul 15, 2024 at 08:48:51AM GMT, Jeff Layton wrote:
> > I think this is pretty much ready for linux-next now. Since the latest
> > changes are pretty minimal, I've left the Reviewed-by's intact. It would
> > be nice to have acks or reviews from maintainers for ext4 and tmpfs too.
> >
> > I did try to plumb this into bcachefs too, but the way it handles
> > timestamps makes that pretty difficult. It keeps the active copies in an
> > internal representation of the on-disk inode and periodically copies
> > them to struct inode. This is backward from the way most blockdev
> > filesystems do this.
> >
> > Christian, would you be willing to pick these up with an eye toward
> > v6.12 after the merge window settles?
>
> Yup. About to queue it up. I'll try to find some time to go through it
> so I might have some replies later but that shouldn't hold up linux-next
> at all.
Great!
There is one minor update to the percpu counter patch to compile those
out when debugfs isn't enabled, so it may be best to pick the series
from the "mgtime" branch in my public git tree. Let me know if you'd
rather I re-post the series though.
Thanks!
--
Jeff Layton <jlayton@kernel.org>
^ permalink raw reply [flat|nested] 32+ messages in thread
* Re: [PATCH v6 5/9] Documentation: add a new file documenting multigrain timestamps
2024-07-15 12:48 ` [PATCH v6 5/9] Documentation: add a new file documenting multigrain timestamps Jeff Layton
2024-07-15 18:45 ` Darrick J. Wong
@ 2024-07-17 6:00 ` Randy Dunlap
2024-07-17 11:31 ` Jan Kara
2 siblings, 0 replies; 32+ messages in thread
From: Randy Dunlap @ 2024-07-17 6:00 UTC (permalink / raw)
To: Jeff Layton, Alexander Viro, Christian Brauner, Jan Kara,
Steven Rostedt, Masami Hiramatsu, Mathieu Desnoyers,
Chandan Babu R, Darrick J. Wong, Theodore Ts'o,
Andreas Dilger, Chris Mason, Josef Bacik, David Sterba,
Hugh Dickins, Andrew Morton, Jonathan Corbet
Cc: Dave Chinner, Andi Kleen, Christoph Hellwig, Uros Bizjak,
Kent Overstreet, Arnd Bergmann, linux-fsdevel, linux-kernel,
linux-trace-kernel, linux-xfs, linux-ext4, linux-btrfs, linux-mm,
linux-nfs, linux-doc
On 7/15/24 5:48 AM, Jeff Layton wrote:
> Add a high-level document that describes how multigrain timestamps work,
> rationale for them, and some info about implementation and tradeoffs.
>
> Reviewed-by: Josef Bacik <josef@toxicpanda.com>
> Signed-off-by: Jeff Layton <jlayton@kernel.org>
> ---
> Documentation/filesystems/multigrain-ts.rst | 120 ++++++++++++++++++++++++++++
> 1 file changed, 120 insertions(+)
>
Reviewed-by: Randy Dunlap <rdunlap@infradead.org>
Thanks.
--
~Randy
^ permalink raw reply [flat|nested] 32+ messages in thread
* Re: [PATCH v6 1/9] fs: add infrastructure for multigrain timestamps
2024-07-15 12:48 ` [PATCH v6 1/9] fs: add infrastructure for multigrain timestamps Jeff Layton
@ 2024-07-17 10:40 ` Jan Kara
2024-08-08 22:09 ` Mateusz Guzik
2024-08-08 23:43 ` Mateusz Guzik
2 siblings, 0 replies; 32+ messages in thread
From: Jan Kara @ 2024-07-17 10:40 UTC (permalink / raw)
To: Jeff Layton
Cc: Alexander Viro, Christian Brauner, Jan Kara, Steven Rostedt,
Masami Hiramatsu, Mathieu Desnoyers, Chandan Babu R,
Darrick J. Wong, Theodore Ts'o, Andreas Dilger, Chris Mason,
Josef Bacik, David Sterba, Hugh Dickins, Andrew Morton,
Jonathan Corbet, Dave Chinner, Andi Kleen, Christoph Hellwig,
Uros Bizjak, Kent Overstreet, Arnd Bergmann, Randy Dunlap,
linux-fsdevel, linux-kernel, linux-trace-kernel, linux-xfs,
linux-ext4, linux-btrfs, linux-mm, linux-nfs, linux-doc
On Mon 15-07-24 08:48:52, Jeff Layton wrote:
> The VFS has always used coarse-grained timestamps when updating the
> ctime and mtime after a change. This has the benefit of allowing
> filesystems to optimize away a lot metadata updates, down to around 1
> per jiffy, even when a file is under heavy writes.
>
> Unfortunately, this has always been an issue when we're exporting via
> NFSv3, which relies on timestamps to validate caches. A lot of changes
> can happen in a jiffy, so timestamps aren't sufficient to help the
> client decide when to invalidate the cache. Even with NFSv4, a lot of
> exported filesystems don't properly support a change attribute and are
> subject to the same problems with timestamp granularity. Other
> applications have similar issues with timestamps (e.g backup
> applications).
>
> If we were to always use fine-grained timestamps, that would improve the
> situation, but that becomes rather expensive, as the underlying
> filesystem would have to log a lot more metadata updates.
>
> What we need is a way to only use fine-grained timestamps when they are
> being actively queried. Use the (unused) top bit in inode->i_ctime_nsec
> as a flag that indicates whether the current timestamps have been
> queried via stat() or the like. When it's set, we allow the kernel to
> use a fine-grained timestamp iff it's necessary to make the ctime show
> a different value.
>
> This solves the problem of being able to distinguish the timestamp
> between updates, but introduces a new problem: it's now possible for a
> file being changed to get a fine-grained timestamp. A file that is
> altered just a bit later can then get a coarse-grained one that appears
> older than the earlier fine-grained time. This violates timestamp
> ordering guarantees.
>
> To remedy this, keep a global monotonic atomic64_t value that acts as a
> timestamp floor. When we go to stamp a file, we first get the latter of
> the current floor value and the current coarse-grained time. If the
> inode ctime hasn't been queried then we just attempt to stamp it with
> that value.
>
> If it has been queried, then first see whether the current coarse time
> is later than the existing ctime. If it is, then we accept that value.
> If it isn't, then we get a fine-grained time and try to swap that into
> the global floor. Whether that succeeds or fails, we take the resulting
> floor time, convert it to realtime and try to swap that into the ctime.
>
> We take the result of the ctime swap whether it succeeds or fails, since
> either is just as valid.
>
> Filesystems can opt into this by setting the FS_MGTIME fstype flag.
> Others should be unaffected (other than being subject to the same floor
> value as multigrain filesystems).
>
> Reviewed-by: Darrick J. Wong <djwong@kernel.org>
> Reviewed-by: Josef Bacik <josef@toxicpanda.com>
> Signed-off-by: Jeff Layton <jlayton@kernel.org>
Phew! Quite subtle in the end but it looks good to me. Feel free to add:
Reviewed-by: Jan Kara <jack@suse.cz>
Honza
> ---
> fs/inode.c | 176 +++++++++++++++++++++++++++++++++++++++++++++--------
> fs/stat.c | 36 ++++++++++-
> include/linux/fs.h | 34 ++++++++---
> 3 files changed, 209 insertions(+), 37 deletions(-)
>
> diff --git a/fs/inode.c b/fs/inode.c
> index f356fe2ec2b6..417acbeabef3 100644
> --- a/fs/inode.c
> +++ b/fs/inode.c
> @@ -60,6 +60,13 @@ static unsigned int i_hash_shift __ro_after_init;
> static struct hlist_head *inode_hashtable __ro_after_init;
> static __cacheline_aligned_in_smp DEFINE_SPINLOCK(inode_hash_lock);
>
> +/*
> + * This represents the latest fine-grained time that we have handed out as a
> + * timestamp on the system. Tracked as a monotonic value, and converted to the
> + * realtime clock on an as-needed basis.
> + */
> +static __cacheline_aligned_in_smp atomic64_t ctime_floor;
> +
> /*
> * Empty aops. Can be used for the cases where the user does not
> * define any of the address_space operations.
> @@ -2127,19 +2134,72 @@ int file_remove_privs(struct file *file)
> }
> EXPORT_SYMBOL(file_remove_privs);
>
> +/**
> + * coarse_ctime - return the current coarse-grained time
> + * @floor: current (monotonic) ctime_floor value
> + *
> + * Get the coarse-grained time, and then determine whether to
> + * return it or the current floor value. Returns the later of the
> + * floor and coarse grained timestamps, converted to realtime
> + * clock value.
> + */
> +static ktime_t coarse_ctime(ktime_t floor)
> +{
> + ktime_t coarse = ktime_get_coarse();
> +
> + /* If coarse time is already newer, return that */
> + if (!ktime_after(floor, coarse))
> + return ktime_get_coarse_real();
> + return ktime_mono_to_real(floor);
> +}
> +
> +/**
> + * current_time - Return FS time (possibly fine-grained)
> + * @inode: inode.
> + *
> + * Return the current time truncated to the time granularity supported by
> + * the fs, as suitable for a ctime/mtime change. If the ctime is flagged
> + * as having been QUERIED, get a fine-grained timestamp.
> + */
> +struct timespec64 current_time(struct inode *inode)
> +{
> + ktime_t floor = atomic64_read(&ctime_floor);
> + ktime_t now = coarse_ctime(floor);
> + struct timespec64 now_ts = ktime_to_timespec64(now);
> + u32 cns;
> +
> + if (!is_mgtime(inode))
> + goto out;
> +
> + /* If nothing has queried it, then coarse time is fine */
> + cns = smp_load_acquire(&inode->i_ctime_nsec);
> + if (cns & I_CTIME_QUERIED) {
> + /*
> + * If there is no apparent change, then
> + * get a fine-grained timestamp.
> + */
> + if (now_ts.tv_nsec == (cns & ~I_CTIME_QUERIED))
> + ktime_get_real_ts64(&now_ts);
> + }
> +out:
> + return timestamp_truncate(now_ts, inode);
> +}
> +EXPORT_SYMBOL(current_time);
> +
> static int inode_needs_update_time(struct inode *inode)
> {
> + struct timespec64 now, ts;
> int sync_it = 0;
> - struct timespec64 now = current_time(inode);
> - struct timespec64 ts;
>
> /* First try to exhaust all avenues to not sync */
> if (IS_NOCMTIME(inode))
> return 0;
>
> + now = current_time(inode);
> +
> ts = inode_get_mtime(inode);
> if (!timespec64_equal(&ts, &now))
> - sync_it = S_MTIME;
> + sync_it |= S_MTIME;
>
> ts = inode_get_ctime(inode);
> if (!timespec64_equal(&ts, &now))
> @@ -2507,6 +2567,15 @@ void inode_nohighmem(struct inode *inode)
> }
> EXPORT_SYMBOL(inode_nohighmem);
>
> +struct timespec64 inode_set_ctime_to_ts(struct inode *inode, struct timespec64 ts)
> +{
> + set_normalized_timespec64(&ts, ts.tv_sec, ts.tv_nsec);
> + inode->i_ctime_sec = ts.tv_sec;
> + inode->i_ctime_nsec = ts.tv_nsec;
> + return ts;
> +}
> +EXPORT_SYMBOL(inode_set_ctime_to_ts);
> +
> /**
> * timestamp_truncate - Truncate timespec to a granularity
> * @t: Timespec
> @@ -2538,38 +2607,91 @@ struct timespec64 timestamp_truncate(struct timespec64 t, struct inode *inode)
> }
> EXPORT_SYMBOL(timestamp_truncate);
>
> -/**
> - * current_time - Return FS time
> - * @inode: inode.
> - *
> - * Return the current time truncated to the time granularity supported by
> - * the fs.
> - *
> - * Note that inode and inode->sb cannot be NULL.
> - * Otherwise, the function warns and returns time without truncation.
> - */
> -struct timespec64 current_time(struct inode *inode)
> -{
> - struct timespec64 now;
> -
> - ktime_get_coarse_real_ts64(&now);
> - return timestamp_truncate(now, inode);
> -}
> -EXPORT_SYMBOL(current_time);
> -
> /**
> * inode_set_ctime_current - set the ctime to current_time
> * @inode: inode
> *
> - * Set the inode->i_ctime to the current value for the inode. Returns
> - * the current value that was assigned to i_ctime.
> + * Set the inode's ctime to the current value for the inode. Returns the
> + * current value that was assigned. If this is not a multigrain inode, then we
> + * just set it to whatever the coarse_ctime is.
> + *
> + * If it is multigrain, then we first see if the coarse-grained timestamp is
> + * distinct from what we have. If so, then we'll just use that. If we have to
> + * get a fine-grained timestamp, then do so, and try to swap it into the floor.
> + * We accept the new floor value regardless of the outcome of the cmpxchg.
> + * After that, we try to swap the new value into i_ctime_nsec. Again, we take
> + * the resulting ctime, regardless of the outcome of the swap.
> */
> struct timespec64 inode_set_ctime_current(struct inode *inode)
> {
> - struct timespec64 now = current_time(inode);
> + ktime_t now, floor = atomic64_read(&ctime_floor);
> + struct timespec64 now_ts;
> + u32 cns, cur;
> +
> + now = coarse_ctime(floor);
> +
> + /* Just return that if this is not a multigrain fs */
> + if (!is_mgtime(inode)) {
> + now_ts = timestamp_truncate(ktime_to_timespec64(now), inode);
> + inode_set_ctime_to_ts(inode, now_ts);
> + goto out;
> + }
> +
> + /*
> + * We only need a fine-grained time if someone has queried it,
> + * and the current coarse grained time isn't later than what's
> + * already there.
> + */
> + cns = smp_load_acquire(&inode->i_ctime_nsec);
> + if (cns & I_CTIME_QUERIED) {
> + ktime_t ctime = ktime_set(inode->i_ctime_sec, cns & ~I_CTIME_QUERIED);
> +
> + if (!ktime_after(now, ctime)) {
> + ktime_t old, fine;
> +
> + /* Get a fine-grained time */
> + fine = ktime_get();
>
> - inode_set_ctime_to_ts(inode, now);
> - return now;
> + /*
> + * If the cmpxchg works, we take the new floor value. If
> + * not, then that means that someone else changed it after we
> + * fetched it but before we got here. That value is just
> + * as good, so keep it.
> + */
> + old = floor;
> + if (!atomic64_try_cmpxchg(&ctime_floor, &old, fine))
> + fine = old;
> + now = ktime_mono_to_real(fine);
> + }
> + }
> + now_ts = timestamp_truncate(ktime_to_timespec64(now), inode);
> + cur = cns;
> +
> + /* No need to cmpxchg if it's exactly the same */
> + if (cns == now_ts.tv_nsec && inode->i_ctime_sec == now_ts.tv_sec)
> + goto out;
> +retry:
> + /* Try to swap the nsec value into place. */
> + if (try_cmpxchg(&inode->i_ctime_nsec, &cur, now_ts.tv_nsec)) {
> + /* If swap occurred, then we're (mostly) done */
> + inode->i_ctime_sec = now_ts.tv_sec;
> + } else {
> + /*
> + * Was the change due to someone marking the old ctime QUERIED?
> + * If so then retry the swap. This can only happen once since
> + * the only way to clear I_CTIME_QUERIED is to stamp the inode
> + * with a new ctime.
> + */
> + if (!(cns & I_CTIME_QUERIED) && (cns | I_CTIME_QUERIED) == cur) {
> + cns = cur;
> + goto retry;
> + }
> + /* Otherwise, keep the existing ctime */
> + now_ts.tv_sec = inode->i_ctime_sec;
> + now_ts.tv_nsec = cur & ~I_CTIME_QUERIED;
> + }
> +out:
> + return now_ts;
> }
> EXPORT_SYMBOL(inode_set_ctime_current);
>
> diff --git a/fs/stat.c b/fs/stat.c
> index 6f65b3456cad..df7fdd3afed9 100644
> --- a/fs/stat.c
> +++ b/fs/stat.c
> @@ -26,6 +26,32 @@
> #include "internal.h"
> #include "mount.h"
>
> +/**
> + * fill_mg_cmtime - Fill in the mtime and ctime and flag ctime as QUERIED
> + * @stat: where to store the resulting values
> + * @request_mask: STATX_* values requested
> + * @inode: inode from which to grab the c/mtime
> + *
> + * Given @inode, grab the ctime and mtime out if it and store the result
> + * in @stat. When fetching the value, flag it as queried so the next write
> + * will ensure a distinct timestamp.
> + */
> +void fill_mg_cmtime(struct kstat *stat, u32 request_mask, struct inode *inode)
> +{
> + atomic_t *pcn = (atomic_t *)&inode->i_ctime_nsec;
> +
> + /* If neither time was requested, then don't report them */
> + if (!(request_mask & (STATX_CTIME|STATX_MTIME))) {
> + stat->result_mask &= ~(STATX_CTIME|STATX_MTIME);
> + return;
> + }
> +
> + stat->mtime = inode_get_mtime(inode);
> + stat->ctime.tv_sec = inode->i_ctime_sec;
> + stat->ctime.tv_nsec = ((u32)atomic_fetch_or(I_CTIME_QUERIED, pcn)) & ~I_CTIME_QUERIED;
> +}
> +EXPORT_SYMBOL(fill_mg_cmtime);
> +
> /**
> * generic_fillattr - Fill in the basic attributes from the inode struct
> * @idmap: idmap of the mount the inode was found from
> @@ -58,8 +84,14 @@ void generic_fillattr(struct mnt_idmap *idmap, u32 request_mask,
> stat->rdev = inode->i_rdev;
> stat->size = i_size_read(inode);
> stat->atime = inode_get_atime(inode);
> - stat->mtime = inode_get_mtime(inode);
> - stat->ctime = inode_get_ctime(inode);
> +
> + if (is_mgtime(inode)) {
> + fill_mg_cmtime(stat, request_mask, inode);
> + } else {
> + stat->ctime = inode_get_ctime(inode);
> + stat->mtime = inode_get_mtime(inode);
> + }
> +
> stat->blksize = i_blocksize(inode);
> stat->blocks = inode->i_blocks;
>
> diff --git a/include/linux/fs.h b/include/linux/fs.h
> index dc9f9c4b2572..f873f6c58669 100644
> --- a/include/linux/fs.h
> +++ b/include/linux/fs.h
> @@ -1608,6 +1608,17 @@ static inline struct timespec64 inode_set_mtime(struct inode *inode,
> return inode_set_mtime_to_ts(inode, ts);
> }
>
> +/*
> + * Multigrain timestamps
> + *
> + * Conditionally use fine-grained ctime and mtime timestamps when there
> + * are users actively observing them via getattr. The primary use-case
> + * for this is NFS clients that use the ctime to distinguish between
> + * different states of the file, and that are often fooled by multiple
> + * operations that occur in the same coarse-grained timer tick.
> + */
> +#define I_CTIME_QUERIED ((u32)BIT(31))
> +
> static inline time64_t inode_get_ctime_sec(const struct inode *inode)
> {
> return inode->i_ctime_sec;
> @@ -1615,7 +1626,7 @@ static inline time64_t inode_get_ctime_sec(const struct inode *inode)
>
> static inline long inode_get_ctime_nsec(const struct inode *inode)
> {
> - return inode->i_ctime_nsec;
> + return inode->i_ctime_nsec & ~I_CTIME_QUERIED;
> }
>
> static inline struct timespec64 inode_get_ctime(const struct inode *inode)
> @@ -1626,13 +1637,7 @@ static inline struct timespec64 inode_get_ctime(const struct inode *inode)
> return ts;
> }
>
> -static inline struct timespec64 inode_set_ctime_to_ts(struct inode *inode,
> - struct timespec64 ts)
> -{
> - inode->i_ctime_sec = ts.tv_sec;
> - inode->i_ctime_nsec = ts.tv_nsec;
> - return ts;
> -}
> +struct timespec64 inode_set_ctime_to_ts(struct inode *inode, struct timespec64 ts);
>
> /**
> * inode_set_ctime - set the ctime in the inode
> @@ -2490,6 +2495,7 @@ struct file_system_type {
> #define FS_USERNS_MOUNT 8 /* Can be mounted by userns root */
> #define FS_DISALLOW_NOTIFY_PERM 16 /* Disable fanotify permission events */
> #define FS_ALLOW_IDMAP 32 /* FS has been updated to handle vfs idmappings. */
> +#define FS_MGTIME 64 /* FS uses multigrain timestamps */
> #define FS_RENAME_DOES_D_MOVE 32768 /* FS will handle d_move() during rename() internally. */
> int (*init_fs_context)(struct fs_context *);
> const struct fs_parameter_spec *parameters;
> @@ -2513,6 +2519,17 @@ struct file_system_type {
>
> #define MODULE_ALIAS_FS(NAME) MODULE_ALIAS("fs-" NAME)
>
> +/**
> + * is_mgtime: is this inode using multigrain timestamps
> + * @inode: inode to test for multigrain timestamps
> + *
> + * Return true if the inode uses multigrain timestamps, false otherwise.
> + */
> +static inline bool is_mgtime(const struct inode *inode)
> +{
> + return inode->i_sb->s_type->fs_flags & FS_MGTIME;
> +}
> +
> extern struct dentry *mount_bdev(struct file_system_type *fs_type,
> int flags, const char *dev_name, void *data,
> int (*fill_super)(struct super_block *, void *, int));
> @@ -3252,6 +3269,7 @@ extern void page_put_link(void *);
> extern int page_symlink(struct inode *inode, const char *symname, int len);
> extern const struct inode_operations page_symlink_inode_operations;
> extern void kfree_link(void *);
> +void fill_mg_cmtime(struct kstat *stat, u32 request_mask, struct inode *inode);
> void generic_fillattr(struct mnt_idmap *, u32, struct inode *, struct kstat *);
> void generic_fill_statx_attr(struct inode *inode, struct kstat *stat);
> extern int vfs_getattr_nosec(const struct path *, struct kstat *, u32, unsigned int);
>
> --
> 2.45.2
>
--
Jan Kara <jack@suse.com>
SUSE Labs, CR
^ permalink raw reply [flat|nested] 32+ messages in thread
* Re: [PATCH v6 2/9] fs: tracepoints around multigrain timestamp events
2024-07-15 12:48 ` [PATCH v6 2/9] fs: tracepoints around multigrain timestamp events Jeff Layton
2024-07-15 18:29 ` Darrick J. Wong
@ 2024-07-17 10:41 ` Jan Kara
1 sibling, 0 replies; 32+ messages in thread
From: Jan Kara @ 2024-07-17 10:41 UTC (permalink / raw)
To: Jeff Layton
Cc: Alexander Viro, Christian Brauner, Jan Kara, Steven Rostedt,
Masami Hiramatsu, Mathieu Desnoyers, Chandan Babu R,
Darrick J. Wong, Theodore Ts'o, Andreas Dilger, Chris Mason,
Josef Bacik, David Sterba, Hugh Dickins, Andrew Morton,
Jonathan Corbet, Dave Chinner, Andi Kleen, Christoph Hellwig,
Uros Bizjak, Kent Overstreet, Arnd Bergmann, Randy Dunlap,
linux-fsdevel, linux-kernel, linux-trace-kernel, linux-xfs,
linux-ext4, linux-btrfs, linux-mm, linux-nfs, linux-doc
On Mon 15-07-24 08:48:53, Jeff Layton wrote:
> Add some tracepoints around various multigrain timestamp events.
>
> Reviewed-by: Josef Bacik <josef@toxicpanda.com>
> Signed-off-by: Jeff Layton <jlayton@kernel.org>
Looks good to me. Feel free to add:
Reviewed-by: Jan Kara <jack@suse.cz>
Honza
> ---
> fs/inode.c | 9 ++-
> fs/stat.c | 3 +
> include/trace/events/timestamp.h | 124 +++++++++++++++++++++++++++++++++++++++
> 3 files changed, 135 insertions(+), 1 deletion(-)
>
> diff --git a/fs/inode.c b/fs/inode.c
> index 417acbeabef3..869994285e87 100644
> --- a/fs/inode.c
> +++ b/fs/inode.c
> @@ -22,6 +22,9 @@
> #include <linux/iversion.h>
> #include <linux/rw_hint.h>
> #include <trace/events/writeback.h>
> +#define CREATE_TRACE_POINTS
> +#include <trace/events/timestamp.h>
> +
> #include "internal.h"
>
> /*
> @@ -2569,6 +2572,7 @@ EXPORT_SYMBOL(inode_nohighmem);
>
> struct timespec64 inode_set_ctime_to_ts(struct inode *inode, struct timespec64 ts)
> {
> + trace_inode_set_ctime_to_ts(inode, &ts);
> set_normalized_timespec64(&ts, ts.tv_sec, ts.tv_nsec);
> inode->i_ctime_sec = ts.tv_sec;
> inode->i_ctime_nsec = ts.tv_nsec;
> @@ -2668,13 +2672,16 @@ struct timespec64 inode_set_ctime_current(struct inode *inode)
> cur = cns;
>
> /* No need to cmpxchg if it's exactly the same */
> - if (cns == now_ts.tv_nsec && inode->i_ctime_sec == now_ts.tv_sec)
> + if (cns == now_ts.tv_nsec && inode->i_ctime_sec == now_ts.tv_sec) {
> + trace_ctime_xchg_skip(inode, &now_ts);
> goto out;
> + }
> retry:
> /* Try to swap the nsec value into place. */
> if (try_cmpxchg(&inode->i_ctime_nsec, &cur, now_ts.tv_nsec)) {
> /* If swap occurred, then we're (mostly) done */
> inode->i_ctime_sec = now_ts.tv_sec;
> + trace_ctime_ns_xchg(inode, cns, now_ts.tv_nsec, cur);
> } else {
> /*
> * Was the change due to someone marking the old ctime QUERIED?
> diff --git a/fs/stat.c b/fs/stat.c
> index df7fdd3afed9..552dfd67688b 100644
> --- a/fs/stat.c
> +++ b/fs/stat.c
> @@ -23,6 +23,8 @@
> #include <linux/uaccess.h>
> #include <asm/unistd.h>
>
> +#include <trace/events/timestamp.h>
> +
> #include "internal.h"
> #include "mount.h"
>
> @@ -49,6 +51,7 @@ void fill_mg_cmtime(struct kstat *stat, u32 request_mask, struct inode *inode)
> stat->mtime = inode_get_mtime(inode);
> stat->ctime.tv_sec = inode->i_ctime_sec;
> stat->ctime.tv_nsec = ((u32)atomic_fetch_or(I_CTIME_QUERIED, pcn)) & ~I_CTIME_QUERIED;
> + trace_fill_mg_cmtime(inode, &stat->ctime, &stat->mtime);
> }
> EXPORT_SYMBOL(fill_mg_cmtime);
>
> diff --git a/include/trace/events/timestamp.h b/include/trace/events/timestamp.h
> new file mode 100644
> index 000000000000..c9e5ec930054
> --- /dev/null
> +++ b/include/trace/events/timestamp.h
> @@ -0,0 +1,124 @@
> +/* SPDX-License-Identifier: GPL-2.0 */
> +#undef TRACE_SYSTEM
> +#define TRACE_SYSTEM timestamp
> +
> +#if !defined(_TRACE_TIMESTAMP_H) || defined(TRACE_HEADER_MULTI_READ)
> +#define _TRACE_TIMESTAMP_H
> +
> +#include <linux/tracepoint.h>
> +#include <linux/fs.h>
> +
> +#define CTIME_QUERIED_FLAGS \
> + { I_CTIME_QUERIED, "Q" }
> +
> +DECLARE_EVENT_CLASS(ctime,
> + TP_PROTO(struct inode *inode,
> + struct timespec64 *ctime),
> +
> + TP_ARGS(inode, ctime),
> +
> + TP_STRUCT__entry(
> + __field(dev_t, dev)
> + __field(ino_t, ino)
> + __field(time64_t, ctime_s)
> + __field(u32, ctime_ns)
> + __field(u32, gen)
> + ),
> +
> + TP_fast_assign(
> + __entry->dev = inode->i_sb->s_dev;
> + __entry->ino = inode->i_ino;
> + __entry->gen = inode->i_generation;
> + __entry->ctime_s = ctime->tv_sec;
> + __entry->ctime_ns = ctime->tv_nsec;
> + ),
> +
> + TP_printk("ino=%d:%d:%ld:%u ctime=%lld.%u",
> + MAJOR(__entry->dev), MINOR(__entry->dev), __entry->ino, __entry->gen,
> + __entry->ctime_s, __entry->ctime_ns
> + )
> +);
> +
> +DEFINE_EVENT(ctime, inode_set_ctime_to_ts,
> + TP_PROTO(struct inode *inode,
> + struct timespec64 *ctime),
> + TP_ARGS(inode, ctime));
> +
> +DEFINE_EVENT(ctime, ctime_xchg_skip,
> + TP_PROTO(struct inode *inode,
> + struct timespec64 *ctime),
> + TP_ARGS(inode, ctime));
> +
> +TRACE_EVENT(ctime_ns_xchg,
> + TP_PROTO(struct inode *inode,
> + u32 old,
> + u32 new,
> + u32 cur),
> +
> + TP_ARGS(inode, old, new, cur),
> +
> + TP_STRUCT__entry(
> + __field(dev_t, dev)
> + __field(ino_t, ino)
> + __field(u32, gen)
> + __field(u32, old)
> + __field(u32, new)
> + __field(u32, cur)
> + ),
> +
> + TP_fast_assign(
> + __entry->dev = inode->i_sb->s_dev;
> + __entry->ino = inode->i_ino;
> + __entry->gen = inode->i_generation;
> + __entry->old = old;
> + __entry->new = new;
> + __entry->cur = cur;
> + ),
> +
> + TP_printk("ino=%d:%d:%ld:%u old=%u:%s new=%u cur=%u:%s",
> + MAJOR(__entry->dev), MINOR(__entry->dev), __entry->ino, __entry->gen,
> + __entry->old & ~I_CTIME_QUERIED,
> + __print_flags(__entry->old & I_CTIME_QUERIED, "|", CTIME_QUERIED_FLAGS),
> + __entry->new,
> + __entry->cur & ~I_CTIME_QUERIED,
> + __print_flags(__entry->cur & I_CTIME_QUERIED, "|", CTIME_QUERIED_FLAGS)
> + )
> +);
> +
> +TRACE_EVENT(fill_mg_cmtime,
> + TP_PROTO(struct inode *inode,
> + struct timespec64 *ctime,
> + struct timespec64 *mtime),
> +
> + TP_ARGS(inode, ctime, mtime),
> +
> + TP_STRUCT__entry(
> + __field(dev_t, dev)
> + __field(ino_t, ino)
> + __field(time64_t, ctime_s)
> + __field(time64_t, mtime_s)
> + __field(u32, ctime_ns)
> + __field(u32, mtime_ns)
> + __field(u32, gen)
> + ),
> +
> + TP_fast_assign(
> + __entry->dev = inode->i_sb->s_dev;
> + __entry->ino = inode->i_ino;
> + __entry->gen = inode->i_generation;
> + __entry->ctime_s = ctime->tv_sec;
> + __entry->mtime_s = mtime->tv_sec;
> + __entry->ctime_ns = ctime->tv_nsec;
> + __entry->mtime_ns = mtime->tv_nsec;
> + ),
> +
> + TP_printk("ino=%d:%d:%ld:%u ctime=%lld.%u mtime=%lld.%u",
> + MAJOR(__entry->dev), MINOR(__entry->dev), __entry->ino, __entry->gen,
> + __entry->ctime_s, __entry->ctime_ns,
> + __entry->mtime_s, __entry->mtime_ns
> + )
> +);
> +#endif /* _TRACE_TIMESTAMP_H */
> +
> +/* This part must be outside protection */
> +#include <trace/define_trace.h>
>
> --
> 2.45.2
>
--
Jan Kara <jack@suse.com>
SUSE Labs, CR
^ permalink raw reply [flat|nested] 32+ messages in thread
* Re: [PATCH v6 3/9] fs: add percpu counters for significant multigrain timestamp events
2024-07-15 19:53 ` Jeff Layton
2024-07-15 20:03 ` Darrick J. Wong
@ 2024-07-17 10:45 ` Jan Kara
1 sibling, 0 replies; 32+ messages in thread
From: Jan Kara @ 2024-07-17 10:45 UTC (permalink / raw)
To: Jeff Layton
Cc: Darrick J. Wong, Alexander Viro, Christian Brauner, Jan Kara,
Steven Rostedt, Masami Hiramatsu, Mathieu Desnoyers,
Chandan Babu R, Theodore Ts'o, Andreas Dilger, Chris Mason,
Josef Bacik, David Sterba, Hugh Dickins, Andrew Morton,
Jonathan Corbet, Dave Chinner, Andi Kleen, Christoph Hellwig,
Uros Bizjak, Kent Overstreet, Arnd Bergmann, Randy Dunlap,
linux-fsdevel, linux-kernel, linux-trace-kernel, linux-xfs,
linux-ext4, linux-btrfs, linux-mm, linux-nfs, linux-doc
On Mon 15-07-24 15:53:42, Jeff Layton wrote:
> On Mon, 2024-07-15 at 11:32 -0700, Darrick J. Wong wrote:
> > On Mon, Jul 15, 2024 at 08:48:54AM -0400, Jeff Layton wrote:
> > > Four percpu counters for counting various stats around mgtimes, and
> > > a
> > > new debugfs file for displaying them:
> > >
> > > - number of attempted ctime updates
> > > - number of successful i_ctime_nsec swaps
> > > - number of fine-grained timestamp fetches
> > > - number of floor value swaps
> > >
> > > Reviewed-by: Josef Bacik <josef@toxicpanda.com>
> > > Signed-off-by: Jeff Layton <jlayton@kernel.org>
> > > ---
> > > fs/inode.c | 70
> > > +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
> > > 1 file changed, 69 insertions(+), 1 deletion(-)
> > >
> > > diff --git a/fs/inode.c b/fs/inode.c
> > > index 869994285e87..fff844345c35 100644
> > > --- a/fs/inode.c
> > > +++ b/fs/inode.c
> > > @@ -21,6 +21,8 @@
> > > #include <linux/list_lru.h>
> > > #include <linux/iversion.h>
> > > #include <linux/rw_hint.h>
> > > +#include <linux/seq_file.h>
> > > +#include <linux/debugfs.h>
> > > #include <trace/events/writeback.h>
> > > #define CREATE_TRACE_POINTS
> > > #include <trace/events/timestamp.h>
> > > @@ -80,6 +82,10 @@ EXPORT_SYMBOL(empty_aops);
> > >
> > > static DEFINE_PER_CPU(unsigned long, nr_inodes);
> > > static DEFINE_PER_CPU(unsigned long, nr_unused);
> > > +static DEFINE_PER_CPU(unsigned long, mg_ctime_updates);
> > > +static DEFINE_PER_CPU(unsigned long, mg_fine_stamps);
> > > +static DEFINE_PER_CPU(unsigned long, mg_floor_swaps);
> > > +static DEFINE_PER_CPU(unsigned long, mg_ctime_swaps);
> >
> > Should this all get switched off if CONFIG_DEBUG_FS=n?
> >
> > --D
> >
>
> Sure, why not. That's simple enough to do.
>
> I pushed an updated mgtime branch to my git tree. Here's the updated
> patch that's the only difference:
>
> https://git.kernel.org/pub/scm/linux/kernel/git/jlayton/linux.git/commit/?h=mgtime&id=ee7fe6e9c0598754861c8620230f15f3de538ca5
>
> Seems to build OK both with and without CONFIG_DEBUG_FS.
Looks good to me. Feel free to add:
Reviewed-by: Jan Kara <jack@suse.cz>
Honza
>
> > >
> > > static struct kmem_cache *inode_cachep __ro_after_init;
> > >
> > > @@ -101,6 +107,42 @@ static inline long get_nr_inodes_unused(void)
> > > return sum < 0 ? 0 : sum;
> > > }
> > >
> > > +static long get_mg_ctime_updates(void)
> > > +{
> > > + int i;
> > > + long sum = 0;
> > > + for_each_possible_cpu(i)
> > > + sum += per_cpu(mg_ctime_updates, i);
> > > + return sum < 0 ? 0 : sum;
> > > +}
> > > +
> > > +static long get_mg_fine_stamps(void)
> > > +{
> > > + int i;
> > > + long sum = 0;
> > > + for_each_possible_cpu(i)
> > > + sum += per_cpu(mg_fine_stamps, i);
> > > + return sum < 0 ? 0 : sum;
> > > +}
> > > +
> > > +static long get_mg_floor_swaps(void)
> > > +{
> > > + int i;
> > > + long sum = 0;
> > > + for_each_possible_cpu(i)
> > > + sum += per_cpu(mg_floor_swaps, i);
> > > + return sum < 0 ? 0 : sum;
> > > +}
> > > +
> > > +static long get_mg_ctime_swaps(void)
> > > +{
> > > + int i;
> > > + long sum = 0;
> > > + for_each_possible_cpu(i)
> > > + sum += per_cpu(mg_ctime_swaps, i);
> > > + return sum < 0 ? 0 : sum;
> > > +}
> > > +
> > > long get_nr_dirty_inodes(void)
> > > {
> > > /* not actually dirty inodes, but a wild approximation */
> > > @@ -2655,6 +2697,7 @@ struct timespec64
> > > inode_set_ctime_current(struct inode *inode)
> > >
> > > /* Get a fine-grained time */
> > > fine = ktime_get();
> > > + this_cpu_inc(mg_fine_stamps);
> > >
> > > /*
> > > * If the cmpxchg works, we take the new
> > > floor value. If
> > > @@ -2663,11 +2706,14 @@ struct timespec64
> > > inode_set_ctime_current(struct inode *inode)
> > > * as good, so keep it.
> > > */
> > > old = floor;
> > > - if (!atomic64_try_cmpxchg(&ctime_floor,
> > > &old, fine))
> > > + if (atomic64_try_cmpxchg(&ctime_floor,
> > > &old, fine))
> > > + this_cpu_inc(mg_floor_swaps);
> > > + else
> > > fine = old;
> > > now = ktime_mono_to_real(fine);
> > > }
> > > }
> > > + this_cpu_inc(mg_ctime_updates);
> > > now_ts = timestamp_truncate(ktime_to_timespec64(now),
> > > inode);
> > > cur = cns;
> > >
> > > @@ -2682,6 +2728,7 @@ struct timespec64
> > > inode_set_ctime_current(struct inode *inode)
> > > /* If swap occurred, then we're (mostly) done */
> > > inode->i_ctime_sec = now_ts.tv_sec;
> > > trace_ctime_ns_xchg(inode, cns, now_ts.tv_nsec,
> > > cur);
> > > + this_cpu_inc(mg_ctime_swaps);
> > > } else {
> > > /*
> > > * Was the change due to someone marking the old
> > > ctime QUERIED?
> > > @@ -2751,3 +2798,24 @@ umode_t mode_strip_sgid(struct mnt_idmap
> > > *idmap,
> > > return mode & ~S_ISGID;
> > > }
> > > EXPORT_SYMBOL(mode_strip_sgid);
> > > +
> > > +static int mgts_show(struct seq_file *s, void *p)
> > > +{
> > > + long ctime_updates = get_mg_ctime_updates();
> > > + long ctime_swaps = get_mg_ctime_swaps();
> > > + long fine_stamps = get_mg_fine_stamps();
> > > + long floor_swaps = get_mg_floor_swaps();
> > > +
> > > + seq_printf(s, "%lu %lu %lu %lu\n",
> > > + ctime_updates, ctime_swaps, fine_stamps,
> > > floor_swaps);
> > > + return 0;
> > > +}
> > > +
> > > +DEFINE_SHOW_ATTRIBUTE(mgts);
> > > +
> > > +static int __init mg_debugfs_init(void)
> > > +{
> > > + debugfs_create_file("multigrain_timestamps", S_IFREG |
> > > S_IRUGO, NULL, NULL, &mgts_fops);
> > > + return 0;
> > > +}
> > > +late_initcall(mg_debugfs_init);
> > >
> > > --
> > > 2.45.2
> > >
> > >
>
> --
> Jeff Layton <jlayton@kernel.org>
>
--
Jan Kara <jack@suse.com>
SUSE Labs, CR
^ permalink raw reply [flat|nested] 32+ messages in thread
* Re: [PATCH v6 4/9] fs: have setattr_copy handle multigrain timestamps appropriately
2024-07-15 12:48 ` [PATCH v6 4/9] fs: have setattr_copy handle multigrain timestamps appropriately Jeff Layton
@ 2024-07-17 11:24 ` Jan Kara
0 siblings, 0 replies; 32+ messages in thread
From: Jan Kara @ 2024-07-17 11:24 UTC (permalink / raw)
To: Jeff Layton
Cc: Alexander Viro, Christian Brauner, Jan Kara, Steven Rostedt,
Masami Hiramatsu, Mathieu Desnoyers, Chandan Babu R,
Darrick J. Wong, Theodore Ts'o, Andreas Dilger, Chris Mason,
Josef Bacik, David Sterba, Hugh Dickins, Andrew Morton,
Jonathan Corbet, Dave Chinner, Andi Kleen, Christoph Hellwig,
Uros Bizjak, Kent Overstreet, Arnd Bergmann, Randy Dunlap,
linux-fsdevel, linux-kernel, linux-trace-kernel, linux-xfs,
linux-ext4, linux-btrfs, linux-mm, linux-nfs, linux-doc
On Mon 15-07-24 08:48:55, Jeff Layton wrote:
> The setattr codepath is still using coarse-grained timestamps, even on
> multigrain filesystems. To fix this, we need to fetch the timestamp for
> ctime updates later, at the point where the assignment occurs in
> setattr_copy.
>
> On a multigrain inode, ignore the ia_ctime in the attrs, and always
> update the ctime to the current clock value. Update the atime and mtime
> with the same value (if needed) unless they are being set to other
> specific values, a'la utimes().
>
> Note that we don't want to do this universally however, as some
> filesystems (e.g. most networked fs) want to do an explicit update
> elsewhere before updating the local inode.
>
> Reviewed-by: Darrick J. Wong <djwong@kernel.org>
> Reviewed-by: Josef Bacik <josef@toxicpanda.com>
> Signed-off-by: Jeff Layton <jlayton@kernel.org>
Looks good to me so feel free to add:
Reviewed-by: Jan Kara <jack@suse.cz>
What is a bit bothering me is that it's now confusing that ATTR_MTIME_SET /
ATTR_ATIME_SET is handled in different place for mgtime and normal inodes
and I'm concerned this will bite us in the future. But not everybody is
using setattr_copy() and unifying the handling of timestamps seems like
quite some work...
Honza
--
Jan Kara <jack@suse.com>
SUSE Labs, CR
^ permalink raw reply [flat|nested] 32+ messages in thread
* Re: [PATCH v6 5/9] Documentation: add a new file documenting multigrain timestamps
2024-07-15 12:48 ` [PATCH v6 5/9] Documentation: add a new file documenting multigrain timestamps Jeff Layton
2024-07-15 18:45 ` Darrick J. Wong
2024-07-17 6:00 ` Randy Dunlap
@ 2024-07-17 11:31 ` Jan Kara
2024-07-17 12:02 ` Jeff Layton
2 siblings, 1 reply; 32+ messages in thread
From: Jan Kara @ 2024-07-17 11:31 UTC (permalink / raw)
To: Jeff Layton
Cc: Alexander Viro, Christian Brauner, Jan Kara, Steven Rostedt,
Masami Hiramatsu, Mathieu Desnoyers, Chandan Babu R,
Darrick J. Wong, Theodore Ts'o, Andreas Dilger, Chris Mason,
Josef Bacik, David Sterba, Hugh Dickins, Andrew Morton,
Jonathan Corbet, Dave Chinner, Andi Kleen, Christoph Hellwig,
Uros Bizjak, Kent Overstreet, Arnd Bergmann, Randy Dunlap,
linux-fsdevel, linux-kernel, linux-trace-kernel, linux-xfs,
linux-ext4, linux-btrfs, linux-mm, linux-nfs, linux-doc
On Mon 15-07-24 08:48:56, Jeff Layton wrote:
> Add a high-level document that describes how multigrain timestamps work,
> rationale for them, and some info about implementation and tradeoffs.
>
> Reviewed-by: Josef Bacik <josef@toxicpanda.com>
> Signed-off-by: Jeff Layton <jlayton@kernel.org>
One comment below. With that fixed feel free to add:
Reviewed-by: Jan Kara <jack@suse.cz>
> +Implementation Notes
> +====================
> +Multigrain timestamps are intended for use by local filesystems that get
> +ctime values from the local clock. This is in contrast to network filesystems
> +and the like that just mirror timestamp values from a server.
> +
> +For most filesystems, it's sufficient to just set the FS_MGTIME flag in the
> +fstype->fs_flags in order to opt-in, providing the ctime is only ever set via
> +inode_set_ctime_current(). If the filesystem has a ->getattr routine that
> +doesn't call generic_fillattr, then you should have it call fill_mg_cmtime to
> +fill those values.
I think you should explicitely mention that ->setattr() implementation
needs to use setattr_copy() or otherwise mimic its behavior...
Honza
--
Jan Kara <jack@suse.com>
SUSE Labs, CR
^ permalink raw reply [flat|nested] 32+ messages in thread
* Re: [PATCH v6 7/9] ext4: switch to multigrain timestamps
2024-07-15 12:48 ` [PATCH v6 7/9] ext4: " Jeff Layton
@ 2024-07-17 11:32 ` Jan Kara
0 siblings, 0 replies; 32+ messages in thread
From: Jan Kara @ 2024-07-17 11:32 UTC (permalink / raw)
To: Jeff Layton
Cc: Alexander Viro, Christian Brauner, Jan Kara, Steven Rostedt,
Masami Hiramatsu, Mathieu Desnoyers, Chandan Babu R,
Darrick J. Wong, Theodore Ts'o, Andreas Dilger, Chris Mason,
Josef Bacik, David Sterba, Hugh Dickins, Andrew Morton,
Jonathan Corbet, Dave Chinner, Andi Kleen, Christoph Hellwig,
Uros Bizjak, Kent Overstreet, Arnd Bergmann, Randy Dunlap,
linux-fsdevel, linux-kernel, linux-trace-kernel, linux-xfs,
linux-ext4, linux-btrfs, linux-mm, linux-nfs, linux-doc
On Mon 15-07-24 08:48:58, Jeff Layton wrote:
> Enable multigrain timestamps, which should ensure that there is an
> apparent change to the timestamp whenever it has been written after
> being actively observed via getattr.
>
> For ext4, we only need to enable the FS_MGTIME flag.
>
> Reviewed-by: Josef Bacik <josef@toxicpanda.com>
> Signed-off-by: Jeff Layton <jlayton@kernel.org>
You, I don't think anything else is needed. Feel free to add:
Reviewed-by: Jan Kara <jack@suse.cz>
Honza
> ---
> fs/ext4/super.c | 2 +-
> 1 file changed, 1 insertion(+), 1 deletion(-)
>
> diff --git a/fs/ext4/super.c b/fs/ext4/super.c
> index eb899628e121..95d4d7c0957a 100644
> --- a/fs/ext4/super.c
> +++ b/fs/ext4/super.c
> @@ -7294,7 +7294,7 @@ static struct file_system_type ext4_fs_type = {
> .init_fs_context = ext4_init_fs_context,
> .parameters = ext4_param_specs,
> .kill_sb = ext4_kill_sb,
> - .fs_flags = FS_REQUIRES_DEV | FS_ALLOW_IDMAP,
> + .fs_flags = FS_REQUIRES_DEV | FS_ALLOW_IDMAP | FS_MGTIME,
> };
> MODULE_ALIAS_FS("ext4");
>
>
> --
> 2.45.2
>
--
Jan Kara <jack@suse.com>
SUSE Labs, CR
^ permalink raw reply [flat|nested] 32+ messages in thread
* Re: [PATCH v6 9/9] tmpfs: add support for multigrain timestamps
2024-07-15 12:49 ` [PATCH v6 9/9] tmpfs: add support for " Jeff Layton
@ 2024-07-17 11:34 ` Jan Kara
0 siblings, 0 replies; 32+ messages in thread
From: Jan Kara @ 2024-07-17 11:34 UTC (permalink / raw)
To: Jeff Layton
Cc: Alexander Viro, Christian Brauner, Jan Kara, Steven Rostedt,
Masami Hiramatsu, Mathieu Desnoyers, Chandan Babu R,
Darrick J. Wong, Theodore Ts'o, Andreas Dilger, Chris Mason,
Josef Bacik, David Sterba, Hugh Dickins, Andrew Morton,
Jonathan Corbet, Dave Chinner, Andi Kleen, Christoph Hellwig,
Uros Bizjak, Kent Overstreet, Arnd Bergmann, Randy Dunlap,
linux-fsdevel, linux-kernel, linux-trace-kernel, linux-xfs,
linux-ext4, linux-btrfs, linux-mm, linux-nfs, linux-doc
On Mon 15-07-24 08:49:00, Jeff Layton wrote:
> Enable multigrain timestamps, which should ensure that there is an
> apparent change to the timestamp whenever it has been written after
> being actively observed via getattr.
>
> tmpfs only requires the FS_MGTIME flag.
>
> Reviewed-by: Josef Bacik <josef@toxicpanda.com>
> Signed-off-by: Jeff Layton <jlayton@kernel.org>
Looks good to me. Feel free to add:
Reviewed-by: Jan Kara <jack@suse.cz>
Honza
> ---
> mm/shmem.c | 2 +-
> 1 file changed, 1 insertion(+), 1 deletion(-)
>
> diff --git a/mm/shmem.c b/mm/shmem.c
> index 7f2b609945a5..75a9a73a769f 100644
> --- a/mm/shmem.c
> +++ b/mm/shmem.c
> @@ -4660,7 +4660,7 @@ static struct file_system_type shmem_fs_type = {
> .parameters = shmem_fs_parameters,
> #endif
> .kill_sb = kill_litter_super,
> - .fs_flags = FS_USERNS_MOUNT | FS_ALLOW_IDMAP,
> + .fs_flags = FS_USERNS_MOUNT | FS_ALLOW_IDMAP | FS_MGTIME,
> };
>
> void __init shmem_init(void)
>
> --
> 2.45.2
>
--
Jan Kara <jack@suse.com>
SUSE Labs, CR
^ permalink raw reply [flat|nested] 32+ messages in thread
* Re: [PATCH v6 5/9] Documentation: add a new file documenting multigrain timestamps
2024-07-17 11:31 ` Jan Kara
@ 2024-07-17 12:02 ` Jeff Layton
0 siblings, 0 replies; 32+ messages in thread
From: Jeff Layton @ 2024-07-17 12:02 UTC (permalink / raw)
To: Jan Kara
Cc: Alexander Viro, Christian Brauner, Steven Rostedt,
Masami Hiramatsu, Mathieu Desnoyers, Chandan Babu R,
Darrick J. Wong, Theodore Ts'o, Andreas Dilger, Chris Mason,
Josef Bacik, David Sterba, Hugh Dickins, Andrew Morton,
Jonathan Corbet, Dave Chinner, Andi Kleen, Christoph Hellwig,
Uros Bizjak, Kent Overstreet, Arnd Bergmann, Randy Dunlap,
linux-fsdevel, linux-kernel, linux-trace-kernel, linux-xfs,
linux-ext4, linux-btrfs, linux-mm, linux-nfs, linux-doc
On Wed, 2024-07-17 at 13:31 +0200, Jan Kara wrote:
> On Mon 15-07-24 08:48:56, Jeff Layton wrote:
> > Add a high-level document that describes how multigrain timestamps work,
> > rationale for them, and some info about implementation and tradeoffs.
> >
> > Reviewed-by: Josef Bacik <josef@toxicpanda.com>
> > Signed-off-by: Jeff Layton <jlayton@kernel.org>
>
> One comment below. With that fixed feel free to add:
>
> Reviewed-by: Jan Kara <jack@suse.cz>
>
> > +Implementation Notes
> > +====================
> > +Multigrain timestamps are intended for use by local filesystems that get
> > +ctime values from the local clock. This is in contrast to network filesystems
> > +and the like that just mirror timestamp values from a server.
> > +
> > +For most filesystems, it's sufficient to just set the FS_MGTIME flag in the
> > +fstype->fs_flags in order to opt-in, providing the ctime is only ever set via
> > +inode_set_ctime_current(). If the filesystem has a ->getattr routine that
> > +doesn't call generic_fillattr, then you should have it call fill_mg_cmtime to
> > +fill those values.
>
> I think you should explicitely mention that ->setattr() implementation
> needs to use setattr_copy() or otherwise mimic its behavior...
>
> Honza
I've added a sentence like you suggest to the patch in my tree. Thanks
for all the reviews!
--
Jeff Layton <jlayton@kernel.org>
^ permalink raw reply [flat|nested] 32+ messages in thread
* Re: [PATCH v6 0/9] fs: multigrain timestamp redux
2024-07-16 12:45 ` Jeff Layton
@ 2024-07-22 15:30 ` Christian Brauner
0 siblings, 0 replies; 32+ messages in thread
From: Christian Brauner @ 2024-07-22 15:30 UTC (permalink / raw)
To: Jeff Layton
Cc: Alexander Viro, Jan Kara, Steven Rostedt, Masami Hiramatsu,
Mathieu Desnoyers, Chandan Babu R, Darrick J. Wong,
Theodore Ts'o, Andreas Dilger, Chris Mason, Josef Bacik,
David Sterba, Hugh Dickins, Andrew Morton, Jonathan Corbet,
Dave Chinner, Andi Kleen, Christoph Hellwig, Uros Bizjak,
Kent Overstreet, Arnd Bergmann, Randy Dunlap, linux-fsdevel,
linux-kernel, linux-trace-kernel, linux-xfs, linux-ext4,
linux-btrfs, linux-mm, linux-nfs, linux-doc
On Tue, Jul 16, 2024 at 08:45:16AM GMT, Jeff Layton wrote:
> On Tue, 2024-07-16 at 09:37 +0200, Christian Brauner wrote:
> > On Mon, Jul 15, 2024 at 08:48:51AM GMT, Jeff Layton wrote:
> > > I think this is pretty much ready for linux-next now. Since the latest
> > > changes are pretty minimal, I've left the Reviewed-by's intact. It would
> > > be nice to have acks or reviews from maintainers for ext4 and tmpfs too.
> > >
> > > I did try to plumb this into bcachefs too, but the way it handles
> > > timestamps makes that pretty difficult. It keeps the active copies in an
> > > internal representation of the on-disk inode and periodically copies
> > > them to struct inode. This is backward from the way most blockdev
> > > filesystems do this.
> > >
> > > Christian, would you be willing to pick these up with an eye toward
> > > v6.12 after the merge window settles?
> >
> > Yup. About to queue it up. I'll try to find some time to go through it
> > so I might have some replies later but that shouldn't hold up linux-next
> > at all.
>
> Great!
>
> There is one minor update to the percpu counter patch to compile those
> out when debugfs isn't enabled, so it may be best to pick the series
> from the "mgtime" branch in my public git tree. Let me know if you'd
I did that now and pushed to vfs.mgtime. Please take a look as I rebased
onto current master and resolved conflicts in xfs and btrfs. Thanks!
^ permalink raw reply [flat|nested] 32+ messages in thread
* Re: [PATCH v6 1/9] fs: add infrastructure for multigrain timestamps
2024-07-15 12:48 ` [PATCH v6 1/9] fs: add infrastructure for multigrain timestamps Jeff Layton
2024-07-17 10:40 ` Jan Kara
@ 2024-08-08 22:09 ` Mateusz Guzik
2024-08-09 0:00 ` Jeff Layton
2024-08-08 23:43 ` Mateusz Guzik
2 siblings, 1 reply; 32+ messages in thread
From: Mateusz Guzik @ 2024-08-08 22:09 UTC (permalink / raw)
To: Jeff Layton
Cc: Alexander Viro, Christian Brauner, Jan Kara, linux-fsdevel,
linux-kernel
On Mon, Jul 15, 2024 at 08:48:52AM -0400, Jeff Layton wrote:
> diff --git a/fs/stat.c b/fs/stat.c
> index 6f65b3456cad..df7fdd3afed9 100644
> --- a/fs/stat.c
> +++ b/fs/stat.c
> @@ -26,6 +26,32 @@
> #include "internal.h"
> #include "mount.h"
>
> +/**
> + * fill_mg_cmtime - Fill in the mtime and ctime and flag ctime as QUERIED
> + * @stat: where to store the resulting values
> + * @request_mask: STATX_* values requested
> + * @inode: inode from which to grab the c/mtime
> + *
> + * Given @inode, grab the ctime and mtime out if it and store the result
> + * in @stat. When fetching the value, flag it as queried so the next write
> + * will ensure a distinct timestamp.
> + */
> +void fill_mg_cmtime(struct kstat *stat, u32 request_mask, struct inode *inode)
> +{
> + atomic_t *pcn = (atomic_t *)&inode->i_ctime_nsec;
> +
> + /* If neither time was requested, then don't report them */
> + if (!(request_mask & (STATX_CTIME|STATX_MTIME))) {
> + stat->result_mask &= ~(STATX_CTIME|STATX_MTIME);
> + return;
> + }
> +
> + stat->mtime = inode_get_mtime(inode);
> + stat->ctime.tv_sec = inode->i_ctime_sec;
> + stat->ctime.tv_nsec = ((u32)atomic_fetch_or(I_CTIME_QUERIED, pcn)) & ~I_CTIME_QUERIED;
> +}
> +EXPORT_SYMBOL(fill_mg_cmtime);
> +
[trimmed the ginormous CC]
This performs the atomic every time (as in it sets the flag even if it
was already set), serializing all fstats and reducing scalability of
stat of the same file.
Bare minimum it should be conditional -- if the flag is already set,
don't dirty anything.
Even that aside adding an atomic to stat is a bummer, but off hand I
don't have a good solution for that.
Anyhow, this being in -next, perhaps the conditional dirty can be
massaged into the thing as present? There are some cosmetic choices to
be made how to express, may be the fastest if you guys just augment it
however you see fit.
If not I can submit a patch tomorrow.
^ permalink raw reply [flat|nested] 32+ messages in thread
* Re: [PATCH v6 1/9] fs: add infrastructure for multigrain timestamps
2024-07-15 12:48 ` [PATCH v6 1/9] fs: add infrastructure for multigrain timestamps Jeff Layton
2024-07-17 10:40 ` Jan Kara
2024-08-08 22:09 ` Mateusz Guzik
@ 2024-08-08 23:43 ` Mateusz Guzik
2024-08-09 0:29 ` Jeff Layton
2 siblings, 1 reply; 32+ messages in thread
From: Mateusz Guzik @ 2024-08-08 23:43 UTC (permalink / raw)
To: Jeff Layton
Cc: Alexander Viro, Christian Brauner, Jan Kara, linux-fsdevel,
linux-kernel
On Mon, Jul 15, 2024 at 08:48:52AM -0400, Jeff Layton wrote:
> /**
> * inode_set_ctime_current - set the ctime to current_time
> * @inode: inode
> *
> - * Set the inode->i_ctime to the current value for the inode. Returns
> - * the current value that was assigned to i_ctime.
> + * Set the inode's ctime to the current value for the inode. Returns the
> + * current value that was assigned. If this is not a multigrain inode, then we
> + * just set it to whatever the coarse_ctime is.
> + *
> + * If it is multigrain, then we first see if the coarse-grained timestamp is
> + * distinct from what we have. If so, then we'll just use that. If we have to
> + * get a fine-grained timestamp, then do so, and try to swap it into the floor.
> + * We accept the new floor value regardless of the outcome of the cmpxchg.
> + * After that, we try to swap the new value into i_ctime_nsec. Again, we take
> + * the resulting ctime, regardless of the outcome of the swap.
> */
> struct timespec64 inode_set_ctime_current(struct inode *inode)
> {
> - struct timespec64 now = current_time(inode);
> + ktime_t now, floor = atomic64_read(&ctime_floor);
> + struct timespec64 now_ts;
> + u32 cns, cur;
> +
> + now = coarse_ctime(floor);
> +
> + /* Just return that if this is not a multigrain fs */
> + if (!is_mgtime(inode)) {
> + now_ts = timestamp_truncate(ktime_to_timespec64(now), inode);
> + inode_set_ctime_to_ts(inode, now_ts);
> + goto out;
> + }
> +
> + /*
> + * We only need a fine-grained time if someone has queried it,
> + * and the current coarse grained time isn't later than what's
> + * already there.
> + */
> + cns = smp_load_acquire(&inode->i_ctime_nsec);
> + if (cns & I_CTIME_QUERIED) {
> + ktime_t ctime = ktime_set(inode->i_ctime_sec, cns & ~I_CTIME_QUERIED);
> +
> + if (!ktime_after(now, ctime)) {
> + ktime_t old, fine;
> +
> + /* Get a fine-grained time */
> + fine = ktime_get();
>
> - inode_set_ctime_to_ts(inode, now);
> - return now;
> + /*
> + * If the cmpxchg works, we take the new floor value. If
> + * not, then that means that someone else changed it after we
> + * fetched it but before we got here. That value is just
> + * as good, so keep it.
> + */
> + old = floor;
> + if (!atomic64_try_cmpxchg(&ctime_floor, &old, fine))
> + fine = old;
> + now = ktime_mono_to_real(fine);
> + }
> + }
> + now_ts = timestamp_truncate(ktime_to_timespec64(now), inode);
> + cur = cns;
> +
> + /* No need to cmpxchg if it's exactly the same */
> + if (cns == now_ts.tv_nsec && inode->i_ctime_sec == now_ts.tv_sec)
> + goto out;
> +retry:
> + /* Try to swap the nsec value into place. */
> + if (try_cmpxchg(&inode->i_ctime_nsec, &cur, now_ts.tv_nsec)) {
> + /* If swap occurred, then we're (mostly) done */
> + inode->i_ctime_sec = now_ts.tv_sec;
Linux always had rather lax approach to consistency of getattr results
and I wonder if with this patchset it is no longer viable.
Ignoring the flag, suppose ctime on the inode is { nsec = 12, sec = 1 },
while the new timestamp is { nsec = 1, sec = 2 }
The current update method results in a transient state where { nsec = 1,
sec = 1 }. But this represents an earlier point in time.
Thus a thread which observed the first state and spotted the transient
value in the second one is going to conclude time went backwards. Is
this considered fine given what the multigrain stuff is trying to
accomplish?
As for fixing this, off hand I note there is a 4-byte hole in struct
inode, just enough to store a sequence counter which fill_mg_cmtime
could use to safely read the sec/nsec pair. The write side would take
the inode spinlock.
> + } else {
> + /*
> + * Was the change due to someone marking the old ctime QUERIED?
> + * If so then retry the swap. This can only happen once since
> + * the only way to clear I_CTIME_QUERIED is to stamp the inode
> + * with a new ctime.
> + */
> + if (!(cns & I_CTIME_QUERIED) && (cns | I_CTIME_QUERIED) == cur) {
> + cns = cur;
> + goto retry;
> + }
> + /* Otherwise, keep the existing ctime */
> + now_ts.tv_sec = inode->i_ctime_sec;
> + now_ts.tv_nsec = cur & ~I_CTIME_QUERIED;
> + }
> +out:
> + return now_ts;
> }
> EXPORT_SYMBOL(inode_set_ctime_current);
^ permalink raw reply [flat|nested] 32+ messages in thread
* Re: [PATCH v6 1/9] fs: add infrastructure for multigrain timestamps
2024-08-08 22:09 ` Mateusz Guzik
@ 2024-08-09 0:00 ` Jeff Layton
0 siblings, 0 replies; 32+ messages in thread
From: Jeff Layton @ 2024-08-09 0:00 UTC (permalink / raw)
To: Mateusz Guzik
Cc: Alexander Viro, Christian Brauner, Jan Kara, linux-fsdevel,
linux-kernel
On Fri, 2024-08-09 at 00:09 +0200, Mateusz Guzik wrote:
> On Mon, Jul 15, 2024 at 08:48:52AM -0400, Jeff Layton wrote:
> > diff --git a/fs/stat.c b/fs/stat.c
> > index 6f65b3456cad..df7fdd3afed9 100644
> > --- a/fs/stat.c
> > +++ b/fs/stat.c
> > @@ -26,6 +26,32 @@
> > #include "internal.h"
> > #include "mount.h"
> >
> > +/**
> > + * fill_mg_cmtime - Fill in the mtime and ctime and flag ctime as QUERIED
> > + * @stat: where to store the resulting values
> > + * @request_mask: STATX_* values requested
> > + * @inode: inode from which to grab the c/mtime
> > + *
> > + * Given @inode, grab the ctime and mtime out if it and store the result
> > + * in @stat. When fetching the value, flag it as queried so the next write
> > + * will ensure a distinct timestamp.
> > + */
> > +void fill_mg_cmtime(struct kstat *stat, u32 request_mask, struct inode *inode)
> > +{
> > + atomic_t *pcn = (atomic_t *)&inode->i_ctime_nsec;
> > +
> > + /* If neither time was requested, then don't report them */
> > + if (!(request_mask & (STATX_CTIME|STATX_MTIME))) {
> > + stat->result_mask &= ~(STATX_CTIME|STATX_MTIME);
> > + return;
> > + }
> > +
> > + stat->mtime = inode_get_mtime(inode);
> > + stat->ctime.tv_sec = inode->i_ctime_sec;
> > + stat->ctime.tv_nsec = ((u32)atomic_fetch_or(I_CTIME_QUERIED, pcn)) & ~I_CTIME_QUERIED;
> > +}
> > +EXPORT_SYMBOL(fill_mg_cmtime);
> > +
>
> [trimmed the ginormous CC]
>
> This performs the atomic every time (as in it sets the flag even if it
> was already set), serializing all fstats and reducing scalability of
> stat of the same file.
>
> Bare minimum it should be conditional -- if the flag is already set,
> don't dirty anything.
>
> Even that aside adding an atomic to stat is a bummer, but off hand I
> don't have a good solution for that.
>
> Anyhow, this being in -next, perhaps the conditional dirty can be
> massaged into the thing as present? There are some cosmetic choices to
> be made how to express, may be the fastest if you guys just augment it
> however you see fit.
>
> If not I can submit a patch tomorrow.
I'm fine with that change. It should simple enough.
--
Jeff Layton <jlayton@kernel.org>
^ permalink raw reply [flat|nested] 32+ messages in thread
* Re: [PATCH v6 1/9] fs: add infrastructure for multigrain timestamps
2024-08-08 23:43 ` Mateusz Guzik
@ 2024-08-09 0:29 ` Jeff Layton
0 siblings, 0 replies; 32+ messages in thread
From: Jeff Layton @ 2024-08-09 0:29 UTC (permalink / raw)
To: Mateusz Guzik
Cc: Alexander Viro, Christian Brauner, Jan Kara, linux-fsdevel,
linux-kernel
On Fri, 2024-08-09 at 01:43 +0200, Mateusz Guzik wrote:
> On Mon, Jul 15, 2024 at 08:48:52AM -0400, Jeff Layton wrote:
> > /**
> > * inode_set_ctime_current - set the ctime to current_time
> > * @inode: inode
> > *
> > - * Set the inode->i_ctime to the current value for the inode. Returns
> > - * the current value that was assigned to i_ctime.
> > + * Set the inode's ctime to the current value for the inode. Returns the
> > + * current value that was assigned. If this is not a multigrain inode, then we
> > + * just set it to whatever the coarse_ctime is.
> > + *
> > + * If it is multigrain, then we first see if the coarse-grained timestamp is
> > + * distinct from what we have. If so, then we'll just use that. If we have to
> > + * get a fine-grained timestamp, then do so, and try to swap it into the floor.
> > + * We accept the new floor value regardless of the outcome of the cmpxchg.
> > + * After that, we try to swap the new value into i_ctime_nsec. Again, we take
> > + * the resulting ctime, regardless of the outcome of the swap.
> > */
> > struct timespec64 inode_set_ctime_current(struct inode *inode)
> > {
> > - struct timespec64 now = current_time(inode);
> > + ktime_t now, floor = atomic64_read(&ctime_floor);
> > + struct timespec64 now_ts;
> > + u32 cns, cur;
> > +
> > + now = coarse_ctime(floor);
> > +
> > + /* Just return that if this is not a multigrain fs */
> > + if (!is_mgtime(inode)) {
> > + now_ts = timestamp_truncate(ktime_to_timespec64(now), inode);
> > + inode_set_ctime_to_ts(inode, now_ts);
> > + goto out;
> > + }
> > +
> > + /*
> > + * We only need a fine-grained time if someone has queried it,
> > + * and the current coarse grained time isn't later than what's
> > + * already there.
> > + */
> > + cns = smp_load_acquire(&inode->i_ctime_nsec);
> > + if (cns & I_CTIME_QUERIED) {
> > + ktime_t ctime = ktime_set(inode->i_ctime_sec, cns & ~I_CTIME_QUERIED);
> > +
> > + if (!ktime_after(now, ctime)) {
> > + ktime_t old, fine;
> > +
> > + /* Get a fine-grained time */
> > + fine = ktime_get();
> >
> > - inode_set_ctime_to_ts(inode, now);
> > - return now;
> > + /*
> > + * If the cmpxchg works, we take the new floor value. If
> > + * not, then that means that someone else changed it after we
> > + * fetched it but before we got here. That value is just
> > + * as good, so keep it.
> > + */
> > + old = floor;
> > + if (!atomic64_try_cmpxchg(&ctime_floor, &old, fine))
> > + fine = old;
> > + now = ktime_mono_to_real(fine);
> > + }
> > + }
> > + now_ts = timestamp_truncate(ktime_to_timespec64(now), inode);
> > + cur = cns;
> > +
> > + /* No need to cmpxchg if it's exactly the same */
> > + if (cns == now_ts.tv_nsec && inode->i_ctime_sec == now_ts.tv_sec)
> > + goto out;
> > +retry:
> > + /* Try to swap the nsec value into place. */
> > + if (try_cmpxchg(&inode->i_ctime_nsec, &cur, now_ts.tv_nsec)) {
> > + /* If swap occurred, then we're (mostly) done */
> > + inode->i_ctime_sec = now_ts.tv_sec;
>
>
> Linux always had rather lax approach to consistency of getattr results
> and I wonder if with this patchset it is no longer viable.
>
> Ignoring the flag, suppose ctime on the inode is { nsec = 12, sec = 1 },
> while the new timestamp is { nsec = 1, sec = 2 }
>
> The current update method results in a transient state where { nsec = 1,
> sec = 1 }. But this represents an earlier point in time.
>
> Thus a thread which observed the first state and spotted the transient
> value in the second one is going to conclude time went backwards. Is
> this considered fine given what the multigrain stuff is trying to
> accomplish?
>
Yes, I think so.
> As for fixing this, off hand I note there is a 4-byte hole in struct
> inode, just enough to store a sequence counter which fill_mg_cmtime
> could use to safely read the sec/nsec pair. The write side would take
> the inode spinlock.
>
Note that this is also a problem today with always-coarse timestamps.
We track timestamps in two separate words, and "torn reads" are always
a possibility. I suspect it happens occasionally and we just never
notice.
The main goal with the multigrain series was to make sure that
measuring the ctime of the same inode on both sides of a change always
shows a change in value. I think we'll still achieve that here, even
with a torn read (unless we're just exceptionally unlucky).
As far as the ordering of timestamps between two different files; this
patchset doesn't make that any worse.
I did have an earlier version of this patchset that converted the
i_ctime fields to a ktime_t. That would have solved this problem too,
but it had other drawbacks. We could reconsider that though.
In any case, I think that's really a separate project from the
multigrain work. Given that no one has complained about torn reads so
far, I wouldn't bother at this point.
> > + } else {
> > + /*
> > + * Was the change due to someone marking the old ctime QUERIED?
> > + * If so then retry the swap. This can only happen once since
> > + * the only way to clear I_CTIME_QUERIED is to stamp the inode
> > + * with a new ctime.
> > + */
> > + if (!(cns & I_CTIME_QUERIED) && (cns | I_CTIME_QUERIED) == cur) {
> > + cns = cur;
> > + goto retry;
> > + }
> > + /* Otherwise, keep the existing ctime */
> > + now_ts.tv_sec = inode->i_ctime_sec;
> > + now_ts.tv_nsec = cur & ~I_CTIME_QUERIED;
> > + }
> > +out:
> > + return now_ts;
> > }
> > EXPORT_SYMBOL(inode_set_ctime_current);
--
Jeff Layton <jlayton@kernel.org>
^ permalink raw reply [flat|nested] 32+ messages in thread
end of thread, other threads:[~2024-08-09 0:29 UTC | newest]
Thread overview: 32+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2024-07-15 12:48 [PATCH v6 0/9] fs: multigrain timestamp redux Jeff Layton
2024-07-15 12:48 ` [PATCH v6 1/9] fs: add infrastructure for multigrain timestamps Jeff Layton
2024-07-17 10:40 ` Jan Kara
2024-08-08 22:09 ` Mateusz Guzik
2024-08-09 0:00 ` Jeff Layton
2024-08-08 23:43 ` Mateusz Guzik
2024-08-09 0:29 ` Jeff Layton
2024-07-15 12:48 ` [PATCH v6 2/9] fs: tracepoints around multigrain timestamp events Jeff Layton
2024-07-15 18:29 ` Darrick J. Wong
2024-07-17 10:41 ` Jan Kara
2024-07-15 12:48 ` [PATCH v6 3/9] fs: add percpu counters for significant " Jeff Layton
2024-07-15 18:32 ` Darrick J. Wong
2024-07-15 19:53 ` Jeff Layton
2024-07-15 20:03 ` Darrick J. Wong
2024-07-17 10:45 ` Jan Kara
2024-07-15 12:48 ` [PATCH v6 4/9] fs: have setattr_copy handle multigrain timestamps appropriately Jeff Layton
2024-07-17 11:24 ` Jan Kara
2024-07-15 12:48 ` [PATCH v6 5/9] Documentation: add a new file documenting multigrain timestamps Jeff Layton
2024-07-15 18:45 ` Darrick J. Wong
2024-07-17 6:00 ` Randy Dunlap
2024-07-17 11:31 ` Jan Kara
2024-07-17 12:02 ` Jeff Layton
2024-07-15 12:48 ` [PATCH v6 6/9] xfs: switch to " Jeff Layton
2024-07-15 18:47 ` Darrick J. Wong
2024-07-15 12:48 ` [PATCH v6 7/9] ext4: " Jeff Layton
2024-07-17 11:32 ` Jan Kara
2024-07-15 12:48 ` [PATCH v6 8/9] btrfs: convert " Jeff Layton
2024-07-15 12:49 ` [PATCH v6 9/9] tmpfs: add support for " Jeff Layton
2024-07-17 11:34 ` Jan Kara
2024-07-16 7:37 ` [PATCH v6 0/9] fs: multigrain timestamp redux Christian Brauner
2024-07-16 12:45 ` Jeff Layton
2024-07-22 15:30 ` Christian Brauner
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).