Add snapshots_changed_nsecs dataset property

Add a read-only dataset property, snapshots_changed_nsecs, which 
exposes the nanosecond resolution version of snapshots_changed.

Reviewed-by: Brian Behlendorf <behlendorf1@llnl.gov>
Reviewed-by: Alexander Motin <alexander.motin@TrueNAS.com>
Signed-off-by: Wolfgang Hoschek <wolfgang.hoschek@mac.com>
Closes #17998
Closes #18031
This commit is contained in:
Wolfgang Hoschek 2026-01-06 12:36:20 -05:00 committed by GitHub
parent 6eef5cdc94
commit c77f17b750
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 89 additions and 11 deletions

View File

@ -203,6 +203,7 @@ typedef enum {
ZFS_PROP_DEFAULTUSEROBJQUOTA, ZFS_PROP_DEFAULTUSEROBJQUOTA,
ZFS_PROP_DEFAULTGROUPOBJQUOTA, ZFS_PROP_DEFAULTGROUPOBJQUOTA,
ZFS_PROP_DEFAULTPROJECTOBJQUOTA, ZFS_PROP_DEFAULTPROJECTOBJQUOTA,
ZFS_PROP_SNAPSHOTS_CHANGED_NSECS,
ZFS_NUM_PROPS ZFS_NUM_PROPS
} zfs_prop_t; } zfs_prop_t;

View File

@ -2311,7 +2311,8 @@
<enumerator name='ZFS_PROP_DEFAULTUSEROBJQUOTA' value='103'/> <enumerator name='ZFS_PROP_DEFAULTUSEROBJQUOTA' value='103'/>
<enumerator name='ZFS_PROP_DEFAULTGROUPOBJQUOTA' value='104'/> <enumerator name='ZFS_PROP_DEFAULTGROUPOBJQUOTA' value='104'/>
<enumerator name='ZFS_PROP_DEFAULTPROJECTOBJQUOTA' value='105'/> <enumerator name='ZFS_PROP_DEFAULTPROJECTOBJQUOTA' value='105'/>
<enumerator name='ZFS_NUM_PROPS' value='106'/> <enumerator name='ZFS_PROP_SNAPSHOTS_CHANGED_NSECS' value='106'/>
<enumerator name='ZFS_NUM_PROPS' value='107'/>
</enum-decl> </enum-decl>
<typedef-decl name='zfs_prop_t' type-id='4b000d60' id='58603c44'/> <typedef-decl name='zfs_prop_t' type-id='4b000d60' id='58603c44'/>
<enum-decl name='zprop_source_t' naming-typedef-id='a2256d42' id='5903f80e'> <enum-decl name='zprop_source_t' naming-typedef-id='a2256d42' id='5903f80e'>

View File

@ -3001,6 +3001,19 @@ zfs_prop_get(zfs_handle_t *zhp, zfs_prop_t prop, char *propbuf, size_t proplen,
zcp_check(zhp, prop, val, NULL); zcp_check(zhp, prop, val, NULL);
break; break;
case ZFS_PROP_SNAPSHOTS_CHANGED_NSECS:
{
if ((get_numeric_property(zhp, prop, src, &source,
&val) != 0) || val == 0) {
return (-1);
}
(void) snprintf(propbuf, proplen, "%llu",
(u_longlong_t)val);
}
zcp_check(zhp, prop, val, NULL);
break;
default: default:
switch (zfs_prop_get_type(prop)) { switch (zfs_prop_get_type(prop)) {
case PROP_TYPE_NUMBER: case PROP_TYPE_NUMBER:

View File

@ -535,6 +535,15 @@ This allows us to be more efficient how often we query snapshots.
The property is persistent across mount and unmount operations only if the The property is persistent across mount and unmount operations only if the
.Sy extensible_dataset .Sy extensible_dataset
feature is enabled. feature is enabled.
.It Sy snapshots_changed_nsecs
Specifies the UTC time at which a snapshot for a dataset was last created
or deleted, expressed as the number of nanoseconds since the Unix epoch.
This is a high-precision version of
.Sy snapshots_changed ,
representing the same instant with nanosecond instead of second resolution.
The property is persistent across mount and unmount operations only if the
.Sy extensible_dataset
feature is enabled.
.It Sy volblocksize .It Sy volblocksize
For volumes, specifies the block size of the volume. For volumes, specifies the block size of the volume.
The The

View File

@ -796,6 +796,12 @@ zfs_prop_init(void)
ZFS_TYPE_VOLUME, "<date>", "SNAPSHOTS_CHANGED", B_FALSE, B_TRUE, ZFS_TYPE_VOLUME, "<date>", "SNAPSHOTS_CHANGED", B_FALSE, B_TRUE,
B_TRUE, NULL, sfeatures); B_TRUE, NULL, sfeatures);
zprop_register_impl(ZFS_PROP_SNAPSHOTS_CHANGED_NSECS,
"snapshots_changed_nsecs", PROP_TYPE_NUMBER, 0, NULL,
PROP_READONLY, ZFS_TYPE_FILESYSTEM | ZFS_TYPE_VOLUME, "<nsec>",
"SNAPSHOTS_CHANGED_NSECS", B_FALSE, B_TRUE, B_TRUE, NULL,
sfeatures);
zprop_register_index(ZFS_PROP_LONGNAME, "longname", 0, PROP_INHERIT, zprop_register_index(ZFS_PROP_LONGNAME, "longname", 0, PROP_INHERIT,
ZFS_TYPE_FILESYSTEM, "on | off", "LONGNAME", boolean_table, ZFS_TYPE_FILESYSTEM, "on | off", "LONGNAME", boolean_table,
sfeatures); sfeatures);

View File

@ -2855,8 +2855,14 @@ dsl_dataset_stats(dsl_dataset_t *ds, nvlist_t *nv)
dsl_get_userrefs(ds)); dsl_get_userrefs(ds));
dsl_prop_nvlist_add_uint64(nv, ZFS_PROP_DEFER_DESTROY, dsl_prop_nvlist_add_uint64(nv, ZFS_PROP_DEFER_DESTROY,
dsl_get_defer_destroy(ds)); dsl_get_defer_destroy(ds));
inode_timespec_t snap_cmtime = dsl_dir_snap_cmtime(ds->ds_dir);
dsl_prop_nvlist_add_uint64(nv, ZFS_PROP_SNAPSHOTS_CHANGED, dsl_prop_nvlist_add_uint64(nv, ZFS_PROP_SNAPSHOTS_CHANGED,
dsl_dir_snap_cmtime(ds->ds_dir).tv_sec); snap_cmtime.tv_sec);
uint64_t snap_cmtime_ns =
((uint64_t)snap_cmtime.tv_sec * NANOSEC) +
snap_cmtime.tv_nsec;
dsl_prop_nvlist_add_uint64(nv, ZFS_PROP_SNAPSHOTS_CHANGED_NSECS,
snap_cmtime_ns);
dsl_dataset_crypt_stats(ds, nv); dsl_dataset_crypt_stats(ds, nv);
if (dsl_dataset_phys(ds)->ds_prev_snap_obj != 0) { if (dsl_dataset_phys(ds)->ds_prev_snap_obj != 0) {

View File

@ -434,6 +434,14 @@ get_special_prop(lua_State *state, dsl_dataset_t *ds, const char *dsname,
numval = dsl_dir_snap_cmtime(ds->ds_dir).tv_sec; numval = dsl_dir_snap_cmtime(ds->ds_dir).tv_sec;
break; break;
case ZFS_PROP_SNAPSHOTS_CHANGED_NSECS: {
inode_timespec_t snap_cmtime =
dsl_dir_snap_cmtime(ds->ds_dir);
numval = ((uint64_t)snap_cmtime.tv_sec * NANOSEC) +
snap_cmtime.tv_nsec;
break;
}
default: default:
/* Did not match these props, check in the dsl_dir */ /* Did not match these props, check in the dsl_dir */
error = get_dsl_dir_prop(ds, zfs_prop, &numval); error = get_dsl_dir_prop(ds, zfs_prop, &numval);

View File

@ -30,22 +30,22 @@
# #
# DESCRIPTION: # DESCRIPTION:
# Verify the functionality of snapshots_changed property # Verify the functionality of snapshots_changed and snapshots_changed_nsecs properties.
# #
# STRATEGY: # STRATEGY:
# 1. Create a pool # 1. Create a pool
# 2. Verify snapshots_changed property is NULL # 2. Verify snapshots_changed and snapshots_changed_nsecs properties are NULL
# 3. Create a filesystem # 3. Create a filesystem
# 4. Verify snapshots_changed property is NULL # 4. Verify snapshots_changed and snapshots_changed_nsecs properties are NULL
# 5. Create snapshots for all filesystems # 5. Create snapshots for all filesystems
# 6. Verify snapshots_changed property shows correct time # 6. Verify snapshots_changed property shows correct time and snapshots_changed_nsecs is a valid nanosecond value
# 7. Unmount all filesystems # 7. Unmount all filesystems
# 8. Create a snapshot while unmounted # 8. Create a snapshot while unmounted
# 9. Verify snapshots_changed # 9. Verify snapshots_changed and snapshots_changed_nsecs
# 10. Mount the filsystems # 10. Mount the filsystems
# 11. Verify snapshots_changed # 11. Verify snapshots_changed and snapshots_changed_nsecs
# 12. Destroy the snapshots # 12. Destroy the snapshots
# 13. Verify snapshots_changed # 13. Verify snapshots_changed and snapshots_changed_nsecs
# #
function cleanup function cleanup
@ -55,7 +55,7 @@ function cleanup
verify_runnable "both" verify_runnable "both"
log_assert "Verify snapshots_changed property" log_assert "Verify snapshots_changed and snapshots_changed_nsecs properties"
log_onexit cleanup log_onexit cleanup
@ -67,13 +67,25 @@ snapdir=".zfs/snapshot"
# Create filesystems and check snapshots_changed is NULL # Create filesystems and check snapshots_changed is NULL
create_pool $TESTPOOL $DISKS create_pool $TESTPOOL $DISKS
snap_changed_testpool=$(zfs get -H -o value -p snapshots_changed $TESTPOOL) snap_changed_testpool=$(zfs get -H -o value -p snapshots_changed $TESTPOOL)
snap_changed_nsecs_testpool=$(zfs get -H -o value -p snapshots_changed_nsecs $TESTPOOL)
log_must eval "[[ $snap_changed_testpool == - ]]" log_must eval "[[ $snap_changed_testpool == - ]]"
log_must eval "[[ $snap_changed_nsecs_testpool == - ]]"
list_changed_testpool=$(zfs list -H -p -o snapshots_changed $TESTPOOL)
list_changed_nsecs_testpool=$(zfs list -H -p -o snapshots_changed_nsecs $TESTPOOL)
log_must eval "[[ $list_changed_testpool == - ]]"
log_must eval "[[ $list_changed_nsecs_testpool == - ]]"
tpool_snapdir=$(get_prop mountpoint $TESTPOOL)/$snapdir tpool_snapdir=$(get_prop mountpoint $TESTPOOL)/$snapdir
log_must eval "[[ $(stat_mtime $tpool_snapdir) == 0 ]]" log_must eval "[[ $(stat_mtime $tpool_snapdir) == 0 ]]"
log_must zfs create $TESTPOOL/$TESTFS log_must zfs create $TESTPOOL/$TESTFS
snap_changed_testfs=$(zfs get -H -o value -p snapshots_changed $TESTPOOL/$TESTFS) snap_changed_testfs=$(zfs get -H -o value -p snapshots_changed $TESTPOOL/$TESTFS)
snap_changed_nsecs_testfs=$(zfs get -H -o value -p snapshots_changed_nsecs $TESTPOOL/$TESTFS)
log_must eval "[[ $snap_changed_testfs == - ]]" log_must eval "[[ $snap_changed_testfs == - ]]"
log_must eval "[[ $snap_changed_nsecs_testfs == - ]]"
list_changed_testfs=$(zfs list -H -p -o snapshots_changed $TESTPOOL/$TESTFS)
list_changed_nsecs_testfs=$(zfs list -H -p -o snapshots_changed_nsecs $TESTPOOL/$TESTFS)
log_must eval "[[ $list_changed_testfs == - ]]"
log_must eval "[[ $list_changed_nsecs_testfs == - ]]"
tfs_snapdir=$(get_prop mountpoint $TESTPOOL/$TESTFS)/$snapdir tfs_snapdir=$(get_prop mountpoint $TESTPOOL/$TESTFS)/$snapdir
log_must eval "[[ $(stat_mtime $tfs_snapdir) == 0 ]]" log_must eval "[[ $(stat_mtime $tfs_snapdir) == 0 ]]"
@ -81,48 +93,70 @@ log_must eval "[[ $(stat_mtime $tfs_snapdir) == 0 ]]"
curr_time=$(date '+%s') curr_time=$(date '+%s')
log_must zfs snapshot $snap_testpool log_must zfs snapshot $snap_testpool
snap_changed_testpool=$(zfs get -H -o value -p snapshots_changed $TESTPOOL) snap_changed_testpool=$(zfs get -H -o value -p snapshots_changed $TESTPOOL)
snap_changed_nsecs_testpool=$(zfs get -H -o value -p snapshots_changed_nsecs $TESTPOOL)
log_must eval "[[ $snap_changed_testpool -ge $curr_time ]]" log_must eval "[[ $snap_changed_testpool -ge $curr_time ]]"
log_must eval "[[ $((snap_changed_nsecs_testpool / 1000000000)) == $snap_changed_testpool ]]"
list_changed_testpool=$(zfs list -H -p -o snapshots_changed $TESTPOOL)
list_changed_nsecs_testpool=$(zfs list -H -p -o snapshots_changed_nsecs $TESTPOOL)
log_must eval "[[ $list_changed_testpool == $snap_changed_testpool ]]"
log_must eval "[[ $list_changed_nsecs_testpool == $snap_changed_nsecs_testpool ]]"
log_must eval "[[ $(stat_mtime $tpool_snapdir) == $snap_changed_testpool ]]" log_must eval "[[ $(stat_mtime $tpool_snapdir) == $snap_changed_testpool ]]"
curr_time=$(date '+%s') curr_time=$(date '+%s')
log_must zfs snapshot $snap_testfsv1 log_must zfs snapshot $snap_testfsv1
snap_changed_testfs=$(zfs get -H -o value -p snapshots_changed $TESTPOOL/$TESTFS) snap_changed_testfs=$(zfs get -H -o value -p snapshots_changed $TESTPOOL/$TESTFS)
snap_changed_nsecs_testfs=$(zfs get -H -o value -p snapshots_changed_nsecs $TESTPOOL/$TESTFS)
log_must eval "[[ $snap_changed_testfs -ge $curr_time ]]" log_must eval "[[ $snap_changed_testfs -ge $curr_time ]]"
log_must eval "[[ $((snap_changed_nsecs_testfs / 1000000000)) == $snap_changed_testfs ]]"
list_changed_testfs=$(zfs list -H -p -o snapshots_changed $TESTPOOL/$TESTFS)
list_changed_nsecs_testfs=$(zfs list -H -p -o snapshots_changed_nsecs $TESTPOOL/$TESTFS)
log_must eval "[[ $list_changed_testfs == $snap_changed_testfs ]]"
log_must eval "[[ $list_changed_nsecs_testfs == $snap_changed_nsecs_testfs ]]"
log_must eval "[[ $(stat_mtime $tfs_snapdir) == $snap_changed_testfs ]]" log_must eval "[[ $(stat_mtime $tfs_snapdir) == $snap_changed_testfs ]]"
# Unmount the filesystems and check snapshots_changed has correct value after unmount # Unmount the filesystems and check snapshots_changed has correct value after unmount
log_must zfs unmount $TESTPOOL/$TESTFS log_must zfs unmount $TESTPOOL/$TESTFS
log_must eval "[[ $(zfs get -H -o value -p snapshots_changed $TESTPOOL/$TESTFS) == $snap_changed_testfs ]]" log_must eval "[[ $(zfs get -H -o value -p snapshots_changed $TESTPOOL/$TESTFS) == $snap_changed_testfs ]]"
log_must eval "[[ $(zfs get -H -o value -p snapshots_changed_nsecs $TESTPOOL/$TESTFS) == $snap_changed_nsecs_testfs ]]"
# Create snapshot while unmounted # Create snapshot while unmounted
curr_time=$(date '+%s') curr_time=$(date '+%s')
log_must zfs snapshot $snap_testfsv2 log_must zfs snapshot $snap_testfsv2
snap_changed_testfs=$(zfs get -H -o value -p snapshots_changed $TESTPOOL/$TESTFS) snap_changed_testfs=$(zfs get -H -o value -p snapshots_changed $TESTPOOL/$TESTFS)
snap_changed_nsecs_testfs=$(zfs get -H -o value -p snapshots_changed_nsecs $TESTPOOL/$TESTFS)
log_must eval "[[ $snap_changed_testfs -ge $curr_time ]]" log_must eval "[[ $snap_changed_testfs -ge $curr_time ]]"
log_must eval "[[ $((snap_changed_nsecs_testfs / 1000000000)) == $snap_changed_testfs ]]"
log_must zfs unmount $TESTPOOL log_must zfs unmount $TESTPOOL
log_must eval "[[ $(zfs get -H -o value -p snapshots_changed $TESTPOOL) == $snap_changed_testpool ]]" log_must eval "[[ $(zfs get -H -o value -p snapshots_changed $TESTPOOL) == $snap_changed_testpool ]]"
log_must eval "[[ $(zfs get -H -o value -p snapshots_changed_nsecs $TESTPOOL) == $snap_changed_nsecs_testpool ]]"
# Mount back the filesystems and check snapshots_changed still has correct value # Mount back the filesystems and check snapshots_changed still has correct value
log_must zfs mount $TESTPOOL log_must zfs mount $TESTPOOL
log_must eval "[[ $(zfs get -H -o value -p snapshots_changed $TESTPOOL) == $snap_changed_testpool ]]" log_must eval "[[ $(zfs get -H -o value -p snapshots_changed $TESTPOOL) == $snap_changed_testpool ]]"
log_must eval "[[ $(zfs get -H -o value -p snapshots_changed_nsecs $TESTPOOL) == $snap_changed_nsecs_testpool ]]"
log_must eval "[[ $(stat_mtime $tpool_snapdir) == $snap_changed_testpool ]]" log_must eval "[[ $(stat_mtime $tpool_snapdir) == $snap_changed_testpool ]]"
log_must zfs mount $TESTPOOL/$TESTFS log_must zfs mount $TESTPOOL/$TESTFS
log_must eval "[[ $(zfs get -H -o value -p snapshots_changed $TESTPOOL/$TESTFS) == $snap_changed_testfs ]]" log_must eval "[[ $(zfs get -H -o value -p snapshots_changed $TESTPOOL/$TESTFS) == $snap_changed_testfs ]]"
log_must eval "[[ $(zfs get -H -o value -p snapshots_changed_nsecs $TESTPOOL/$TESTFS) == $snap_changed_nsecs_testfs ]]"
log_must eval "[[ $(stat_mtime $tfs_snapdir) == $snap_changed_testfs ]]" log_must eval "[[ $(stat_mtime $tfs_snapdir) == $snap_changed_testfs ]]"
# Destroy the snapshots and check snapshots_changed shows correct time # Destroy the snapshots and check snapshots_changed shows correct time
curr_time=$(date '+%s') curr_time=$(date '+%s')
log_must zfs destroy $snap_testfsv1 log_must zfs destroy $snap_testfsv1
snap_changed_testfs=$(zfs get -H -o value -p snapshots_changed $TESTPOOL/$TESTFS) snap_changed_testfs=$(zfs get -H -o value -p snapshots_changed $TESTPOOL/$TESTFS)
snap_changed_nsecs_testfs=$(zfs get -H -o value -p snapshots_changed_nsecs $TESTPOOL/$TESTFS)
log_must eval "[[ $snap_changed_testfs -ge $curr_time ]]" log_must eval "[[ $snap_changed_testfs -ge $curr_time ]]"
log_must eval "[[ $((snap_changed_nsecs_testfs / 1000000000)) == $snap_changed_testfs ]]"
log_must eval "[[ $(stat_mtime $tfs_snapdir) == $snap_changed_testfs ]]" log_must eval "[[ $(stat_mtime $tfs_snapdir) == $snap_changed_testfs ]]"
curr_time=$(date '+%s') curr_time=$(date '+%s')
log_must zfs destroy $snap_testpool log_must zfs destroy $snap_testpool
snap_changed_testpool=$(zfs get -H -o value -p snapshots_changed $TESTPOOL) snap_changed_testpool=$(zfs get -H -o value -p snapshots_changed $TESTPOOL)
snap_changed_nsecs_testpool=$(zfs get -H -o value -p snapshots_changed_nsecs $TESTPOOL)
log_must eval "[[ $snap_changed_testpool -ge $curr_time ]]" log_must eval "[[ $snap_changed_testpool -ge $curr_time ]]"
log_must eval "[[ $((snap_changed_nsecs_testpool / 1000000000)) == $snap_changed_testpool ]]"
log_must eval "[[ $(stat_mtime $tpool_snapdir) == $snap_changed_testpool ]]" log_must eval "[[ $(stat_mtime $tpool_snapdir) == $snap_changed_testpool ]]"
log_pass "snapshots_changed property behaves correctly" log_pass "snapshots_changed and snapshots_changed_nsecs properties behave correctly"