diff --git a/cmd/zfs/zfs_main.c b/cmd/zfs/zfs_main.c index 75c0e40b610c..006da411dfe7 100644 --- a/cmd/zfs/zfs_main.c +++ b/cmd/zfs/zfs_main.c @@ -2341,15 +2341,25 @@ zfs_do_inherit(int argc, char **argv) if (!zfs_prop_inheritable(prop) && !received) { (void) fprintf(stderr, gettext("'%s' property cannot " "be inherited\n"), propname); - if (prop == ZFS_PROP_QUOTA || - prop == ZFS_PROP_RESERVATION || - prop == ZFS_PROP_REFQUOTA || - prop == ZFS_PROP_REFRESERVATION) { + switch (prop) { + case ZFS_PROP_QUOTA: + case ZFS_PROP_RESERVATION: + case ZFS_PROP_REFQUOTA: + case ZFS_PROP_REFRESERVATION: + case ZFS_PROP_RATELIMIT_BW_READ: + case ZFS_PROP_RATELIMIT_BW_WRITE: + case ZFS_PROP_RATELIMIT_BW_TOTAL: + case ZFS_PROP_RATELIMIT_OP_READ: + case ZFS_PROP_RATELIMIT_OP_WRITE: + case ZFS_PROP_RATELIMIT_OP_TOTAL: (void) fprintf(stderr, gettext("use 'zfs set " "%s=none' to clear\n"), propname); (void) fprintf(stderr, gettext("use 'zfs " "inherit -S %s' to revert to received " "value\n"), propname); + break; + default: + break; } return (1); } diff --git a/include/os/freebsd/zfs/sys/zfs_znode_impl.h b/include/os/freebsd/zfs/sys/zfs_znode_impl.h index 050fc3036f87..682f94ab7570 100644 --- a/include/os/freebsd/zfs/sys/zfs_znode_impl.h +++ b/include/os/freebsd/zfs/sys/zfs_znode_impl.h @@ -168,9 +168,12 @@ zfs_exit(zfsvfs_t *zfsvfs, const char *tag) (tp)->tv_sec = (time_t)(stmp)[0]; \ (tp)->tv_nsec = (long)(stmp)[1]; \ } -#define ZFS_ACCESSTIME_STAMP(zfsvfs, zp) \ - if ((zfsvfs)->z_atime && !((zfsvfs)->z_vfs->vfs_flag & VFS_RDONLY)) \ - zfs_tstamp_update_setup_ext(zp, ACCESSED, NULL, NULL, B_FALSE); +#define ZFS_ACCESSTIME_STAMP(zfsvfs, zp) do { \ + if ((zfsvfs)->z_atime && !((zfsvfs)->z_vfs->vfs_flag & VFS_RDONLY)) { \ + vfs_ratelimit_metadata_write((zfsvfs)->z_os); \ + zfs_tstamp_update_setup_ext(zp, ACCESSED, NULL, NULL, B_FALSE);\ + } \ +} while (0) extern void zfs_tstamp_update_setup_ext(struct znode *, uint_t, uint64_t [2], uint64_t [2], boolean_t have_tx); diff --git a/include/sys/dsl_dir.h b/include/sys/dsl_dir.h index f7c0d9acd10d..c820a96ac6ad 100644 --- a/include/sys/dsl_dir.h +++ b/include/sys/dsl_dir.h @@ -42,6 +42,7 @@ extern "C" { #endif struct dsl_dataset; +struct vfs_ratelimit; struct zthr; /* * DD_FIELD_* are strings that are used in the "extensified" dsl_dir zap object. @@ -127,6 +128,10 @@ struct dsl_dir { boolean_t dd_activity_cancelled; uint64_t dd_activity_waiters; + /* protected by spa_ratelimit_lock */ + struct vfs_ratelimit *dd_ratelimit; + dsl_dir_t *dd_ratelimit_root; + /* protected by dd_lock; keep at end of struct for better locality */ char dd_myname[ZFS_MAX_DATASET_NAME_LEN]; }; @@ -182,6 +187,7 @@ int dsl_dir_set_quota(const char *ddname, zprop_source_t source, uint64_t quota); int dsl_dir_set_reservation(const char *ddname, zprop_source_t source, uint64_t reservation); +int dsl_dir_set_ratelimit(const char *dsname, zfs_prop_t prop, uint64_t value); int dsl_dir_activate_fs_ss_limit(const char *); int dsl_fs_ss_limit_check(dsl_dir_t *, uint64_t, zfs_prop_t, dsl_dir_t *, cred_t *, proc_t *); diff --git a/include/sys/fs/zfs.h b/include/sys/fs/zfs.h index e191420f2d2d..117c596a1331 100644 --- a/include/sys/fs/zfs.h +++ b/include/sys/fs/zfs.h @@ -193,6 +193,12 @@ typedef enum { ZFS_PROP_SNAPSHOTS_CHANGED, ZFS_PROP_PREFETCH, ZFS_PROP_VOLTHREADING, + ZFS_PROP_RATELIMIT_BW_READ, + ZFS_PROP_RATELIMIT_BW_WRITE, + ZFS_PROP_RATELIMIT_BW_TOTAL, + ZFS_PROP_RATELIMIT_OP_READ, + ZFS_PROP_RATELIMIT_OP_WRITE, + ZFS_PROP_RATELIMIT_OP_TOTAL, ZFS_NUM_PROPS } zfs_prop_t; diff --git a/include/sys/spa_impl.h b/include/sys/spa_impl.h index 5605a35b8641..8cd02ca7084d 100644 --- a/include/sys/spa_impl.h +++ b/include/sys/spa_impl.h @@ -457,6 +457,8 @@ struct spa { uint64_t spa_leaf_list_gen; /* track leaf_list changes */ uint32_t spa_hostid; /* cached system hostid */ + rrmlock_t spa_ratelimit_lock; + /* synchronization for threads in spa_wait */ kmutex_t spa_activities_lock; kcondvar_t spa_activities_cv; diff --git a/include/sys/vfs_ratelimit.h b/include/sys/vfs_ratelimit.h new file mode 100644 index 000000000000..c54821aa21e1 --- /dev/null +++ b/include/sys/vfs_ratelimit.h @@ -0,0 +1,67 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ + +/* + * Copyright (c) 2024 The FreeBSD Foundation + * + * This software was developed by Pawel Dawidek + * under sponsorship from the FreeBSD Foundation. + */ + +#ifndef _SYS_VFS_RATELIMIT_H +#define _SYS_VFS_RATELIMIT_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +struct vfs_ratelimit; + +#define ZFS_RATELIMIT_BW_READ 0 +#define ZFS_RATELIMIT_BW_WRITE 1 +#define ZFS_RATELIMIT_BW_TOTAL 2 +#define ZFS_RATELIMIT_OP_READ 3 +#define ZFS_RATELIMIT_OP_WRITE 4 +#define ZFS_RATELIMIT_OP_TOTAL 5 +#define ZFS_RATELIMIT_FIRST ZFS_RATELIMIT_BW_READ +#define ZFS_RATELIMIT_LAST ZFS_RATELIMIT_OP_TOTAL +#define ZFS_RATELIMIT_NTYPES (ZFS_RATELIMIT_LAST + 1) + +int vfs_ratelimit_prop_to_type(zfs_prop_t prop); +zfs_prop_t vfs_ratelimit_type_to_prop(int type); + +struct vfs_ratelimit *vfs_ratelimit_alloc(const uint64_t *limits); +void vfs_ratelimit_free(struct vfs_ratelimit *rl); +struct vfs_ratelimit *vfs_ratelimit_set(struct vfs_ratelimit *rl, + zfs_prop_t prop, uint64_t limit); + +void vfs_ratelimit_data_read(objset_t *os, size_t blocksize, size_t bytes); +void vfs_ratelimit_data_write(objset_t *os, size_t blocksize, size_t bytes); +void vfs_ratelimit_metadata_read(objset_t *os); +void vfs_ratelimit_metadata_write(objset_t *os); + +#ifdef __cplusplus +} +#endif + +#endif /* _SYS_VFS_RATELIMIT_H */ diff --git a/lib/libzfs/libzfs.abi b/lib/libzfs/libzfs.abi index 80f4b7439a55..5388db07e949 100644 --- a/lib/libzfs/libzfs.abi +++ b/lib/libzfs/libzfs.abi @@ -1847,7 +1847,13 @@ - + + + + + + + diff --git a/lib/libzfs/libzfs_dataset.c b/lib/libzfs/libzfs_dataset.c index 231bbbd92dbf..c15ff1188a16 100644 --- a/lib/libzfs/libzfs_dataset.c +++ b/lib/libzfs/libzfs_dataset.c @@ -59,6 +59,7 @@ #include #include #include +#include #include #include @@ -2287,6 +2288,12 @@ get_numeric_property(zfs_handle_t *zhp, zfs_prop_t prop, zprop_source_t *src, case ZFS_PROP_SNAPSHOT_LIMIT: case ZFS_PROP_FILESYSTEM_COUNT: case ZFS_PROP_SNAPSHOT_COUNT: + case ZFS_PROP_RATELIMIT_BW_READ: + case ZFS_PROP_RATELIMIT_BW_WRITE: + case ZFS_PROP_RATELIMIT_BW_TOTAL: + case ZFS_PROP_RATELIMIT_OP_READ: + case ZFS_PROP_RATELIMIT_OP_WRITE: + case ZFS_PROP_RATELIMIT_OP_TOTAL: *val = getprop_uint64(zhp, prop, source); if (*source == NULL) { @@ -2811,12 +2818,15 @@ zfs_prop_get(zfs_handle_t *zhp, zfs_prop_t prop, char *propbuf, size_t proplen, case ZFS_PROP_REFQUOTA: case ZFS_PROP_RESERVATION: case ZFS_PROP_REFRESERVATION: + case ZFS_PROP_RATELIMIT_BW_READ: + case ZFS_PROP_RATELIMIT_BW_WRITE: + case ZFS_PROP_RATELIMIT_BW_TOTAL: if (get_numeric_property(zhp, prop, src, &source, &val) != 0) return (-1); /* - * If quota or reservation is 0, we translate this into 'none' - * (unless literal is set), and indicate that it's the default + * If the value is 0, we translate this into 'none' (unless + * literal is set), and indicate that it's the default * value. Otherwise, we print the number nicely and indicate * that its set locally. */ @@ -2835,6 +2845,25 @@ zfs_prop_get(zfs_handle_t *zhp, zfs_prop_t prop, char *propbuf, size_t proplen, zcp_check(zhp, prop, val, NULL); break; + case ZFS_PROP_RATELIMIT_OP_READ: + case ZFS_PROP_RATELIMIT_OP_WRITE: + case ZFS_PROP_RATELIMIT_OP_TOTAL: + + if (get_numeric_property(zhp, prop, src, &source, &val) != 0) + return (-1); + /* + * If the value is 0, we translate this into 'none', unless + * literal is set. + */ + if (val == 0 && !literal) { + (void) strlcpy(propbuf, "none", proplen); + } else { + (void) snprintf(propbuf, proplen, "%llu", + (u_longlong_t)val); + } + zcp_check(zhp, prop, val, NULL); + break; + case ZFS_PROP_FILESYSTEM_LIMIT: case ZFS_PROP_SNAPSHOT_LIMIT: case ZFS_PROP_FILESYSTEM_COUNT: diff --git a/lib/libzpool/Makefile.am b/lib/libzpool/Makefile.am index 42f3404db5a9..7ac03e907e6b 100644 --- a/lib/libzpool/Makefile.am +++ b/lib/libzpool/Makefile.am @@ -162,6 +162,7 @@ nodist_libzpool_la_SOURCES = \ module/zfs/vdev_removal.c \ module/zfs/vdev_root.c \ module/zfs/vdev_trim.c \ + module/zfs/vfs_ratelimit.c \ module/zfs/zap.c \ module/zfs/zap_leaf.c \ module/zfs/zap_micro.c \ diff --git a/man/man7/zfsprops.7 b/man/man7/zfsprops.7 index 9ff0236f4d74..a97c69d12120 100644 --- a/man/man7/zfsprops.7 +++ b/man/man7/zfsprops.7 @@ -1184,6 +1184,117 @@ and the minimum is .Sy 100000 . This property may be changed with .Nm zfs Cm change-key . +.It Sy limit_bw_read Ns = Ns Ar size Ns | Ns Sy none +.It Sy limit_bw_write Ns = Ns Ar size Ns | Ns Sy none +.It Sy limit_bw_total Ns = Ns Ar size Ns | Ns Sy none +Limits the read, write, or combined bandwidth, respectively, that a dataset and +its descendants can consume. +Limits are applied to both file systems and ZFS volumes. +Bandwidth limits are in bytes per second. +.Pp +The configured limits are hierarchical, just like quotas; i.e., even if a +higher limit is configured on the child dataset, the parent's lower limit will +be enforced. +.Pp +The limits are applied at the VFS level, not at the disk level. +The dataset is charged for each operation even if no disk access is required +(e.g., due to caching, compression, deduplication, or NOP writes) or if the +operation will cause more traffic (due to the copies property, mirroring, +or RAIDZ). +.Pp +Read bandwidth consumption is based on: +.Bl -bullet +.It +read-like syscalls, eg., +.Xr aio_read 2 , +.Xr copy_file_range 2 , +.Xr pread 2 , +.Xr preadv 2 , +.Xr read 2 , +.Xr readv 2 , +.Xr sendfile 2 +.It +syscalls like +.Xr getdents 2 +and +.Xr getdirentries 2 +.It +reading via mmaped files +.It +.Nm zfs Cm send +.El +.Pp +Write bandwidth consumption is based on: +.Bl -bullet +.It +write-like syscalls, eg., +.Xr aio_write 2 , +.Xr copy_file_range 2 , +.Xr pwrite 2 , +.Xr pwritev 2 , +.Xr write 2 , +.Xr writev 2 +.It +writing via mmaped files +.It +.Nm zfs Cm receive +.El +.It Sy limit_op_read Ns = Ns Ar count Ns | Ns Sy none +.It Sy limit_op_write Ns = Ns Ar count Ns | Ns Sy none +.It Sy limit_op_total Ns = Ns Ar count Ns | Ns Sy none +Limits the read, write, or both metadata operations, respectively, that a +dataset and its descendants can generate. +Limits are number of operations per second. +.Pp +Read operations consumption is based on: +.Bl -bullet +.It +read-like syscalls where the number of operations is equal to the number of +blocks being read (never less than 1) +.It +reading via mmaped files, where the number of operations is equal to the +number of pages being read (never less than 1) +.It +syscalls accessing metadata: +.Xr readlink 2 , +.Xr stat 2 +.El +.Pp +Write operations consumption is based on: +.Bl -bullet +.It +write-like syscalls where the number of operations is equal to the number of +blocks being written (never less than 1) +.It +writing via mmaped files, where the number of operations is equal to the +number of pages being written (never less than 1) +.It +syscalls modifing a directory's content: +.Xr bind 2 (UNIX-domain sockets) , +.Xr link 2 , +.Xr mkdir 2 , +.Xr mkfifo 2 , +.Xr mknod 2 , +.Xr open 2 (file creation) , +.Xr rename 2 , +.Xr rmdir 2 , +.Xr symlink 2 , +.Xr unlink 2 +.It +syscalls modifing metadata: +.Xr chflags 2 , +.Xr chmod 2 , +.Xr chown 2 , +.Xr utimes 2 +.It +updating the access time of a file when reading it +.El +.Pp +Just like +.Sy limit_bw +limits, the +.Sy limit_op +limits are also hierarchical and applied at the VFS level. .It Sy exec Ns = Ns Sy on Ns | Ns Sy off Controls whether processes can be executed from within this file system. The default value is diff --git a/module/Kbuild.in b/module/Kbuild.in index 7e08374fa2b9..0fbeb07a2b67 100644 --- a/module/Kbuild.in +++ b/module/Kbuild.in @@ -401,6 +401,7 @@ ZFS_OBJS := \ vdev_removal.o \ vdev_root.o \ vdev_trim.o \ + vfs_ratelimit.o \ zap.o \ zap_leaf.o \ zap_micro.o \ diff --git a/module/Makefile.bsd b/module/Makefile.bsd index d9d31564d090..bda2f7a5efd3 100644 --- a/module/Makefile.bsd +++ b/module/Makefile.bsd @@ -331,6 +331,7 @@ SRCS+= abd.c \ vdev_removal.c \ vdev_root.c \ vdev_trim.c \ + vfs_ratelimit.c \ zap.c \ zap_leaf.c \ zap_micro.c \ diff --git a/module/os/freebsd/zfs/zfs_vnops_os.c b/module/os/freebsd/zfs/zfs_vnops_os.c index b9b332434bd2..62f6c87eca2d 100644 --- a/module/os/freebsd/zfs/zfs_vnops_os.c +++ b/module/os/freebsd/zfs/zfs_vnops_os.c @@ -81,6 +81,7 @@ #include #include #include +#include #include #include #include @@ -483,6 +484,8 @@ update_pages(znode_t *zp, int64_t start, int len, objset_t *os) obj = vp->v_object; ASSERT3P(obj, !=, NULL); + vfs_ratelimit_data_read(zp->z_zfsvfs->z_os, PAGESIZE, len); + off = start & PAGEOFFSET; zfs_vmobject_wlock_12(obj); #if __FreeBSD_version >= 1300041 @@ -542,6 +545,8 @@ mappedread_sf(znode_t *zp, int nbytes, zfs_uio_t *uio) ASSERT3P(obj, !=, NULL); ASSERT0(zfs_uio_offset(uio) & PAGEOFFSET); + vfs_ratelimit_data_read(os, PAGESIZE, len); + zfs_vmobject_wlock_12(obj); for (start = zfs_uio_offset(uio); len > 0; start += PAGESIZE) { int bytes = MIN(PAGESIZE, len); @@ -621,6 +626,8 @@ mappedread(znode_t *zp, int nbytes, zfs_uio_t *uio) obj = vp->v_object; ASSERT3P(obj, !=, NULL); + vfs_ratelimit_data_read(zp->z_zfsvfs->z_os, PAGESIZE, nbytes); + start = zfs_uio_offset(uio); off = start & PAGEOFFSET; zfs_vmobject_wlock_12(obj); @@ -1149,6 +1156,8 @@ zfs_create(znode_t *dzp, const char *name, vattr_t *vap, int excl, int mode, goto out; } + vfs_ratelimit_metadata_write(os); + getnewvnode_reserve_(); tx = dmu_tx_create(os); @@ -1282,6 +1291,8 @@ zfs_remove_(vnode_t *dvp, vnode_t *vp, const char *name, cred_t *cr) ASSERT0(error); } + vfs_ratelimit_metadata_write(zfsvfs->z_os); + /* * We may delete the znode now, or we may put it in the unlinked set; * it depends on whether we're the last link, and on whether there are @@ -1509,6 +1520,8 @@ zfs_mkdir(znode_t *dzp, const char *dirname, vattr_t *vap, znode_t **zpp, return (SET_ERROR(EDQUOT)); } + vfs_ratelimit_metadata_write(zfsvfs->z_os); + /* * Add a new entry to the directory. */ @@ -1632,6 +1645,8 @@ zfs_rmdir_(vnode_t *dvp, vnode_t *vp, const char *name, cred_t *cr) vnevent_rmdir(vp, dvp, name, ct); + vfs_ratelimit_metadata_write(zfsvfs->z_os); + tx = dmu_tx_create(zfsvfs->z_os); dmu_tx_hold_zap(tx, dzp->z_id, FALSE, name); dmu_tx_hold_sa(tx, zp->z_sa_hdl, B_FALSE); @@ -1788,12 +1803,11 @@ zfs_readdir(vnode_t *vp, zfs_uio_t *uio, cred_t *cr, int *eofp, */ iovp = GET_UIO_STRUCT(uio)->uio_iov; bytes_wanted = iovp->iov_len; + bufsize = bytes_wanted; if (zfs_uio_segflg(uio) != UIO_SYSSPACE || zfs_uio_iovcnt(uio) != 1) { - bufsize = bytes_wanted; outbuf = kmem_alloc(bufsize, KM_SLEEP); odp = (struct dirent64 *)outbuf; } else { - bufsize = bytes_wanted; outbuf = NULL; odp = (struct dirent64 *)iovp->iov_base; } @@ -1925,6 +1939,14 @@ zfs_readdir(vnode_t *vp, zfs_uio_t *uio, cred_t *cr, int *eofp, if (ncookies != NULL) *ncookies -= ncooks; + /* + * This is post factum, but if we would do that inside the loop we + * wouldn't know the record length before reading it anyway plus we + * would be calling vfs_ratelimit_data_read() way too often and each + * call accounts for a single operation. + */ + vfs_ratelimit_data_read(os, zp->z_blksz, outcount); + if (zfs_uio_segflg(uio) == UIO_SYSSPACE && zfs_uio_iovcnt(uio) == 1) { iovp->iov_base += outcount; iovp->iov_len -= outcount; @@ -2017,6 +2039,8 @@ zfs_getattr(vnode_t *vp, vattr_t *vap, int flags, cred_t *cr) } } + vfs_ratelimit_metadata_read(zfsvfs->z_os); + /* * Return all attributes. It's cheaper to provide the answer * than to determine whether we were asked the question. @@ -2612,6 +2636,9 @@ zfs_setattr(znode_t *zp, vattr_t *vap, int flags, cred_t *cr, zidmap_t *mnt_ns) goto out2; } } + + vfs_ratelimit_metadata_write(os); + tx = dmu_tx_create(os); if (mask & AT_MODE) { @@ -3367,6 +3394,8 @@ zfs_do_rename_impl(vnode_t *sdvp, vnode_t **svpp, struct componentname *scnp, vnevent_rename_dest_dir(tdvp, ct); } + vfs_ratelimit_metadata_write(zfsvfs->z_os); + tx = dmu_tx_create(zfsvfs->z_os); dmu_tx_hold_sa(tx, szp->z_sa_hdl, B_FALSE); dmu_tx_hold_sa(tx, sdzp->z_sa_hdl, B_FALSE); @@ -3564,6 +3593,8 @@ zfs_symlink(znode_t *dzp, const char *name, vattr_t *vap, return (SET_ERROR(EDQUOT)); } + vfs_ratelimit_metadata_write(zfsvfs->z_os); + getnewvnode_reserve_(); tx = dmu_tx_create(zfsvfs->z_os); fuid_dirtied = zfsvfs->z_fuid_dirty; @@ -3661,6 +3692,8 @@ zfs_readlink(vnode_t *vp, zfs_uio_t *uio, cred_t *cr, caller_context_t *ct) if ((error = zfs_enter_verify_zp(zfsvfs, zp, FTAG)) != 0) return (error); + vfs_ratelimit_metadata_read(zfsvfs->z_os); + if (zp->z_is_sa) error = sa_lookup_uio(zp->z_sa_hdl, SA_ZPL_SYMLINK(zfsvfs), uio); @@ -3789,6 +3822,8 @@ zfs_link(znode_t *tdzp, znode_t *szp, const char *name, cred_t *cr, return (error); } + vfs_ratelimit_metadata_write(zfsvfs->z_os); + tx = dmu_tx_create(zfsvfs->z_os); dmu_tx_hold_sa(tx, szp->z_sa_hdl, B_FALSE); dmu_tx_hold_zap(tx, tdzp->z_id, TRUE, name); @@ -4118,6 +4153,9 @@ zfs_getpages(struct vnode *vp, vm_page_t *ma, int count, int *rbehind, pgsin_a = MIN(*rahead, pgsin_a); } + vfs_ratelimit_data_read(zfsvfs->z_os, zp->z_blksz, + MIN(end, obj_size) - start); + /* * NB: we need to pass the exact byte size of the data that we expect * to read after accounting for the file size. This is required because @@ -4254,6 +4292,8 @@ zfs_putpages(struct vnode *vp, vm_page_t *ma, size_t len, int flags, goto out; } + vfs_ratelimit_data_write(zfsvfs->z_os, zp->z_blksz, len); + tx = dmu_tx_create(zfsvfs->z_os); dmu_tx_hold_write(tx, zp->z_id, off, len); diff --git a/module/os/freebsd/zfs/zvol_os.c b/module/os/freebsd/zfs/zvol_os.c index 712ff1b837d7..4f8278feb139 100644 --- a/module/os/freebsd/zfs/zvol_os.c +++ b/module/os/freebsd/zfs/zvol_os.c @@ -88,6 +88,7 @@ #include #include #include +#include #include #include #include @@ -728,6 +729,8 @@ zvol_geom_bio_strategy(struct bio *bp) doread ? RL_READER : RL_WRITER); if (bp->bio_cmd == BIO_DELETE) { + /* Should we account only for a single metadata write? */ + vfs_ratelimit_metadata_write(zv->zv_objset); dmu_tx_t *tx = dmu_tx_create(zv->zv_objset); error = dmu_tx_assign(tx, TXG_WAIT); if (error != 0) { @@ -744,9 +747,13 @@ zvol_geom_bio_strategy(struct bio *bp) while (resid != 0 && off < volsize) { size_t size = MIN(resid, zvol_maxphys); if (doread) { + vfs_ratelimit_data_read(zv->zv_objset, + zv->zv_volblocksize, size); error = dmu_read(os, ZVOL_OBJ, off, size, addr, DMU_READ_PREFETCH); } else { + vfs_ratelimit_data_write(zv->zv_objset, + zv->zv_volblocksize, size); dmu_tx_t *tx = dmu_tx_create(os); dmu_tx_hold_write_by_dnode(tx, zv->zv_dn, off, size); error = dmu_tx_assign(tx, TXG_WAIT); diff --git a/module/os/linux/zfs/zfs_vnops_os.c b/module/os/linux/zfs/zfs_vnops_os.c index 1cecad9f7755..4f3d3eea1b7e 100644 --- a/module/os/linux/zfs/zfs_vnops_os.c +++ b/module/os/linux/zfs/zfs_vnops_os.c @@ -69,6 +69,7 @@ #include #include #include +#include /* * Programming rules. @@ -237,9 +238,12 @@ static int zfs_fillpage(struct inode *ip, struct page *pp); void update_pages(znode_t *zp, int64_t start, int len, objset_t *os) { + zfsvfs_t *zfsvfs = ZTOZSB(zp); struct address_space *mp = ZTOI(zp)->i_mapping; int64_t off = start & (PAGE_SIZE - 1); + vfs_ratelimit_data_read(zfsvfs->z_os, PAGESIZE, len); + for (start &= PAGE_MASK; len > 0; start += PAGE_SIZE) { uint64_t nbytes = MIN(PAGE_SIZE - off, len); @@ -281,17 +285,19 @@ update_pages(znode_t *zp, int64_t start, int len, objset_t *os) * from memory mapped pages, otherwise fallback to reading through the dmu. */ int -mappedread(znode_t *zp, int nbytes, zfs_uio_t *uio) +mappedread(znode_t *zp, int len, zfs_uio_t *uio) { + zfsvfs_t *zfsvfs = ZTOZSB(zp); struct inode *ip = ZTOI(zp); struct address_space *mp = ip->i_mapping; int64_t start = uio->uio_loffset; int64_t off = start & (PAGE_SIZE - 1); - int len = nbytes; int error = 0; + vfs_ratelimit_data_read(zfsvfs->z_os, PAGESIZE, len); + for (start &= PAGE_MASK; len > 0; start += PAGE_SIZE) { - uint64_t bytes = MIN(PAGE_SIZE - off, len); + uint64_t nbytes = MIN(PAGE_SIZE - off, len); struct page *pp = find_lock_page(mp, start >> PAGE_SHIFT); if (pp) { @@ -314,7 +320,7 @@ mappedread(znode_t *zp, int nbytes, zfs_uio_t *uio) unlock_page(pp); void *pb = kmap(pp); - error = zfs_uiomove(pb + off, bytes, UIO_READ, uio); + error = zfs_uiomove(pb + off, nbytes, UIO_READ, uio); kunmap(pp); if (mapping_writably_mapped(mp)) @@ -324,10 +330,10 @@ mappedread(znode_t *zp, int nbytes, zfs_uio_t *uio) put_page(pp); } else { error = dmu_read_uio_dbuf(sa_get_db(zp->z_sa_hdl), - uio, bytes); + uio, nbytes); } - len -= bytes; + len -= nbytes; off = 0; if (error) @@ -677,6 +683,8 @@ zfs_create(znode_t *dzp, char *name, vattr_t *vap, int excl, goto out; } + vfs_ratelimit_metadata_write(os); + tx = dmu_tx_create(os); dmu_tx_hold_sa_create(tx, acl_ids.z_aclp->z_acl_bytes + @@ -871,6 +879,8 @@ zfs_tmpfile(struct inode *dip, vattr_t *vap, int excl, goto out; } + vfs_ratelimit_metadata_write(os); + tx = dmu_tx_create(os); dmu_tx_hold_sa_create(tx, acl_ids.z_aclp->z_acl_bytes + @@ -1007,6 +1017,8 @@ zfs_remove(znode_t *dzp, char *name, cred_t *cr, int flags) !zn_has_cached_data(zp, 0, LLONG_MAX); mutex_exit(&zp->z_lock); + vfs_ratelimit_metadata_write(zfsvfs->z_os); + /* * We may delete the znode now, or we may put it in the unlinked set; * it depends on whether we're the last link, and on whether there are @@ -1278,6 +1290,8 @@ zfs_mkdir(znode_t *dzp, char *dirname, vattr_t *vap, znode_t **zpp, return (SET_ERROR(EDQUOT)); } + vfs_ratelimit_metadata_write(zfsvfs->z_os); + /* * Add a new entry to the directory. */ @@ -1420,6 +1434,8 @@ zfs_rmdir(znode_t *dzp, char *name, znode_t *cwd, cred_t *cr, goto out; } + vfs_ratelimit_metadata_write(zfsvfs->z_os); + /* * Grab a lock on the directory to make sure that no one is * trying to add (or lookup) entries while we are removing it. @@ -1632,6 +1648,16 @@ zfs_readdir(struct inode *ip, zpl_dir_context_t *ctx, cred_t *cr) } zp->z_zn_prefetch = B_FALSE; /* a lookup will re-enable pre-fetching */ +#ifdef TODO + /* + * This is post factum, but if we would do that inside the loop we + * wouldn't know the record length before reading it anyway plus we + * would be calling vfs_ratelimit_data_read() way too often and each + * call accounts for a single operation. + */ + vfs_ratelimit_data_read(os, zp->z_blksz, size /* ??? */); +#endif + update: zap_cursor_fini(&zc); if (error == ENOENT) @@ -1671,6 +1697,8 @@ zfs_getattr_fast(zidmap_t *user_ns, struct inode *ip, struct kstat *sp) if ((error = zfs_enter_verify_zp(zfsvfs, zp, FTAG)) != 0) return (error); + vfs_ratelimit_metadata_read(zfsvfs->z_os); + mutex_enter(&zp->z_lock); #ifdef HAVE_GENERIC_FILLATTR_IDMAP_REQMASK @@ -2269,6 +2297,9 @@ zfs_setattr(znode_t *zp, vattr_t *vap, int flags, cred_t *cr, zidmap_t *mnt_ns) goto out2; } } + + vfs_ratelimit_metadata_write(os); + tx = dmu_tx_create(os); if (mask & ATTR_MODE) { @@ -2981,6 +3012,8 @@ zfs_rename(znode_t *sdzp, char *snm, znode_t *tdzp, char *tnm, } } + vfs_ratelimit_metadata_write(zfsvfs->z_os); + tx = dmu_tx_create(zfsvfs->z_os); dmu_tx_hold_sa(tx, szp->z_sa_hdl, B_FALSE); dmu_tx_hold_sa(tx, sdzp->z_sa_hdl, B_FALSE); @@ -3294,6 +3327,9 @@ zfs_symlink(znode_t *dzp, char *name, vattr_t *vap, char *link, zfs_exit(zfsvfs, FTAG); return (SET_ERROR(EDQUOT)); } + + vfs_ratelimit_metadata_write(zfsvfs->z_os); + tx = dmu_tx_create(zfsvfs->z_os); fuid_dirtied = zfsvfs->z_fuid_dirty; dmu_tx_hold_write(tx, DMU_NEW_OBJECT, 0, MAX(1, len)); @@ -3402,6 +3438,8 @@ zfs_readlink(struct inode *ip, zfs_uio_t *uio, cred_t *cr) if ((error = zfs_enter_verify_zp(zfsvfs, zp, FTAG)) != 0) return (error); + vfs_ratelimit_metadata_read(zfsvfs->z_os); + mutex_enter(&zp->z_lock); if (zp->z_is_sa) error = sa_lookup_uio(zp->z_sa_hdl, @@ -3539,6 +3577,8 @@ zfs_link(znode_t *tdzp, znode_t *szp, char *name, cred_t *cr, return (error); } + vfs_ratelimit_metadata_write(zfsvfs->z_os); + top: /* * Attempt to lock directory; fail if entry already exists. @@ -3790,6 +3830,8 @@ zfs_putpage(struct inode *ip, struct page *pp, struct writeback_control *wbc, set_page_writeback(pp); unlock_page(pp); + vfs_ratelimit_data_write(zfsvfs->z_os, zp->z_blksz, pglen); + tx = dmu_tx_create(zfsvfs->z_os); dmu_tx_hold_write(tx, zp->z_id, pgoff, pglen); dmu_tx_hold_sa(tx, zp->z_sa_hdl, B_FALSE); @@ -3905,6 +3947,8 @@ zfs_dirty_inode(struct inode *ip, int flags) } #endif + vfs_ratelimit_metadata_write(zfsvfs->z_os); + tx = dmu_tx_create(zfsvfs->z_os); dmu_tx_hold_sa(tx, zp->z_sa_hdl, B_FALSE); @@ -3965,6 +4009,8 @@ zfs_inactive(struct inode *ip) } if (zp->z_atime_dirty && zp->z_unlinked == B_FALSE) { + vfs_ratelimit_metadata_write(zfsvfs->z_os); + dmu_tx_t *tx = dmu_tx_create(zfsvfs->z_os); dmu_tx_hold_sa(tx, zp->z_sa_hdl, B_FALSE); @@ -4006,6 +4052,8 @@ zfs_fillpage(struct inode *ip, struct page *pp) if (io_off + io_len > i_size) io_len = i_size - io_off; + vfs_ratelimit_data_read(zfsvfs->z_os, PAGESIZE, io_len); + void *va = kmap(pp); int error = dmu_read(zfsvfs->z_os, ITOZ(ip)->z_id, io_off, io_len, va, DMU_READ_PREFETCH); diff --git a/module/os/linux/zfs/zvol_os.c b/module/os/linux/zfs/zvol_os.c index 3e020e532263..5a19f3e579b4 100644 --- a/module/os/linux/zfs/zvol_os.c +++ b/module/os/linux/zfs/zvol_os.c @@ -35,6 +35,7 @@ #include #include #include +#include #include #include #include @@ -292,11 +293,15 @@ zvol_write(zv_request_t *zvr) while (uio.uio_resid > 0 && uio.uio_loffset < volsize) { uint64_t bytes = MIN(uio.uio_resid, DMU_MAX_ACCESS >> 1); uint64_t off = uio.uio_loffset; - dmu_tx_t *tx = dmu_tx_create(zv->zv_objset); if (bytes > volsize - off) /* don't write past the end */ bytes = volsize - off; + vfs_ratelimit_data_write(zv->zv_objset, zv->zv_volblocksize, + bytes); + + dmu_tx_t *tx = dmu_tx_create(zv->zv_objset); + dmu_tx_hold_write_by_dnode(tx, zv->zv_dn, off, bytes); /* This will only fail for ENOSPC */ @@ -394,6 +399,9 @@ zvol_discard(zv_request_t *zvr) zfs_locked_range_t *lr = zfs_rangelock_enter(&zv->zv_rangelock, start, size, RL_WRITER); + /* Should we account only for a single metadata write? */ + vfs_ratelimit_metadata_write(zv->zv_objset); + tx = dmu_tx_create(zv->zv_objset); dmu_tx_mark_netfree(tx); error = dmu_tx_assign(tx, TXG_WAIT); @@ -475,6 +483,9 @@ zvol_read(zv_request_t *zvr) if (bytes > volsize - uio.uio_loffset) bytes = volsize - uio.uio_loffset; + vfs_ratelimit_data_read(zv->zv_objset, zv->zv_volblocksize, + bytes); + error = dmu_read_uio_dnode(zv->zv_dn, &uio, bytes); if (error) { /* convert checksum errors into IO errors */ diff --git a/module/zcommon/zfs_prop.c b/module/zcommon/zfs_prop.c index 764993b45e7c..92bdab11d134 100644 --- a/module/zcommon/zfs_prop.c +++ b/module/zcommon/zfs_prop.c @@ -37,6 +37,7 @@ #include #include #include +#include #include #include #include @@ -694,6 +695,24 @@ zfs_prop_init(void) zprop_register_number(ZFS_PROP_SNAPSHOT_LIMIT, "snapshot_limit", UINT64_MAX, PROP_DEFAULT, ZFS_TYPE_FILESYSTEM | ZFS_TYPE_VOLUME, " | none", "SSLIMIT", B_FALSE, sfeatures); + zprop_register_number(ZFS_PROP_RATELIMIT_BW_READ, "limit_bw_read", + 0, PROP_DEFAULT, ZFS_TYPE_FILESYSTEM | ZFS_TYPE_VOLUME, + " | none", "RTBWRD", B_FALSE, sfeatures); + zprop_register_number(ZFS_PROP_RATELIMIT_BW_WRITE, "limit_bw_write", + 0, PROP_DEFAULT, ZFS_TYPE_FILESYSTEM | ZFS_TYPE_VOLUME, + " | none", "RTBWWR", B_FALSE, sfeatures); + zprop_register_number(ZFS_PROP_RATELIMIT_BW_TOTAL, "limit_bw_total", + 0, PROP_DEFAULT, ZFS_TYPE_FILESYSTEM | ZFS_TYPE_VOLUME, + " | none", "RTBWTL", B_FALSE, sfeatures); + zprop_register_number(ZFS_PROP_RATELIMIT_OP_READ, "limit_op_read", + 0, PROP_DEFAULT, ZFS_TYPE_FILESYSTEM | ZFS_TYPE_VOLUME, + " | none", "RTOPRD", B_FALSE, sfeatures); + zprop_register_number(ZFS_PROP_RATELIMIT_OP_WRITE, "limit_op_write", + 0, PROP_DEFAULT, ZFS_TYPE_FILESYSTEM | ZFS_TYPE_VOLUME, + " | none", "RTOPWR", B_FALSE, sfeatures); + zprop_register_number(ZFS_PROP_RATELIMIT_OP_TOTAL, "limit_op_total", + 0, PROP_DEFAULT, ZFS_TYPE_FILESYSTEM | ZFS_TYPE_VOLUME, + " | none", "RTOPTL", B_FALSE, sfeatures); /* inherit number properties */ zprop_register_number(ZFS_PROP_RECORDSIZE, "recordsize", @@ -997,7 +1016,6 @@ zfs_prop_valid_keylocation(const char *str, boolean_t encrypted) return (B_FALSE); } - #ifndef _KERNEL #include diff --git a/module/zfs/dmu_recv.c b/module/zfs/dmu_recv.c index 680aed4513bc..871d3d1789ba 100644 --- a/module/zfs/dmu_recv.c +++ b/module/zfs/dmu_recv.c @@ -63,6 +63,7 @@ #include #include #include +#include #ifdef _KERNEL #include #endif @@ -2204,6 +2205,9 @@ flush_write_batch_impl(struct receive_writer_arg *rwa) ASSERT3U(drrw->drr_object, ==, rwa->last_object); + vfs_ratelimit_data_write(rwa->os, drrw->drr_logical_size, + drrw->drr_logical_size); + if (drrw->drr_logical_size != dn->dn_datablksz) { /* * The WRITE record is larger than the object's block diff --git a/module/zfs/dmu_send.c b/module/zfs/dmu_send.c index b6cc2f0a5e91..216936ad50e0 100644 --- a/module/zfs/dmu_send.c +++ b/module/zfs/dmu_send.c @@ -62,6 +62,7 @@ #include #include #include +#include #ifdef _KERNEL #include #endif @@ -1684,6 +1685,8 @@ issue_data_read(struct send_reader_thread_arg *srta, struct send_range *range) .zb_blkid = range->start_blkid, }; + vfs_ratelimit_data_read(os, BP_GET_LSIZE(bp), BP_GET_LSIZE(bp)); + arc_flags_t aflags = ARC_FLAG_CACHED_ONLY; int arc_err = arc_read(NULL, os->os_spa, bp, diff --git a/module/zfs/dsl_dir.c b/module/zfs/dsl_dir.c index baf970121a61..4f7252e2c38d 100644 --- a/module/zfs/dsl_dir.c +++ b/module/zfs/dsl_dir.c @@ -47,6 +47,7 @@ #include #include #include +#include #include #include #include @@ -143,7 +144,7 @@ dsl_dir_evict_async(void *dbu) { dsl_dir_t *dd = dbu; int t; - dsl_pool_t *dp __maybe_unused = dd->dd_pool; + dsl_pool_t *dp = dd->dd_pool; dd->dd_dbuf = NULL; @@ -161,6 +162,19 @@ dsl_dir_evict_async(void *dbu) if (dsl_deadlist_is_open(&dd->dd_livelist)) dsl_dir_livelist_close(dd); + rrm_enter_write(&dp->dp_spa->spa_ratelimit_lock); + if (dd->dd_ratelimit_root == dd) { + vfs_ratelimit_free(dd->dd_ratelimit); + dd->dd_ratelimit = NULL; + dd->dd_ratelimit_root = NULL; + /* + * We don't have to recurse down, because there are no children. + * If there were any, they will have a hold on us and we + * couldn't be evicted. + */ + } + rrm_exit(&dp->dp_spa->spa_ratelimit_lock, NULL); + dsl_prop_fini(dd); cv_destroy(&dd->dd_activity_cv); mutex_destroy(&dd->dd_activity_lock); @@ -168,6 +182,75 @@ dsl_dir_evict_async(void *dbu) kmem_free(dd, sizeof (dsl_dir_t)); } +static boolean_t +dsl_dir_ratelimit_read_properties(dsl_dir_t *dd, uint64_t *limits) +{ + char *myname, *setpoint; + boolean_t isset; + int type; + + myname = kmem_alloc(ZFS_MAX_DATASET_NAME_LEN, KM_SLEEP); + dsl_dir_name(dd, myname); + setpoint = kmem_alloc(MAXNAMELEN, KM_SLEEP); + + isset = B_FALSE; + for (type = ZFS_RATELIMIT_FIRST; type <= ZFS_RATELIMIT_LAST; type++) { + const char *propname; + uint64_t limit; + + propname = zfs_prop_to_name(vfs_ratelimit_type_to_prop(type)); + if (dsl_prop_get_dd(dd, propname, 8, 1, &limit, setpoint, + B_FALSE) != 0) { + /* Property doesn't exist or unable to read. */ + continue; + } + if (strcmp(myname, setpoint) != 0) { + /* Property is not set here. */ + continue; + } + if (limit == 0) { + /* Property is set to none, but we treat it as unset. */ + continue; + } + limits[type] = limit; + isset = B_TRUE; + } + + kmem_free(setpoint, MAXNAMELEN); + kmem_free(myname, ZFS_MAX_DATASET_NAME_LEN); + + return (isset); +} + +static void +dsl_dir_ratelimit_read(dsl_dir_t *dd) +{ + uint64_t limits[ZFS_RATELIMIT_NTYPES] = {0}; + boolean_t isset, needlock; + + isset = dsl_dir_ratelimit_read_properties(dd, limits); + needlock = !RRM_WRITE_HELD(&dd->dd_pool->dp_spa->spa_ratelimit_lock); + + if (needlock) { + rrm_enter_write(&dd->dd_pool->dp_spa->spa_ratelimit_lock); + } + if (isset) { + dd->dd_ratelimit = vfs_ratelimit_alloc(limits); + dd->dd_ratelimit_root = dd; + } else { + dd->dd_ratelimit = NULL; + if (dd->dd_parent == NULL) { + dd->dd_ratelimit_root = NULL; + } else { + dd->dd_ratelimit_root = + dd->dd_parent->dd_ratelimit_root; + } + } + if (needlock) { + rrm_exit(&dd->dd_pool->dp_spa->spa_ratelimit_lock, NULL); + } +} + int dsl_dir_hold_obj(dsl_pool_t *dp, uint64_t ddobj, const char *tail, const void *tag, dsl_dir_t **ddp) @@ -304,6 +387,10 @@ dsl_dir_hold_obj(dsl_pool_t *dp, uint64_t ddobj, dd = winner; } else { spa_open_ref(dp->dp_spa, dd); + + if (dd->dd_myname[0] != '$') { + dsl_dir_ratelimit_read(dd); + } } } @@ -1908,6 +1995,126 @@ would_change(dsl_dir_t *dd, int64_t delta, dsl_dir_t *ancestor) return (would_change(dd->dd_parent, delta, ancestor)); } +static void +dsl_dir_ratelimit_recurse(dsl_dir_t *dd) +{ + dsl_pool_t *dp = dd->dd_pool; + objset_t *os = dp->dp_meta_objset; + zap_cursor_t *zc; + zap_attribute_t *za; + + ASSERT(dsl_pool_config_held(dp)); + ASSERT(RRM_WRITE_HELD(&dp->dp_spa->spa_ratelimit_lock)); + + zc = kmem_alloc(sizeof (zap_cursor_t), KM_SLEEP); + za = kmem_alloc(sizeof (zap_attribute_t), KM_SLEEP); + + /* Iterate my child dirs */ + for (zap_cursor_init(zc, os, dsl_dir_phys(dd)->dd_child_dir_zapobj); + zap_cursor_retrieve(zc, za) == 0; zap_cursor_advance(zc)) { + dsl_dir_t *child_dd; + + VERIFY0(dsl_dir_hold_obj(dp, za->za_first_integer, NULL, FTAG, + &child_dd)); + + /* + * Ignore hidden ($FREE, $MOS & $ORIGIN) objsets. + */ + if (child_dd->dd_myname[0] == '$') { + dsl_dir_rele(child_dd, FTAG); + continue; + } + + /* + * Ratelimit properties are also set here, don't overwrite. + */ + if (child_dd->dd_ratelimit_root == child_dd) { + dsl_dir_rele(child_dd, FTAG); + continue; + } + + ASSERT(child_dd->dd_ratelimit == NULL); + child_dd->dd_ratelimit_root = dd->dd_ratelimit_root; + + + dsl_dir_ratelimit_recurse(child_dd); + + dsl_dir_rele(child_dd, FTAG); + } + zap_cursor_fini(zc); + + kmem_free(zc, sizeof (zap_cursor_t)); + kmem_free(za, sizeof (zap_attribute_t)); +} + +int +dsl_dir_set_ratelimit(const char *dsname, zfs_prop_t prop, uint64_t limit) +{ + spa_t *spa; + dsl_pool_t *dp; + dsl_dir_t *dd; + int err; + + mutex_enter(&spa_namespace_lock); + + spa = spa_lookup(dsname); + if (spa == NULL) { + mutex_exit(&spa_namespace_lock); + return (ENOENT); + } + + dp = spa->spa_dsl_pool; + dsl_pool_config_enter(dp, FTAG); + + mutex_exit(&spa_namespace_lock); + + err = dsl_dir_hold(spa->spa_dsl_pool, dsname, FTAG, &dd, NULL); + if (err != 0) { + dsl_pool_config_exit(dp, FTAG); + return (err); + } + + rrm_enter_write(&spa->spa_ratelimit_lock); + + if (dd->dd_ratelimit_root == dd) { + /* We are the root. */ + ASSERT(dd->dd_ratelimit != NULL); + + dd->dd_ratelimit = vfs_ratelimit_set(dd->dd_ratelimit, prop, + limit); + if (dd->dd_ratelimit == NULL) { + if (dd->dd_parent == NULL) { + dd->dd_ratelimit_root = NULL; + } else { + dd->dd_ratelimit_root = + dd->dd_parent->dd_ratelimit_root; + } + dsl_dir_ratelimit_recurse(dd); + } + } else if (limit != 0) { + /* + * No limits are currently configured or we are not the root. + * Allocate new structure and set the limit. + */ + dd->dd_ratelimit = vfs_ratelimit_set(NULL, prop, limit); + dd->dd_ratelimit_root = dd; + dsl_dir_ratelimit_recurse(dd); + } else { + /* + * We are not the root and limits is set to none, + * so nothing to do. + */ + } + + rrm_exit(&spa->spa_ratelimit_lock, NULL); + + dsl_dir_rele(dd, FTAG); + + dsl_pool_config_exit(dp, FTAG); + + return (0); +} + typedef struct dsl_dir_rename_arg { const char *ddra_oldname; const char *ddra_newname; @@ -2105,6 +2312,22 @@ dsl_dir_rename_check(void *arg, dmu_tx_t *tx) return (0); } +static void +dsl_dir_ratelimit_rename(dsl_dir_t *dd, dsl_dir_t *newparent) +{ + + rrm_enter_write(&dd->dd_pool->dp_spa->spa_ratelimit_lock); + + if (dd->dd_ratelimit_root != dd) { + ASSERT(dd->dd_ratelimit == NULL); + dd->dd_ratelimit_root = newparent; + + dsl_dir_ratelimit_recurse(dd); + } + + rrm_exit(&dd->dd_pool->dp_spa->spa_ratelimit_lock, NULL); +} + static void dsl_dir_rename_sync(void *arg, dmu_tx_t *tx) { @@ -2156,6 +2379,8 @@ dsl_dir_rename_sync(void *arg, dmu_tx_t *tx) dsl_fs_ss_count_adjust(newparent, ss_cnt, DD_FIELD_SNAPSHOT_COUNT, tx); + dsl_dir_ratelimit_rename(dd, newparent); + dsl_dir_diduse_space(dd->dd_parent, DD_USED_CHILD, -dsl_dir_phys(dd)->dd_used_bytes, -dsl_dir_phys(dd)->dd_compressed_bytes, diff --git a/module/zfs/spa_misc.c b/module/zfs/spa_misc.c index d1d41bbe7214..e21f6f166c66 100644 --- a/module/zfs/spa_misc.c +++ b/module/zfs/spa_misc.c @@ -715,6 +715,8 @@ spa_add(const char *name, nvlist_t *config, const char *altroot) mutex_init(&spa->spa_flushed_ms_lock, NULL, MUTEX_DEFAULT, NULL); mutex_init(&spa->spa_activities_lock, NULL, MUTEX_DEFAULT, NULL); + rrm_init(&spa->spa_ratelimit_lock, B_FALSE); + cv_init(&spa->spa_async_cv, NULL, CV_DEFAULT, NULL); cv_init(&spa->spa_evicting_os_cv, NULL, CV_DEFAULT, NULL); cv_init(&spa->spa_proc_cv, NULL, CV_DEFAULT, NULL); @@ -902,6 +904,8 @@ spa_remove(spa_t *spa) cv_destroy(&spa->spa_activities_cv); cv_destroy(&spa->spa_waiters_cv); + rrm_destroy(&spa->spa_ratelimit_lock); + mutex_destroy(&spa->spa_flushed_ms_lock); mutex_destroy(&spa->spa_async_lock); mutex_destroy(&spa->spa_errlist_lock); diff --git a/module/zfs/vfs_ratelimit.c b/module/zfs/vfs_ratelimit.c new file mode 100644 index 000000000000..4e6493b136ba --- /dev/null +++ b/module/zfs/vfs_ratelimit.c @@ -0,0 +1,664 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ + +/* + * Copyright (c) 2024 The FreeBSD Foundation + * + * This software was developed by Pawel Dawidek + * under sponsorship from the FreeBSD Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include + +/* + * The following comment describes rate limiting bandwidth and operations for + * datasets that have the ratelimit property configured. + * + * The goal was to provide practically useful rate limiting for ZFS + * without introducing any performance degradation when the limits are + * configured, but not exceeded. + * + * The rate limiting is applied at the VFS level for file systems, before going + * to DMU. The limits are not applied at the disk level. This means that even if + * no disk access is required to perform the given operation, the dataset is + * still charged for it. + * The reasons for this design choice are the following: + * - It would be impossible or at least very complicated to enforce such limits + * at the VDEV level, especially for writes. At that point the writes are + * already assigned to the specific txg and wating here would mean the whole + * pool has to wait. + * - It would be hard to predict what limits should be configured as there are a + * lot of factors that dictate how much disk bandwidth is really required + * (due to RAIDZ inflation, compression, gang blocks, deduplication, + * NOP writes, I/O aggregation, metadata traffic, etc.). + * By enforcing the limits at the VFS level for file system operations it should + * be easy to find out what limits applications require and verify that the + * limits are correctly enforced by monitoring system calls issued by the + * applications. + * + * Bandwidth and operation limits are divided into three types: read, write and + * total, where total is a combined limit for reads and writes. + * + * Each dataset can have its own limits configured. The configured limits are + * enforced on the dataset and all its children - limits are hierarchical, + * like quota. Even if a child dataset has a higher limit configured than its + * parent, it cannot go beyond its parent limit. + * + * Dataset can have only selected limits configured (eg. read bandwidth and + * write operations, but not the rest). + * + * The limits are stored in the vfs_ratelimit structure and attached to the + * dsl_dir of the dataset we have configured the ratelimit properties on. + * We walk down the dataset tree and set dd_ratelimit_root field to point to + * this dsl_dir until we find dsl_dir that also has the vfs_ratelimit structure + * already attached to it (which means it has its own limits configured). + * During the accounting it allows us for quick access to the ratelimit + * structure we need by just going to ds_dir->dd_ratelimit_root; + * If ratelimits are not configured on this dataset or any of its parents, + * the ds_dir->dd_ratelimit_root will be set to NULL, so we know we don't + * have to do any accounting. + * + * The limits are configured per second, but we divde the second and the limits + * into RATELIMIT_RESOLUTION slots (10 by default). This is to avoid a choking + * effect, when process is doing progress in 1s steps. For example if we have + * read bandwidth limits configured to 100MB/s and the process is trying to + * read 130MB, it will take 1.3 seconds, not 2 seconds. + * Not that very low limits may be rounded up - 7 ops/s limit will be rounded + * up to 10 ops/s, so each slot is assigned 1 op/s limit. This rounding up + * is done in the kernel and isn't shown in the properties value. + * + * How does the accounting work? + * + * When a request comes, we may need to consider multiple limits. + * For example a data read request of eg. 192kB (with 128kB recordsize) is + * accounted as 192kB bandwidth read, 192kB bandwidth total, two operations read + * and two operations total. Not all of those limits have to be configured or + * some might be configured on a dataset and others on a parent dataset(s). + * + * We remember those values in the rtslot structures at every level we have + * limits configured on. The rtslot strucuture also remembers the time of + * the request. For each ratelimit type (read bandwidth, total, operation read, + * operation total) and for each dataset with the limits configured when we walk + * the dataset tree up we find the point in time until which we have to wait to + * satisfy configured limit. We select the furthest point in time and we do to + * sleep. If the request doesn't exceed any limits, we just do the accounting + * and allow for the request to be executed immediately. + */ + +/* + * Number of slots we divide one second into. More granularity is better for + * interactivity, but it takes more memory and more calculations. + */ +#define RATELIMIT_RESOLUTION 16 + +struct vfs_ratelimit { + kmutex_t rl_lock; + uint64_t rl_limits[ZFS_RATELIMIT_NTYPES]; + /* List of current waiters and past activity. */ + list_t rl_list; +}; + +struct rtslot { + list_node_t rts_node; + hrtime_t rts_timeslot; + int rts_types; + uint64_t rts_counts[ZFS_RATELIMIT_NTYPES]; +}; + +int +vfs_ratelimit_prop_to_type(zfs_prop_t prop) +{ + + switch (prop) { + case ZFS_PROP_RATELIMIT_BW_READ: + return (ZFS_RATELIMIT_BW_READ); + case ZFS_PROP_RATELIMIT_BW_WRITE: + return (ZFS_RATELIMIT_BW_WRITE); + case ZFS_PROP_RATELIMIT_BW_TOTAL: + return (ZFS_RATELIMIT_BW_TOTAL); + case ZFS_PROP_RATELIMIT_OP_READ: + return (ZFS_RATELIMIT_OP_READ); + case ZFS_PROP_RATELIMIT_OP_WRITE: + return (ZFS_RATELIMIT_OP_WRITE); + case ZFS_PROP_RATELIMIT_OP_TOTAL: + return (ZFS_RATELIMIT_OP_TOTAL); + default: + panic("Invalid property %d", prop); + } +} + +zfs_prop_t +vfs_ratelimit_type_to_prop(int type) +{ + + switch (type) { + case ZFS_RATELIMIT_BW_READ: + return (ZFS_PROP_RATELIMIT_BW_READ); + case ZFS_RATELIMIT_BW_WRITE: + return (ZFS_PROP_RATELIMIT_BW_WRITE); + case ZFS_RATELIMIT_BW_TOTAL: + return (ZFS_PROP_RATELIMIT_BW_TOTAL); + case ZFS_RATELIMIT_OP_READ: + return (ZFS_PROP_RATELIMIT_OP_READ); + case ZFS_RATELIMIT_OP_WRITE: + return (ZFS_PROP_RATELIMIT_OP_WRITE); + case ZFS_RATELIMIT_OP_TOTAL: + return (ZFS_PROP_RATELIMIT_OP_TOTAL); + default: + panic("Invalid type %d", type); + } +} + +static boolean_t +ratelimit_is_none(const uint64_t *limits) +{ + + for (int i = ZFS_RATELIMIT_FIRST; i < ZFS_RATELIMIT_NTYPES; i++) { + if (limits[i] != 0) { + return (B_FALSE); + } + } + + return (B_TRUE); +} + +struct vfs_ratelimit * +vfs_ratelimit_alloc(const uint64_t *limits) +{ + struct vfs_ratelimit *rl; + int i; + + ASSERT(limits == NULL || !ratelimit_is_none(limits)); + + rl = kmem_zalloc(sizeof (*rl), KM_SLEEP); + + mutex_init(&rl->rl_lock, NULL, MUTEX_DEFAULT, NULL); + list_create(&rl->rl_list, sizeof (struct rtslot), + offsetof(struct rtslot, rts_node)); + /* Create two slots for a good start. */ + for (i = 0; i < 2; i++) { + list_insert_tail(&rl->rl_list, + kmem_zalloc(sizeof (struct rtslot), KM_SLEEP)); + } + + if (limits != NULL) { + for (i = ZFS_RATELIMIT_FIRST; i < ZFS_RATELIMIT_NTYPES; i++) { + uint64_t limit; + + /* + * We cannot have limits lower than RATELIMIT_RESOLUTION + * as they will effectively be zero, so unlimited. + */ + limit = limits[i]; + if (limit > 0 && limit < RATELIMIT_RESOLUTION) { + limit = RATELIMIT_RESOLUTION; + } + rl->rl_limits[i] = limit / RATELIMIT_RESOLUTION; + } + } + + return (rl); +} + +void +vfs_ratelimit_free(struct vfs_ratelimit *rl) +{ + struct rtslot *slot; + + if (rl == NULL) { + return; + } + + while ((slot = list_remove_head(&rl->rl_list)) != NULL) { + kmem_free(slot, sizeof (*slot)); + } + list_destroy(&rl->rl_list); + + mutex_destroy(&rl->rl_lock); + + kmem_free(rl, sizeof (*rl)); +} + +/* + * If this change will make all the limits to be 0, we free the vfs_ratelimit + * structure and return NULL. + */ +struct vfs_ratelimit * +vfs_ratelimit_set(struct vfs_ratelimit *rl, zfs_prop_t prop, uint64_t limit) +{ + int type; + + if (rl == NULL) { + if (limit == 0) { + return (NULL); + } else { + rl = vfs_ratelimit_alloc(NULL); + } + } + + type = vfs_ratelimit_prop_to_type(prop); + if (limit > 0 && limit < RATELIMIT_RESOLUTION) { + limit = RATELIMIT_RESOLUTION; + } + rl->rl_limits[type] = limit / RATELIMIT_RESOLUTION; + + if (ratelimit_is_none(rl->rl_limits)) { + vfs_ratelimit_free(rl); + return (NULL); + } + + return (rl); +} + +static __inline hrtime_t +gettimeslot(void) +{ + inode_timespec_t ts; + hrtime_t nsec; + + gethrestime(&ts); + nsec = ((hrtime_t)ts.tv_sec * NANOSEC) + ts.tv_nsec; + return (nsec / (NANOSEC / RATELIMIT_RESOLUTION)); +} + +/* + * Returns bit mask of the types configured for the given ratelimit structure. + */ +static int +ratelimit_types(const struct vfs_ratelimit *rl) +{ + int types, type; + + if (rl == NULL) { + return (0); + } + + types = 0; + for (type = ZFS_RATELIMIT_FIRST; type <= ZFS_RATELIMIT_LAST; type++) { + if (rl->rl_limits[type] > 0) { + types |= (1 << type); + } + } + + return (types); +} + +/* + * Returns the ratelimit structure that includes one of the requested types + * configured on the given dataset (os). If the given dataset doesn't have + * ratelimit structure for one of the types, we walk up dataset tree trying + * to find a dataset that has limits configured for one of the types we are + * interested in. + */ +static dsl_dir_t * +ratelimit_first(objset_t *os, int types) +{ + dsl_dir_t *dd; + int mytypes; + + ASSERT(RRM_READ_HELD(&os->os_spa->spa_ratelimit_lock)); + + dd = os->os_dsl_dataset->ds_dir->dd_ratelimit_root; + for (;;) { + if (dd == NULL) { + return (NULL); + } + mytypes = ratelimit_types(dd->dd_ratelimit); + if ((mytypes & types) != 0) { + /* + * This dataset has at last one limit we are + * interested in. + */ + return (dd); + } + if (dd->dd_parent == NULL) { + return (NULL); + } + dd = dd->dd_parent->dd_ratelimit_root; + } +} + +/* + * Returns the ratelimit structure of the parent dataset. If the parent dataset + * has no ratelimit structure configured or the ratelimit structure doesn't + * include any of the types we are interested in, we walk up and continue our + * search. + */ +static dsl_dir_t * +ratelimit_parent(dsl_dir_t *dd, int types) +{ + int mytypes; + + ASSERT(RRM_READ_HELD(&dd->dd_pool->dp_spa->spa_ratelimit_lock)); + + for (;;) { + if (dd->dd_parent == NULL) { + return (NULL); + } + dd = dd->dd_parent->dd_ratelimit_root; + if (dd == NULL) { + return (NULL); + } + mytypes = ratelimit_types(dd->dd_ratelimit); + if ((mytypes & types) != 0) { + /* + * This dataset has at last one limit we are + * interested in. + */ + return (dd); + } + } +} + +/* + * If we have any entries with 'timeslot > now' we also must have an entry with + * 'timeslot == now'. In other words if there is no entry with + * 'timeslot == now', it means that all the entires expired. + * + * We return either the most recent entry related to the given type or we return + * 'timeslot == now' entry not related to the given type and we will use it to + * store accouting information about this type as well. + */ +static struct rtslot * +ratelimit_find(struct vfs_ratelimit *rl, int typebit, hrtime_t now) +{ + struct rtslot *slot; + + ASSERT(MUTEX_HELD(&rl->rl_lock)); + + for (slot = list_head(&rl->rl_list); slot != NULL; + slot = list_next(&rl->rl_list, slot)) { + if (slot->rts_timeslot < now) { + break; + } + if ((slot->rts_types & typebit) != 0 || + slot->rts_timeslot == now) { + return (slot); + } + } + /* All the entries expired. */ +#ifndef NDEBUG + for (slot = list_head(&rl->rl_list); slot != NULL; + slot = list_next(&rl->rl_list, slot)) { + ASSERT(slot->rts_timeslot < now); + } +#endif + + return (NULL); +} + +/* + * Account for our request across all the types configured in this ratelimit + * structure. + * Return a timeslot we should wait for or now if we can execute the request + * without waiting (we are within limits). + */ +static uint64_t +ratelimit_account(struct vfs_ratelimit *rl, int types, hrtime_t now, + const uint64_t *counts) +{ + uint64_t timeslot; + int type, typebit; + + timeslot = 0; + + mutex_enter(&rl->rl_lock); + + for (type = ZFS_RATELIMIT_FIRST; type <= ZFS_RATELIMIT_LAST; type++) { + struct rtslot *slot; + uint64_t count, nexttimeslot; + + typebit = (1 << type); + + if ((types & typebit) == 0) { + /* Not interested in this type. */ + continue; + } + if (rl->rl_limits[type] == 0) { + /* This type has no limit configured on this dataset. */ + continue; + } + count = counts[type]; + ASSERT(count > 0); + + slot = ratelimit_find(rl, typebit, now); + if (slot == NULL) { + slot = list_remove_tail(&rl->rl_list); + ASSERT(slot->rts_timeslot < now); + slot->rts_types = typebit; + slot->rts_timeslot = now; + memset(slot->rts_counts, 0, sizeof (slot->rts_counts)); + list_insert_head(&rl->rl_list, slot); + } else if (slot->rts_timeslot == now) { + /* The 'now' slot may not have our type yet. */ + slot->rts_types |= typebit; + } + ASSERT((slot->rts_types & typebit) != 0); + nexttimeslot = slot->rts_timeslot + 1; + + for (;;) { + if (slot->rts_counts[type] + count <= + rl->rl_limits[type]) { + slot->rts_counts[type] += count; + break; + } + + /* + * This request is too big to fit into a single slot, + * ie. a single request exceeds the limit or this and + * the previous requests exceed the limit. + */ + + /* + * Fit as much as we can into the current slot. + */ + count -= rl->rl_limits[type] - slot->rts_counts[type]; + slot->rts_counts[type] = rl->rl_limits[type]; + + /* + * Take the next slot (if already exists isn't aware of + * our type yet), take an expired slot from the tail of + * the list or allocate a new slot. + */ + slot = list_prev(&rl->rl_list, slot); + if (slot != NULL) { + ASSERT((slot->rts_types & typebit) == 0); + ASSERT(slot->rts_timeslot == nexttimeslot); + ASSERT0(slot->rts_counts[type]); + + slot->rts_types |= typebit; + } else { + slot = list_tail(&rl->rl_list); + if (slot->rts_timeslot < now) { + list_remove(&rl->rl_list, slot); + } else { + slot = kmem_alloc(sizeof (*slot), + KM_SLEEP); + } + slot->rts_types = typebit; + slot->rts_timeslot = nexttimeslot; + memset(slot->rts_counts, 0, + sizeof (slot->rts_counts)); + list_insert_head(&rl->rl_list, slot); + } + + nexttimeslot++; + } + + if (timeslot < slot->rts_timeslot) { + timeslot = slot->rts_timeslot; + } + } + + mutex_exit(&rl->rl_lock); + + return (timeslot); +} + +static void +vfs_ratelimit(objset_t *os, int types, const uint64_t *counts) +{ + dsl_dir_t *dd; + hrtime_t now, timeslot; + + now = gettimeslot(); + timeslot = 0; + + /* + * Prevents configuration changes when we have requests in-flight. + */ + rrm_enter_read(&os->os_spa->spa_ratelimit_lock, FTAG); + + for (dd = ratelimit_first(os, types); dd != NULL; + dd = ratelimit_parent(dd, types)) { + hrtime_t ts; + + ts = ratelimit_account(dd->dd_ratelimit, types, now, counts); + if (ts > timeslot) { + timeslot = ts; + } + } + + rrm_exit(&os->os_spa->spa_ratelimit_lock, FTAG); + + if (timeslot > now) { + /* + * Too much traffic, slow it down. + */ + delay((hz / RATELIMIT_RESOLUTION) * (timeslot - now)); + } +} + +/* + * For every data read we charge: + * - bytes of read bandwidth + * - bytes of total bandwidth + * - (bytes - 1) / blocksize + 1 of read operations + * - (bytes - 1) / blocksize + 1 of total operations + */ +void +vfs_ratelimit_data_read(objset_t *os, size_t blocksize, size_t bytes) +{ + uint64_t counts[ZFS_RATELIMIT_NTYPES]; + unsigned int types; + + if (bytes == 0) { + return; + } + if (blocksize == 0) { + blocksize = bytes; + } + + types = (1 << ZFS_RATELIMIT_BW_READ); + types |= (1 << ZFS_RATELIMIT_BW_TOTAL); + types |= (1 << ZFS_RATELIMIT_OP_READ); + types |= (1 << ZFS_RATELIMIT_OP_TOTAL); + + memset(counts, 0, sizeof (counts)); + counts[ZFS_RATELIMIT_BW_READ] = bytes; + counts[ZFS_RATELIMIT_BW_TOTAL] = bytes; + counts[ZFS_RATELIMIT_OP_READ] = (bytes - 1) / blocksize + 1; + counts[ZFS_RATELIMIT_OP_TOTAL] = (bytes - 1) / blocksize + 1; + + vfs_ratelimit(os, types, counts); +} + +/* + * For every data write we charge: + * - bytes of write bandwidth + * - bytes of total bandwidth + * - (bytes - 1) / blocksize + 1 of write operations + * - (bytes - 1) / blocksize + 1 of total operations + */ +void +vfs_ratelimit_data_write(objset_t *os, size_t blocksize, size_t bytes) +{ + uint64_t counts[ZFS_RATELIMIT_NTYPES]; + unsigned int types; + + if (bytes == 0) { + return; + } + if (blocksize == 0) { + blocksize = bytes; + } + + types = (1 << ZFS_RATELIMIT_BW_WRITE); + types |= (1 << ZFS_RATELIMIT_BW_TOTAL); + types |= (1 << ZFS_RATELIMIT_OP_WRITE); + types |= (1 << ZFS_RATELIMIT_OP_TOTAL); + + memset(counts, 0, sizeof (counts)); + counts[ZFS_RATELIMIT_BW_WRITE] = bytes; + counts[ZFS_RATELIMIT_BW_TOTAL] = bytes; + counts[ZFS_RATELIMIT_OP_WRITE] = (bytes - 1) / blocksize + 1; + counts[ZFS_RATELIMIT_OP_TOTAL] = (bytes - 1) / blocksize + 1; + + vfs_ratelimit(os, types, counts); +} + +/* + * For every metadata read we charge: + * - one read operation + * - one total operation + */ +void +vfs_ratelimit_metadata_read(objset_t *os) +{ + uint64_t counts[ZFS_RATELIMIT_NTYPES]; + unsigned int types; + + types = (1 << ZFS_RATELIMIT_OP_READ); + types |= (1 << ZFS_RATELIMIT_OP_TOTAL); + + memset(counts, 0, sizeof (counts)); + counts[ZFS_RATELIMIT_OP_READ] = 1; + counts[ZFS_RATELIMIT_OP_TOTAL] = 1; + + vfs_ratelimit(os, types, counts); +} + +/* + * For every metadata write we charge: + * - one read operation + * - one total operation + */ +void +vfs_ratelimit_metadata_write(objset_t *os) +{ + uint64_t counts[ZFS_RATELIMIT_NTYPES]; + unsigned int types; + + types = (1 << ZFS_RATELIMIT_OP_WRITE); + types |= (1 << ZFS_RATELIMIT_OP_TOTAL); + + memset(counts, 0, sizeof (counts)); + counts[ZFS_RATELIMIT_OP_WRITE] = 1; + counts[ZFS_RATELIMIT_OP_TOTAL] = 1; + + vfs_ratelimit(os, types, counts); +} diff --git a/module/zfs/zfs_ioctl.c b/module/zfs/zfs_ioctl.c index b720b4f222b1..02fe1464af49 100644 --- a/module/zfs/zfs_ioctl.c +++ b/module/zfs/zfs_ioctl.c @@ -623,6 +623,12 @@ zfs_secpolicy_setprop(const char *dsname, zfs_prop_t prop, nvpair_t *propval, case ZFS_PROP_QUOTA: case ZFS_PROP_FILESYSTEM_LIMIT: case ZFS_PROP_SNAPSHOT_LIMIT: + case ZFS_PROP_RATELIMIT_BW_READ: + case ZFS_PROP_RATELIMIT_BW_WRITE: + case ZFS_PROP_RATELIMIT_BW_TOTAL: + case ZFS_PROP_RATELIMIT_OP_READ: + case ZFS_PROP_RATELIMIT_OP_WRITE: + case ZFS_PROP_RATELIMIT_OP_TOTAL: if (!INGLOBALZONE(curproc)) { uint64_t zoned; char setpoint[ZFS_MAX_DATASET_NAME_LEN]; @@ -2495,6 +2501,16 @@ zfs_prop_set_special(const char *dsname, zprop_source_t source, if (err == 0) err = -1; break; + case ZFS_PROP_RATELIMIT_BW_READ: + case ZFS_PROP_RATELIMIT_BW_WRITE: + case ZFS_PROP_RATELIMIT_BW_TOTAL: + case ZFS_PROP_RATELIMIT_OP_READ: + case ZFS_PROP_RATELIMIT_OP_WRITE: + case ZFS_PROP_RATELIMIT_OP_TOTAL: + err = dsl_dir_set_ratelimit(dsname, prop, intval); + if (err == 0) + err = -1; + break; case ZFS_PROP_KEYLOCATION: err = dsl_crypto_can_set_keylocation(dsname, strval); diff --git a/module/zfs/zfs_vnops.c b/module/zfs/zfs_vnops.c index babb07ca25a9..a0b35d13dc28 100644 --- a/module/zfs/zfs_vnops.c +++ b/module/zfs/zfs_vnops.c @@ -53,6 +53,7 @@ #include #include #include +#include #include #include #include @@ -297,6 +298,9 @@ zfs_read(struct znode *zp, zfs_uio_t *uio, int ioflag, cred_t *cr) while (n > 0) { ssize_t nbytes = MIN(n, zfs_vnops_read_chunk_size - P2PHASE(zfs_uio_offset(uio), zfs_vnops_read_chunk_size)); + + vfs_ratelimit_data_read(zfsvfs->z_os, zp->z_blksz, nbytes); + #ifdef UIO_NOCOPY if (zfs_uio_segflg(uio) == UIO_NOCOPY) error = mappedread_sf(zp, nbytes, uio); @@ -610,6 +614,8 @@ zfs_write(znode_t *zp, zfs_uio_t *uio, int ioflag, cred_t *cr) } } + vfs_ratelimit_data_write(zfsvfs->z_os, blksz, nbytes); + /* * Start a transaction. */ @@ -1309,6 +1315,9 @@ zfs_clone_range(znode_t *inzp, uint64_t *inoffp, znode_t *outzp, break; } + vfs_ratelimit_data_read(inos, inblksz, size); + vfs_ratelimit_data_write(outos, inblksz, size); + nbps = maxblocks; last_synced_txg = spa_last_synced_txg(dmu_objset_spa(inos)); error = dmu_read_l0_bps(inos, inzp->z_id, inoff, size, bps, diff --git a/tests/Makefile.am b/tests/Makefile.am index 12e9c9f9daf2..726ee8d75abf 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -22,6 +22,7 @@ dist_scripts_runfiles_DATA = \ %D%/runfiles/linux.run \ %D%/runfiles/longevity.run \ %D%/runfiles/perf-regression.run \ + %D%/runfiles/ratelimit.run \ %D%/runfiles/sanity.run \ %D%/runfiles/sunos.run diff --git a/tests/runfiles/bclone.run b/tests/runfiles/bclone.run index 3d0f545d9226..69821a669a12 100644 --- a/tests/runfiles/bclone.run +++ b/tests/runfiles/bclone.run @@ -8,9 +8,7 @@ # source. A copy of the CDDL is also available via the Internet at # http://www.illumos.org/license/CDDL. # -# This run file contains all of the common functional tests. When -# adding a new test consider also adding it to the sanity.run file -# if the new test runs to completion in only a few seconds. +# This run file contains all tests related to the block cloning functionality. # # Approximate run time: 5 hours # diff --git a/tests/runfiles/common.run b/tests/runfiles/common.run index ac2c541a9188..629b615196cd 100644 --- a/tests/runfiles/common.run +++ b/tests/runfiles/common.run @@ -807,6 +807,10 @@ tests = ['quota_001_pos', 'quota_002_pos', 'quota_003_pos', 'quota_004_pos', 'quota_005_pos', 'quota_006_neg'] tags = ['functional', 'quota'] +[tests/functional/ratelimit] +tests = ['ratelimit_random'] +tags = ['functional', 'ratelimit'] + [tests/functional/redacted_send] tests = ['redacted_compressed', 'redacted_contents', 'redacted_deleted', 'redacted_disabled_feature', 'redacted_embedded', 'redacted_holes', diff --git a/tests/runfiles/ratelimit.run b/tests/runfiles/ratelimit.run new file mode 100644 index 000000000000..20098cbfdc43 --- /dev/null +++ b/tests/runfiles/ratelimit.run @@ -0,0 +1,51 @@ +# +# This file and its contents are supplied under the terms of the +# Common Development and Distribution License ("CDDL"), version 1.0. +# You may only use this file in accordance with the terms of version +# 1.0 of the CDDL. +# +# A full copy of the text of the CDDL should have accompanied this +# source. A copy of the CDDL is also available via the Internet at +# http://www.illumos.org/license/CDDL. +# +# This run file contains all tests related to the ratelimit functionality. +# +# Approximate run time: 1 hour 10 minutes +# + +[DEFAULT] +pre = setup +quiet = False +pre_user = root +user = root +timeout = 7200 +post_user = root +post = cleanup +failsafe_user = root +failsafe = callbacks/zfs_failsafe +outputdir = /var/tmp/test_results +tags = ['ratelimit'] + +[tests/functional/ratelimit] +tests = ['filesystem_bw_combined', + 'filesystem_bw_hierarchical_horizontal', + 'filesystem_bw_hierarchical_vertical_read', + 'filesystem_bw_hierarchical_vertical_total', + 'filesystem_bw_hierarchical_vertical_write', + 'filesystem_bw_multiple', + 'filesystem_bw_recv', + 'filesystem_bw_send', + 'filesystem_bw_single', + 'filesystem_op_single', + 'filesystem_op_multiple', + 'inheritance', + 'volume_bw_combined', + 'volume_bw_hierarchical_horizontal', + 'volume_bw_hierarchical_vertical_read', + 'volume_bw_hierarchical_vertical_total', + 'volume_bw_hierarchical_vertical_write', + 'volume_bw_multiple', + 'volume_bw_recv', + 'volume_bw_send', + 'volume_bw_single'] +tags = ['ratelimit'] diff --git a/tests/zfs-tests/cmd/.gitignore b/tests/zfs-tests/cmd/.gitignore index 0ed0a69eb013..92717126a2c2 100644 --- a/tests/zfs-tests/cmd/.gitignore +++ b/tests/zfs-tests/cmd/.gitignore @@ -12,6 +12,7 @@ /file_check /file_trunc /file_write +/fsop /get_diff /getversion /largest_file diff --git a/tests/zfs-tests/cmd/Makefile.am b/tests/zfs-tests/cmd/Makefile.am index 23848a82ffbd..c92cc37abbd6 100644 --- a/tests/zfs-tests/cmd/Makefile.am +++ b/tests/zfs-tests/cmd/Makefile.am @@ -9,6 +9,7 @@ scripts_zfs_tests_bin_PROGRAMS += %D%/cp_files scripts_zfs_tests_bin_PROGRAMS += %D%/ctime scripts_zfs_tests_bin_PROGRAMS += %D%/dir_rd_update scripts_zfs_tests_bin_PROGRAMS += %D%/dosmode_readonly_write +scripts_zfs_tests_bin_PROGRAMS += %D%/fsop scripts_zfs_tests_bin_PROGRAMS += %D%/get_diff scripts_zfs_tests_bin_PROGRAMS += %D%/rename_dir scripts_zfs_tests_bin_PROGRAMS += %D%/suid_write_to_file diff --git a/tests/zfs-tests/cmd/fsop.c b/tests/zfs-tests/cmd/fsop.c new file mode 100644 index 000000000000..cc9bfdb0d6fe --- /dev/null +++ b/tests/zfs-tests/cmd/fsop.c @@ -0,0 +1,243 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or https://opensource.org/licenses/CDDL-1.0. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ + +/* + * Copyright (c) 2024 The FreeBSD Foundation + * + * This software was developed by Pawel Dawidek + * under sponsorship from the FreeBSD Foundation. + */ + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#ifndef __unused +#define __unused __attribute__((__unused__)) +#endif + +static const char *progname; + +static void +usage(void) +{ + + (void) fprintf(stderr, "usage: %s \n", progname); + (void) fprintf(stderr, " chmod \n"); + (void) fprintf(stderr, " chown \n"); + (void) fprintf(stderr, " create \n"); + (void) fprintf(stderr, " link \n"); + (void) fprintf(stderr, " mkdir \n"); + (void) fprintf(stderr, " readlink \n"); + (void) fprintf(stderr, " rmdir \n"); + (void) fprintf(stderr, " stat \n"); + (void) fprintf(stderr, " symlink \n"); + (void) fprintf(stderr, " unlink \n"); + exit(3); +} + +static bool +fsop_chmod(int i __unused, const char *path) +{ + + return (chmod(path, 0600) == 0); +} + +static bool +fsop_chown(int i __unused, const char *path) +{ + + return (chown(path, 0, 0) == 0); +} + +static bool +fsop_create(int i, const char *base) +{ + char path[MAXPATHLEN]; + int fd; + + snprintf(path, sizeof (path), "%s.%d", base, i); + + fd = open(path, O_CREAT | O_EXCL, 0600); + if (fd < 0) { + return (false); + } + close(fd); + return (true); +} + +static bool +fsop_link(int i, const char *base) +{ + char path[MAXPATHLEN]; + + snprintf(path, sizeof (path), "%s.%d", base, i); + + return (link(base, path) == 0); +} + +static bool +fsop_stat(int i __unused, const char *path) +{ + struct stat sb; + + return (stat(path, &sb) == 0); +} + +static bool +fsop_mkdir(int i, const char *base) +{ + char path[MAXPATHLEN]; + + snprintf(path, sizeof (path), "%s.%d", base, i); + + return (mkdir(path, 0700) == 0); +} + +static bool +fsop_readlink(int i __unused, const char *symlink) +{ + char path[MAXPATHLEN]; + + return (readlink(symlink, path, sizeof (path)) >= 0); +} + +static bool +fsop_rename(int i, const char *base) +{ + char path[MAXPATHLEN]; + const char *src, *dst; + + snprintf(path, sizeof (path), "%s.renamed", base); + + if ((i & 1) == 0) { + src = base; + dst = path; + } else { + src = path; + dst = base; + } + + return (rename(src, dst) == 0); +} + +static bool +fsop_rmdir(int i, const char *base) +{ + char path[MAXPATHLEN]; + + snprintf(path, sizeof (path), "%s.%d", base, i); + + return (rmdir(path) == 0); +} + +static bool +fsop_symlink(int i, const char *base) +{ + char path[MAXPATHLEN]; + + snprintf(path, sizeof (path), "%s.%d", base, i); + + return (symlink(base, path) == 0); +} + +static bool +fsop_unlink(int i, const char *base) +{ + char path[MAXPATHLEN]; + + snprintf(path, sizeof (path), "%s.%d", base, i); + + return (unlink(path) == 0); +} + +static struct fsop { + const char *fo_syscall; + bool (*fo_handler)(int, const char *); +} fsops[] = { + { "chmod", fsop_chmod }, + { "chown", fsop_chown }, + { "create", fsop_create }, + { "link", fsop_link }, + { "mkdir", fsop_mkdir }, + { "readlink", fsop_readlink }, + { "rename", fsop_rename }, + { "rmdir", fsop_rmdir }, + { "stat", fsop_stat }, + { "symlink", fsop_symlink }, + { "unlink", fsop_unlink } +}; + +int +main(int argc, char *argv[]) +{ + struct fsop *fsop; + const char *syscall; + int count; + + progname = argv[0]; + + if (argc < 3) { + usage(); + } + + count = atoi(argv[1]); + if (count <= 0) { + (void) fprintf(stderr, "invalid count\n"); + exit(2); + } + syscall = argv[2]; + argc -= 3; + argv += 3; + if (argc != 1) { + usage(); + } + + fsop = NULL; + for (unsigned int i = 0; i < sizeof (fsops) / sizeof (fsops[0]); i++) { + if (strcmp(fsops[i].fo_syscall, syscall) == 0) { + fsop = &fsops[i]; + break; + } + } + if (fsop == NULL) { + fprintf(stderr, "Unknown syscall: %s\n", syscall); + exit(2); + } + + for (int i = 0; i < count; i++) { + if (!fsop->fo_handler(i, argv[0])) { + fprintf(stderr, "%s() failed: %s\n", syscall, + strerror(errno)); + exit(1); + } + } + + exit(0); +} diff --git a/tests/zfs-tests/include/commands.cfg b/tests/zfs-tests/include/commands.cfg index daa794551682..6937e646e384 100644 --- a/tests/zfs-tests/include/commands.cfg +++ b/tests/zfs-tests/include/commands.cfg @@ -195,6 +195,7 @@ export ZFSTEST_FILES='badsend file_check file_trunc file_write + fsop get_diff getversion largest_file diff --git a/tests/zfs-tests/include/libtest.shlib b/tests/zfs-tests/include/libtest.shlib index dfab48d2cdaf..d0d3dd742f27 100644 --- a/tests/zfs-tests/include/libtest.shlib +++ b/tests/zfs-tests/include/libtest.shlib @@ -1626,6 +1626,38 @@ function create_dataset #dataset dataset_options return 0 } +# Return 0 if created successfully; $? otherwise +# +# $1 - dataset name +# $2 - dataset size +# $3-n - dataset options + +function create_volume #dataset size dataset_options +{ + typeset dataset=$1 + typeset size=$2 + + shift 2 + + if [[ -z $dataset ]]; then + log_note "Missing dataset name." + return 1 + fi + if [[ -z $size ]]; then + log_note "Missing dataset size." + return 1 + fi + + if datasetexists $dataset ; then + destroy_dataset $dataset + fi + + log_must zfs create -V $size $@ $dataset + is_linux && zvol_wait + + return 0 +} + # Return 0 if destroy successfully or the dataset exists; $? otherwise # Note: In local zones, this function should return 0 silently. # diff --git a/tests/zfs-tests/tests/Makefile.am b/tests/zfs-tests/tests/Makefile.am index 44eedcf6fae5..e4fae3a0a907 100644 --- a/tests/zfs-tests/tests/Makefile.am +++ b/tests/zfs-tests/tests/Makefile.am @@ -346,6 +346,7 @@ nobase_dist_datadir_zfs_tests_tests_DATA += \ functional/projectquota/projectquota_common.kshlib \ functional/quota/quota.cfg \ functional/quota/quota.kshlib \ + functional/ratelimit/ratelimit_common.kshlib \ functional/redacted_send/redacted.cfg \ functional/redacted_send/redacted.kshlib \ functional/redundancy/redundancy.cfg \ @@ -1720,6 +1721,30 @@ nobase_dist_datadir_zfs_tests_tests_SCRIPTS += \ functional/raidz/raidz_expand_006_neg.ksh \ functional/raidz/raidz_expand_007_neg.ksh \ functional/raidz/setup.ksh \ + functional/ratelimit/cleanup.ksh \ + functional/ratelimit/filesystem_bw_combined.ksh \ + functional/ratelimit/filesystem_bw_hierarchical_horizontal.ksh \ + functional/ratelimit/filesystem_bw_hierarchical_vertical_read.ksh \ + functional/ratelimit/filesystem_bw_hierarchical_vertical_total.ksh \ + functional/ratelimit/filesystem_bw_hierarchical_vertical_write.ksh \ + functional/ratelimit/filesystem_bw_multiple.ksh \ + functional/ratelimit/filesystem_bw_recv.ksh \ + functional/ratelimit/filesystem_bw_send.ksh \ + functional/ratelimit/filesystem_bw_single.ksh \ + functional/ratelimit/filesystem_op_multiple.ksh \ + functional/ratelimit/filesystem_op_single.ksh \ + functional/ratelimit/inheritance.ksh \ + functional/ratelimit/ratelimit_random.ksh \ + functional/ratelimit/setup.ksh \ + functional/ratelimit/volume_bw_combined.ksh \ + functional/ratelimit/volume_bw_hierarchical_horizontal.ksh \ + functional/ratelimit/volume_bw_hierarchical_vertical_read.ksh \ + functional/ratelimit/volume_bw_hierarchical_vertical_total.ksh \ + functional/ratelimit/volume_bw_hierarchical_vertical_write.ksh \ + functional/ratelimit/volume_bw_multiple.ksh \ + functional/ratelimit/volume_bw_recv.ksh \ + functional/ratelimit/volume_bw_send.ksh \ + functional/ratelimit/volume_bw_single.ksh \ functional/redacted_send/cleanup.ksh \ functional/redacted_send/redacted_compressed.ksh \ functional/redacted_send/redacted_contents.ksh \ diff --git a/tests/zfs-tests/tests/functional/ratelimit/cleanup.ksh b/tests/zfs-tests/tests/functional/ratelimit/cleanup.ksh new file mode 100755 index 000000000000..6f44bcd541e0 --- /dev/null +++ b/tests/zfs-tests/tests/functional/ratelimit/cleanup.ksh @@ -0,0 +1,34 @@ +#!/bin/ksh -p +# +# CDDL HEADER START +# +# The contents of this file are subject to the terms of the +# Common Development and Distribution License (the "License"). +# You may not use this file except in compliance with the License. +# +# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE +# or https://opensource.org/licenses/CDDL-1.0. +# See the License for the specific language governing permissions +# and limitations under the License. +# +# When distributing Covered Code, include this CDDL HEADER in each +# file and include the License file at usr/src/OPENSOLARIS.LICENSE. +# If applicable, add the following below this CDDL HEADER, with the +# fields enclosed by brackets "[]" replaced with your own identifying +# information: Portions Copyright [yyyy] [name of copyright owner] +# +# CDDL HEADER END +# + +# +# Copyright (c) 2024 The FreeBSD Foundation +# +# This software was developed by Pawel Dawidek +# under sponsorship from the FreeBSD Foundation. +# + +. $STF_SUITE/include/libtest.shlib + +default_cleanup_noexit + +log_pass diff --git a/tests/zfs-tests/tests/functional/ratelimit/filesystem_bw_combined.ksh b/tests/zfs-tests/tests/functional/ratelimit/filesystem_bw_combined.ksh new file mode 100755 index 000000000000..d2177f2b2888 --- /dev/null +++ b/tests/zfs-tests/tests/functional/ratelimit/filesystem_bw_combined.ksh @@ -0,0 +1,104 @@ +#! /bin/ksh -p +# +# CDDL HEADER START +# +# The contents of this file are subject to the terms of the +# Common Development and Distribution License (the "License"). +# You may not use this file except in compliance with the License. +# +# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE +# or https://opensource.org/licenses/CDDL-1.0. +# See the License for the specific language governing permissions +# and limitations under the License. +# +# When distributing Covered Code, include this CDDL HEADER in each +# file and include the License file at usr/src/OPENSOLARIS.LICENSE. +# If applicable, add the following below this CDDL HEADER, with the +# fields enclosed by brackets "[]" replaced with your own identifying +# information: Portions Copyright [yyyy] [name of copyright owner] +# +# CDDL HEADER END +# + +# +# Copyright (c) 2024 The FreeBSD Foundation +# +# This software was developed by Pawel Dawidek +# under sponsorship from the FreeBSD Foundation. +# + +. $STF_SUITE/include/libtest.shlib +. $STF_SUITE/tests/functional/ratelimit/ratelimit_common.kshlib + +verify_runnable "both" + +log_assert "Verify configurations where multiple limit types are set" + +ratelimit_reset + +log_must truncate -s 1G "$TESTDIR/file" + +log_must zfs set limit_bw_read=4M "$TESTPOOL/$TESTFS" +log_must zfs set limit_bw_write=5M "$TESTPOOL/$TESTFS" +log_must zfs set limit_bw_total=6M "$TESTPOOL/$TESTFS" + +log_must ratelimit_bw_read 12 3 "$TESTDIR/file" +log_must ratelimit_bw_write 15 3 "$TESTDIR/file" +stopwatch_start +ddio "$TESTDIR/file" "/dev/null" 36 & +ddio "/dev/zero" "$TESTDIR/file" 36 & +wait +stopwatch_check 12 + +log_must zfs set limit_bw_read=none "$TESTPOOL/$TESTFS" +log_must zfs set limit_bw_write=none "$TESTPOOL/$TESTFS" +log_must zfs set limit_bw_total=none "$TESTPOOL/$TESTFS" + +rm -f "$TESTDIR/file" + +log_must create_dataset "$TESTPOOL/$TESTFS/lvl0" +log_must create_dataset "$TESTPOOL/$TESTFS/lvl0/lvl1" +log_must create_dataset "$TESTPOOL/$TESTFS/lvl0/lvl1/lvl2" + +log_must truncate -s 1G "$TESTDIR/lvl0/lvl1/lvl2/file0" +log_must truncate -s 1G "$TESTDIR/lvl0/lvl1/lvl2/file1" + +log_must zfs set limit_bw_read=4M "$TESTPOOL/$TESTFS/lvl0" +log_must zfs set limit_bw_write=5M "$TESTPOOL/$TESTFS/lvl0/lvl1" +log_must zfs set limit_bw_total=6M "$TESTPOOL/$TESTFS/lvl0/lvl1/lvl2" + +log_must ratelimit_bw_read 12 3 "$TESTDIR/lvl0/lvl1/lvl2/file0" +log_must ratelimit_bw_write 15 3 "$TESTDIR/lvl0/lvl1/lvl2/file1" +stopwatch_start +ddio "$TESTDIR/lvl0/lvl1/lvl2/file0" "/dev/null" 36 & +ddio "/dev/zero" "$TESTDIR/lvl0/lvl1/lvl2/file1" 36 & +wait +stopwatch_check 12 + +log_must zfs set limit_bw_read=none "$TESTPOOL/$TESTFS/lvl0" +log_must zfs set limit_bw_write=none "$TESTPOOL/$TESTFS/lvl0/lvl1" +log_must zfs set limit_bw_total=none "$TESTPOOL/$TESTFS/lvl0/lvl1/lvl2" + +log_must zfs set limit_bw_total=6M "$TESTPOOL/$TESTFS/lvl0" +log_must zfs set limit_bw_read=4M "$TESTPOOL/$TESTFS/lvl0/lvl1" +log_must zfs set limit_bw_write=5M "$TESTPOOL/$TESTFS/lvl0/lvl1/lvl2" + +log_must ratelimit_bw_read 12 3 "$TESTDIR/lvl0/lvl1/lvl2/file0" +log_must ratelimit_bw_write 15 3 "$TESTDIR/lvl0/lvl1/lvl2/file1" +stopwatch_start +ddio "$TESTDIR/lvl0/lvl1/lvl2/file0" "/dev/null" 36 & +ddio "/dev/zero" "$TESTDIR/lvl0/lvl1/lvl2/file1" 36 & +wait +stopwatch_check 12 + +log_must zfs set limit_bw_total=none "$TESTPOOL/$TESTFS/lvl0" +log_must zfs set limit_bw_read=none "$TESTPOOL/$TESTFS/lvl0/lvl1" +log_must zfs set limit_bw_write=none "$TESTPOOL/$TESTFS/lvl0/lvl1/lvl2" + +log_must destroy_dataset "$TESTPOOL/$TESTFS/lvl0/lvl1/lvl2" +log_must destroy_dataset "$TESTPOOL/$TESTFS/lvl0/lvl1" +log_must destroy_dataset "$TESTPOOL/$TESTFS/lvl0" + +rm -f "$TESTDIR/lvl0/lvl1/lvl2/file0" "$TESTDIR/lvl0/lvl1/lvl2/file1" + +log_pass diff --git a/tests/zfs-tests/tests/functional/ratelimit/filesystem_bw_hierarchical_horizontal.ksh b/tests/zfs-tests/tests/functional/ratelimit/filesystem_bw_hierarchical_horizontal.ksh new file mode 100755 index 000000000000..cb06cb88baa6 --- /dev/null +++ b/tests/zfs-tests/tests/functional/ratelimit/filesystem_bw_hierarchical_horizontal.ksh @@ -0,0 +1,214 @@ +#! /bin/ksh -p +# +# CDDL HEADER START +# +# The contents of this file are subject to the terms of the +# Common Development and Distribution License (the "License"). +# You may not use this file except in compliance with the License. +# +# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE +# or https://opensource.org/licenses/CDDL-1.0. +# See the License for the specific language governing permissions +# and limitations under the License. +# +# When distributing Covered Code, include this CDDL HEADER in each +# file and include the License file at usr/src/OPENSOLARIS.LICENSE. +# If applicable, add the following below this CDDL HEADER, with the +# fields enclosed by brackets "[]" replaced with your own identifying +# information: Portions Copyright [yyyy] [name of copyright owner] +# +# CDDL HEADER END +# + +# +# Copyright (c) 2024 The FreeBSD Foundation +# +# This software was developed by Pawel Dawidek +# under sponsorship from the FreeBSD Foundation. +# + +. $STF_SUITE/include/libtest.shlib +. $STF_SUITE/tests/functional/ratelimit/ratelimit_common.kshlib + +verify_runnable "both" + +log_assert "Verify hierarchical limits for multiple file systems at the same level" + +ratelimit_reset + +log_must create_dataset "$TESTPOOL/$TESTFS/foo" +log_must create_dataset "$TESTPOOL/$TESTFS/bar" +log_must create_dataset "$TESTPOOL/$TESTFS/baz" + +log_must truncate -s 1G "$TESTDIR/file" +log_must truncate -s 1G "$TESTDIR/foo/file" +log_must truncate -s 1G "$TESTDIR/bar/file" +log_must truncate -s 1G "$TESTDIR/baz/file" + +log_must zfs set limit_bw_read=none "$TESTPOOL/$TESTFS" +log_must zfs set limit_bw_read=1M "$TESTPOOL/$TESTFS/foo" +log_must zfs set limit_bw_read=1M "$TESTPOOL/$TESTFS/bar" +log_must zfs set limit_bw_read=1M "$TESTPOOL/$TESTFS/baz" +log_must ratelimit_bw_read 6 6 "$TESTDIR/foo/file" +log_must ratelimit_bw_read 6 6 "$TESTDIR/foo/file" "$TESTDIR/bar/file" +log_must ratelimit_bw_read 6 6 "$TESTDIR/foo/file" "$TESTDIR/bar/file" "$TESTDIR/baz/file" +log_must ratelimit_bw_read 6 6 "$TESTDIR/file" "$TESTDIR/foo/file" "$TESTDIR/bar/file" "$TESTDIR/baz/file" + +log_must zfs set limit_bw_read=2M "$TESTPOOL/$TESTFS" +log_must zfs set limit_bw_read=1M "$TESTPOOL/$TESTFS/foo" +log_must zfs set limit_bw_read=1M "$TESTPOOL/$TESTFS/bar" +log_must zfs set limit_bw_read=1M "$TESTPOOL/$TESTFS/baz" +log_must ratelimit_bw_read 6 6 "$TESTDIR/foo/file" "$TESTDIR/bar/file" +log_must ratelimit_bw_read 6 9 "$TESTDIR/foo/file" "$TESTDIR/bar/file" "$TESTDIR/baz/file" +log_must ratelimit_bw_read 6 12 "$TESTDIR/file" "$TESTDIR/foo/file" "$TESTDIR/bar/file" "$TESTDIR/baz/file" + +log_must zfs set limit_bw_read=2M "$TESTPOOL/$TESTFS" +log_must zfs set limit_bw_read=2M "$TESTPOOL/$TESTFS/foo" +log_must zfs set limit_bw_read=2M "$TESTPOOL/$TESTFS/bar" +log_must zfs set limit_bw_read=2M "$TESTPOOL/$TESTFS/baz" +log_must ratelimit_bw_read 6 3 "$TESTDIR/foo/file" +log_must ratelimit_bw_read 6 6 "$TESTDIR/foo/file" "$TESTDIR/bar/file" +log_must ratelimit_bw_read 6 9 "$TESTDIR/foo/file" "$TESTDIR/bar/file" "$TESTDIR/baz/file" +log_must ratelimit_bw_read 6 12 "$TESTDIR/file" "$TESTDIR/foo/file" "$TESTDIR/bar/file" "$TESTDIR/baz/file" + +log_must zfs set limit_bw_read=2M "$TESTPOOL/$TESTFS" +log_must zfs set limit_bw_read=none "$TESTPOOL/$TESTFS/foo" +log_must zfs set limit_bw_read=none "$TESTPOOL/$TESTFS/bar" +log_must zfs set limit_bw_read=none "$TESTPOOL/$TESTFS/baz" +log_must ratelimit_bw_read 6 3 "$TESTDIR/foo/file" +log_must ratelimit_bw_read 6 6 "$TESTDIR/foo/file" "$TESTDIR/bar/file" +log_must ratelimit_bw_read 6 9 "$TESTDIR/foo/file" "$TESTDIR/bar/file" "$TESTDIR/baz/file" +log_must ratelimit_bw_read 6 12 "$TESTDIR/file" "$TESTDIR/foo/file" "$TESTDIR/bar/file" "$TESTDIR/baz/file" + +log_must zfs set limit_bw_read=none "$TESTPOOL/$TESTFS" +log_must zfs set limit_bw_read=none "$TESTPOOL/$TESTFS/foo" +log_must zfs set limit_bw_read=none "$TESTPOOL/$TESTFS/bar" +log_must zfs set limit_bw_read=none "$TESTPOOL/$TESTFS/baz" + +log_must zfs set limit_bw_total=none "$TESTPOOL/$TESTFS" +log_must zfs set limit_bw_total=1M "$TESTPOOL/$TESTFS/foo" +log_must zfs set limit_bw_total=1M "$TESTPOOL/$TESTFS/bar" +log_must zfs set limit_bw_total=1M "$TESTPOOL/$TESTFS/baz" +log_must ratelimit_bw_read 6 6 "$TESTDIR/foo/file" +log_must ratelimit_bw_read 6 6 "$TESTDIR/foo/file" "$TESTDIR/bar/file" +log_must ratelimit_bw_read 6 6 "$TESTDIR/foo/file" "$TESTDIR/bar/file" "$TESTDIR/baz/file" +log_must ratelimit_bw_read 6 6 "$TESTDIR/file" "$TESTDIR/foo/file" "$TESTDIR/bar/file" "$TESTDIR/baz/file" + +log_must zfs set limit_bw_total=2M "$TESTPOOL/$TESTFS" +log_must zfs set limit_bw_total=1M "$TESTPOOL/$TESTFS/foo" +log_must zfs set limit_bw_total=1M "$TESTPOOL/$TESTFS/bar" +log_must zfs set limit_bw_total=1M "$TESTPOOL/$TESTFS/baz" +log_must ratelimit_bw_read 6 6 "$TESTDIR/foo/file" "$TESTDIR/bar/file" +log_must ratelimit_bw_read 6 9 "$TESTDIR/foo/file" "$TESTDIR/bar/file" "$TESTDIR/baz/file" +log_must ratelimit_bw_read 6 12 "$TESTDIR/file" "$TESTDIR/foo/file" "$TESTDIR/bar/file" "$TESTDIR/baz/file" + +log_must zfs set limit_bw_total=2M "$TESTPOOL/$TESTFS" +log_must zfs set limit_bw_total=2M "$TESTPOOL/$TESTFS/foo" +log_must zfs set limit_bw_total=2M "$TESTPOOL/$TESTFS/bar" +log_must zfs set limit_bw_total=2M "$TESTPOOL/$TESTFS/baz" +log_must ratelimit_bw_read 6 3 "$TESTDIR/foo/file" +log_must ratelimit_bw_read 6 6 "$TESTDIR/foo/file" "$TESTDIR/bar/file" +log_must ratelimit_bw_read 6 9 "$TESTDIR/foo/file" "$TESTDIR/bar/file" "$TESTDIR/baz/file" +log_must ratelimit_bw_read 6 12 "$TESTDIR/file" "$TESTDIR/foo/file" "$TESTDIR/bar/file" "$TESTDIR/baz/file" + +log_must zfs set limit_bw_total=2M "$TESTPOOL/$TESTFS" +log_must zfs set limit_bw_total=none "$TESTPOOL/$TESTFS/foo" +log_must zfs set limit_bw_total=none "$TESTPOOL/$TESTFS/bar" +log_must zfs set limit_bw_total=none "$TESTPOOL/$TESTFS/baz" +log_must ratelimit_bw_read 6 3 "$TESTDIR/foo/file" +log_must ratelimit_bw_read 6 6 "$TESTDIR/foo/file" "$TESTDIR/bar/file" +log_must ratelimit_bw_read 6 9 "$TESTDIR/foo/file" "$TESTDIR/bar/file" "$TESTDIR/baz/file" +log_must ratelimit_bw_read 6 12 "$TESTDIR/file" "$TESTDIR/foo/file" "$TESTDIR/bar/file" "$TESTDIR/baz/file" + +log_must zfs set limit_bw_total=none "$TESTPOOL/$TESTFS" +log_must zfs set limit_bw_total=none "$TESTPOOL/$TESTFS/foo" +log_must zfs set limit_bw_total=none "$TESTPOOL/$TESTFS/bar" +log_must zfs set limit_bw_total=none "$TESTPOOL/$TESTFS/baz" + +log_must zfs set limit_bw_write=none "$TESTPOOL/$TESTFS" +log_must zfs set limit_bw_write=1M "$TESTPOOL/$TESTFS/foo" +log_must zfs set limit_bw_write=1M "$TESTPOOL/$TESTFS/bar" +log_must zfs set limit_bw_write=1M "$TESTPOOL/$TESTFS/baz" +log_must ratelimit_bw_write 6 6 "$TESTDIR/foo/file" +log_must ratelimit_bw_write 6 6 "$TESTDIR/foo/file" "$TESTDIR/bar/file" +log_must ratelimit_bw_write 6 6 "$TESTDIR/foo/file" "$TESTDIR/bar/file" "$TESTDIR/baz/file" +log_must ratelimit_bw_write 6 6 "$TESTDIR/file" "$TESTDIR/foo/file" "$TESTDIR/bar/file" "$TESTDIR/baz/file" + +log_must zfs set limit_bw_write=2M "$TESTPOOL/$TESTFS" +log_must zfs set limit_bw_write=1M "$TESTPOOL/$TESTFS/foo" +log_must zfs set limit_bw_write=1M "$TESTPOOL/$TESTFS/bar" +log_must zfs set limit_bw_write=1M "$TESTPOOL/$TESTFS/baz" +log_must ratelimit_bw_write 6 6 "$TESTDIR/foo/file" "$TESTDIR/bar/file" +log_must ratelimit_bw_write 6 9 "$TESTDIR/foo/file" "$TESTDIR/bar/file" "$TESTDIR/baz/file" +log_must ratelimit_bw_write 6 12 "$TESTDIR/file" "$TESTDIR/foo/file" "$TESTDIR/bar/file" "$TESTDIR/baz/file" + +log_must zfs set limit_bw_write=2M "$TESTPOOL/$TESTFS" +log_must zfs set limit_bw_write=2M "$TESTPOOL/$TESTFS/foo" +log_must zfs set limit_bw_write=2M "$TESTPOOL/$TESTFS/bar" +log_must zfs set limit_bw_write=2M "$TESTPOOL/$TESTFS/baz" +log_must ratelimit_bw_write 6 3 "$TESTDIR/foo/file" +log_must ratelimit_bw_write 6 6 "$TESTDIR/foo/file" "$TESTDIR/bar/file" +log_must ratelimit_bw_write 6 9 "$TESTDIR/foo/file" "$TESTDIR/bar/file" "$TESTDIR/baz/file" +log_must ratelimit_bw_write 6 12 "$TESTDIR/file" "$TESTDIR/foo/file" "$TESTDIR/bar/file" "$TESTDIR/baz/file" + +log_must zfs set limit_bw_write=2M "$TESTPOOL/$TESTFS" +log_must zfs set limit_bw_write=none "$TESTPOOL/$TESTFS/foo" +log_must zfs set limit_bw_write=none "$TESTPOOL/$TESTFS/bar" +log_must zfs set limit_bw_write=none "$TESTPOOL/$TESTFS/baz" +log_must ratelimit_bw_write 6 3 "$TESTDIR/foo/file" +log_must ratelimit_bw_write 6 6 "$TESTDIR/foo/file" "$TESTDIR/bar/file" +log_must ratelimit_bw_write 6 9 "$TESTDIR/foo/file" "$TESTDIR/bar/file" "$TESTDIR/baz/file" +log_must ratelimit_bw_write 6 12 "$TESTDIR/file" "$TESTDIR/foo/file" "$TESTDIR/bar/file" "$TESTDIR/baz/file" + +log_must zfs set limit_bw_write=none "$TESTPOOL/$TESTFS" +log_must zfs set limit_bw_write=none "$TESTPOOL/$TESTFS/foo" +log_must zfs set limit_bw_write=none "$TESTPOOL/$TESTFS/bar" +log_must zfs set limit_bw_write=none "$TESTPOOL/$TESTFS/baz" + +log_must zfs set limit_bw_total=none "$TESTPOOL/$TESTFS" +log_must zfs set limit_bw_total=1M "$TESTPOOL/$TESTFS/foo" +log_must zfs set limit_bw_total=1M "$TESTPOOL/$TESTFS/bar" +log_must zfs set limit_bw_total=1M "$TESTPOOL/$TESTFS/baz" +log_must ratelimit_bw_write 6 6 "$TESTDIR/foo/file" +log_must ratelimit_bw_write 6 6 "$TESTDIR/foo/file" "$TESTDIR/bar/file" +log_must ratelimit_bw_write 6 6 "$TESTDIR/foo/file" "$TESTDIR/bar/file" "$TESTDIR/baz/file" +log_must ratelimit_bw_write 6 6 "$TESTDIR/file" "$TESTDIR/foo/file" "$TESTDIR/bar/file" "$TESTDIR/baz/file" + +log_must zfs set limit_bw_total=2M "$TESTPOOL/$TESTFS" +log_must zfs set limit_bw_total=1M "$TESTPOOL/$TESTFS/foo" +log_must zfs set limit_bw_total=1M "$TESTPOOL/$TESTFS/bar" +log_must zfs set limit_bw_total=1M "$TESTPOOL/$TESTFS/baz" +log_must ratelimit_bw_write 6 6 "$TESTDIR/foo/file" "$TESTDIR/bar/file" +log_must ratelimit_bw_write 6 9 "$TESTDIR/foo/file" "$TESTDIR/bar/file" "$TESTDIR/baz/file" +log_must ratelimit_bw_write 6 12 "$TESTDIR/file" "$TESTDIR/foo/file" "$TESTDIR/bar/file" "$TESTDIR/baz/file" + +log_must zfs set limit_bw_total=2M "$TESTPOOL/$TESTFS" +log_must zfs set limit_bw_total=2M "$TESTPOOL/$TESTFS/foo" +log_must zfs set limit_bw_total=2M "$TESTPOOL/$TESTFS/bar" +log_must zfs set limit_bw_total=2M "$TESTPOOL/$TESTFS/baz" +log_must ratelimit_bw_write 6 3 "$TESTDIR/foo/file" +log_must ratelimit_bw_write 6 6 "$TESTDIR/foo/file" "$TESTDIR/bar/file" +log_must ratelimit_bw_write 6 9 "$TESTDIR/foo/file" "$TESTDIR/bar/file" "$TESTDIR/baz/file" +log_must ratelimit_bw_write 6 12 "$TESTDIR/file" "$TESTDIR/foo/file" "$TESTDIR/bar/file" "$TESTDIR/baz/file" + +log_must zfs set limit_bw_total=2M "$TESTPOOL/$TESTFS" +log_must zfs set limit_bw_total=none "$TESTPOOL/$TESTFS/foo" +log_must zfs set limit_bw_total=none "$TESTPOOL/$TESTFS/bar" +log_must zfs set limit_bw_total=none "$TESTPOOL/$TESTFS/baz" +log_must ratelimit_bw_write 6 3 "$TESTDIR/foo/file" +log_must ratelimit_bw_write 6 6 "$TESTDIR/foo/file" "$TESTDIR/bar/file" +log_must ratelimit_bw_write 6 9 "$TESTDIR/foo/file" "$TESTDIR/bar/file" "$TESTDIR/baz/file" +log_must ratelimit_bw_write 6 12 "$TESTDIR/file" "$TESTDIR/foo/file" "$TESTDIR/bar/file" "$TESTDIR/baz/file" + +log_must zfs set limit_bw_total=none "$TESTPOOL/$TESTFS" +log_must zfs set limit_bw_total=none "$TESTPOOL/$TESTFS/foo" +log_must zfs set limit_bw_total=none "$TESTPOOL/$TESTFS/bar" +log_must zfs set limit_bw_total=none "$TESTPOOL/$TESTFS/baz" + +log_must destroy_dataset "$TESTPOOL/$TESTFS/foo" +log_must destroy_dataset "$TESTPOOL/$TESTFS/bar" +log_must destroy_dataset "$TESTPOOL/$TESTFS/baz" + +rm -f "$TESTDIR/file" "$TESTDIR/foo/file" "$TESTDIR/bar/file" "$TESTDIR/baz/file" + +log_pass diff --git a/tests/zfs-tests/tests/functional/ratelimit/filesystem_bw_hierarchical_vertical_read.ksh b/tests/zfs-tests/tests/functional/ratelimit/filesystem_bw_hierarchical_vertical_read.ksh new file mode 100755 index 000000000000..71419cfd78a5 --- /dev/null +++ b/tests/zfs-tests/tests/functional/ratelimit/filesystem_bw_hierarchical_vertical_read.ksh @@ -0,0 +1,65 @@ +#! /bin/ksh -p +# +# CDDL HEADER START +# +# The contents of this file are subject to the terms of the +# Common Development and Distribution License (the "License"). +# You may not use this file except in compliance with the License. +# +# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE +# or https://opensource.org/licenses/CDDL-1.0. +# See the License for the specific language governing permissions +# and limitations under the License. +# +# When distributing Covered Code, include this CDDL HEADER in each +# file and include the License file at usr/src/OPENSOLARIS.LICENSE. +# If applicable, add the following below this CDDL HEADER, with the +# fields enclosed by brackets "[]" replaced with your own identifying +# information: Portions Copyright [yyyy] [name of copyright owner] +# +# CDDL HEADER END +# + +# +# Copyright (c) 2024 The FreeBSD Foundation +# +# This software was developed by Pawel Dawidek +# under sponsorship from the FreeBSD Foundation. +# + +. $STF_SUITE/include/libtest.shlib +. $STF_SUITE/tests/functional/ratelimit/ratelimit_common.kshlib + +verify_runnable "both" + +log_assert "Verify hierarchical bandwidth read limits configured on multiple levels" + +ratelimit_reset + +log_must create_dataset "$TESTPOOL/$TESTFS/lvl0" +log_must create_dataset "$TESTPOOL/$TESTFS/lvl0/lvl1" +log_must create_dataset "$TESTPOOL/$TESTFS/lvl0/lvl1/lvl2" + +log_must truncate -s 1G "$TESTDIR/lvl0/lvl1/lvl2/file" + +for lvl0 in none 1M 3M 5M; do + for lvl1 in none 1M 3M 5M; do + for lvl2 in none 1M 3M 5M; do + # We need at least one level with 1M limit. + if [ $lvl0 != "1M" ] && [ $lvl1 != "1M" ] && [ $lvl2 != "1M" ]; then + continue + fi + + log_must zfs set limit_bw_read=$lvl0 "$TESTPOOL/$TESTFS/lvl0" + log_must zfs set limit_bw_read=$lvl1 "$TESTPOOL/$TESTFS/lvl0/lvl1" + log_must zfs set limit_bw_read=$lvl2 "$TESTPOOL/$TESTFS/lvl0/lvl1/lvl2" + log_must ratelimit_bw_read 5 5 "$TESTDIR/lvl0/lvl1/lvl2/file" + done + done +done + +log_must destroy_dataset "$TESTPOOL/$TESTFS/lvl0/lvl1/lvl2" +log_must destroy_dataset "$TESTPOOL/$TESTFS/lvl0/lvl1" +log_must destroy_dataset "$TESTPOOL/$TESTFS/lvl0" + +log_pass diff --git a/tests/zfs-tests/tests/functional/ratelimit/filesystem_bw_hierarchical_vertical_total.ksh b/tests/zfs-tests/tests/functional/ratelimit/filesystem_bw_hierarchical_vertical_total.ksh new file mode 100755 index 000000000000..d40a8b6de42d --- /dev/null +++ b/tests/zfs-tests/tests/functional/ratelimit/filesystem_bw_hierarchical_vertical_total.ksh @@ -0,0 +1,66 @@ +#! /bin/ksh -p +# +# CDDL HEADER START +# +# The contents of this file are subject to the terms of the +# Common Development and Distribution License (the "License"). +# You may not use this file except in compliance with the License. +# +# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE +# or https://opensource.org/licenses/CDDL-1.0. +# See the License for the specific language governing permissions +# and limitations under the License. +# +# When distributing Covered Code, include this CDDL HEADER in each +# file and include the License file at usr/src/OPENSOLARIS.LICENSE. +# If applicable, add the following below this CDDL HEADER, with the +# fields enclosed by brackets "[]" replaced with your own identifying +# information: Portions Copyright [yyyy] [name of copyright owner] +# +# CDDL HEADER END +# + +# +# Copyright (c) 2024 The FreeBSD Foundation +# +# This software was developed by Pawel Dawidek +# under sponsorship from the FreeBSD Foundation. +# + +. $STF_SUITE/include/libtest.shlib +. $STF_SUITE/tests/functional/ratelimit/ratelimit_common.kshlib + +verify_runnable "both" + +log_assert "Verify hierarchical bandwidth total limits configured on multiple levels" + +ratelimit_reset + +log_must create_dataset "$TESTPOOL/$TESTFS/lvl0" +log_must create_dataset "$TESTPOOL/$TESTFS/lvl0/lvl1" +log_must create_dataset "$TESTPOOL/$TESTFS/lvl0/lvl1/lvl2" + +log_must truncate -s 1G "$TESTDIR/lvl0/lvl1/lvl2/file" + +for lvl0 in none 1M 3M 5M; do + for lvl1 in none 1M 3M 5M; do + for lvl2 in none 1M 3M 5M; do + # We need at least one level with 1M limit. + if [ $lvl0 != "1M" ] && [ $lvl1 != "1M" ] && [ $lvl2 != "1M" ]; then + continue + fi + + log_must zfs set limit_bw_total=$lvl0 "$TESTPOOL/$TESTFS/lvl0" + log_must zfs set limit_bw_total=$lvl1 "$TESTPOOL/$TESTFS/lvl0/lvl1" + log_must zfs set limit_bw_total=$lvl2 "$TESTPOOL/$TESTFS/lvl0/lvl1/lvl2" + log_must ratelimit_bw_read 5 5 "$TESTDIR/lvl0/lvl1/lvl2/file" + log_must ratelimit_bw_write 5 5 "$TESTDIR/lvl0/lvl1/lvl2/file" + done + done +done + +log_must destroy_dataset "$TESTPOOL/$TESTFS/lvl0/lvl1/lvl2" +log_must destroy_dataset "$TESTPOOL/$TESTFS/lvl0/lvl1" +log_must destroy_dataset "$TESTPOOL/$TESTFS/lvl0" + +log_pass diff --git a/tests/zfs-tests/tests/functional/ratelimit/filesystem_bw_hierarchical_vertical_write.ksh b/tests/zfs-tests/tests/functional/ratelimit/filesystem_bw_hierarchical_vertical_write.ksh new file mode 100755 index 000000000000..e66c44e10299 --- /dev/null +++ b/tests/zfs-tests/tests/functional/ratelimit/filesystem_bw_hierarchical_vertical_write.ksh @@ -0,0 +1,65 @@ +#! /bin/ksh -p +# +# CDDL HEADER START +# +# The contents of this file are subject to the terms of the +# Common Development and Distribution License (the "License"). +# You may not use this file except in compliance with the License. +# +# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE +# or https://opensource.org/licenses/CDDL-1.0. +# See the License for the specific language governing permissions +# and limitations under the License. +# +# When distributing Covered Code, include this CDDL HEADER in each +# file and include the License file at usr/src/OPENSOLARIS.LICENSE. +# If applicable, add the following below this CDDL HEADER, with the +# fields enclosed by brackets "[]" replaced with your own identifying +# information: Portions Copyright [yyyy] [name of copyright owner] +# +# CDDL HEADER END +# + +# +# Copyright (c) 2024 The FreeBSD Foundation +# +# This software was developed by Pawel Dawidek +# under sponsorship from the FreeBSD Foundation. +# + +. $STF_SUITE/include/libtest.shlib +. $STF_SUITE/tests/functional/ratelimit/ratelimit_common.kshlib + +verify_runnable "both" + +log_assert "Verify hierarchical bandwidth write limits configured on multiple levels" + +ratelimit_reset + +log_must create_dataset "$TESTPOOL/$TESTFS/lvl0" +log_must create_dataset "$TESTPOOL/$TESTFS/lvl0/lvl1" +log_must create_dataset "$TESTPOOL/$TESTFS/lvl0/lvl1/lvl2" + +log_must truncate -s 1G "$TESTDIR/lvl0/lvl1/lvl2/file" + +for lvl0 in none 1M 3M 5M; do + for lvl1 in none 1M 3M 5M; do + for lvl2 in none 1M 3M 5M; do + # We need at least one level with 1M limit. + if [ $lvl0 != "1M" ] && [ $lvl1 != "1M" ] && [ $lvl2 != "1M" ]; then + continue + fi + + log_must zfs set limit_bw_write=$lvl0 "$TESTPOOL/$TESTFS/lvl0" + log_must zfs set limit_bw_write=$lvl1 "$TESTPOOL/$TESTFS/lvl0/lvl1" + log_must zfs set limit_bw_write=$lvl2 "$TESTPOOL/$TESTFS/lvl0/lvl1/lvl2" + log_must ratelimit_bw_write 5 5 "$TESTDIR/lvl0/lvl1/lvl2/file" + done + done +done + +log_must destroy_dataset "$TESTPOOL/$TESTFS/lvl0/lvl1/lvl2" +log_must destroy_dataset "$TESTPOOL/$TESTFS/lvl0/lvl1" +log_must destroy_dataset "$TESTPOOL/$TESTFS/lvl0" + +log_pass diff --git a/tests/zfs-tests/tests/functional/ratelimit/filesystem_bw_multiple.ksh b/tests/zfs-tests/tests/functional/ratelimit/filesystem_bw_multiple.ksh new file mode 100755 index 000000000000..78f4f51613ec --- /dev/null +++ b/tests/zfs-tests/tests/functional/ratelimit/filesystem_bw_multiple.ksh @@ -0,0 +1,67 @@ +#! /bin/ksh -p +# +# CDDL HEADER START +# +# The contents of this file are subject to the terms of the +# Common Development and Distribution License (the "License"). +# You may not use this file except in compliance with the License. +# +# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE +# or https://opensource.org/licenses/CDDL-1.0. +# See the License for the specific language governing permissions +# and limitations under the License. +# +# When distributing Covered Code, include this CDDL HEADER in each +# file and include the License file at usr/src/OPENSOLARIS.LICENSE. +# If applicable, add the following below this CDDL HEADER, with the +# fields enclosed by brackets "[]" replaced with your own identifying +# information: Portions Copyright [yyyy] [name of copyright owner] +# +# CDDL HEADER END +# + +# +# Copyright (c) 2024 The FreeBSD Foundation +# +# This software was developed by Pawel Dawidek +# under sponsorship from the FreeBSD Foundation. +# + +. $STF_SUITE/include/libtest.shlib +. $STF_SUITE/tests/functional/ratelimit/ratelimit_common.kshlib + +verify_runnable "both" + +log_assert "Verify bandwidth limits for multiple active processes" + +ratelimit_reset + +log_must truncate -s 1G "$TESTDIR/file" + +log_must ratelimit_filesystem_bw_read_multiple limit_bw_read=none 500 1 +log_must ratelimit_filesystem_bw_read_multiple limit_bw_read=1M 5 15 +log_must ratelimit_filesystem_bw_read_multiple limit_bw_read=10M 50 15 +log_must ratelimit_filesystem_bw_read_multiple limit_bw_read=100M 500 15 +log_must ratelimit_filesystem_bw_read_multiple limit_bw_read=none 500 1 + +log_must ratelimit_filesystem_bw_read_multiple limit_bw_total=none 500 1 +log_must ratelimit_filesystem_bw_read_multiple limit_bw_total=1M 5 15 +log_must ratelimit_filesystem_bw_read_multiple limit_bw_total=10M 50 15 +log_must ratelimit_filesystem_bw_read_multiple limit_bw_total=100M 500 15 +log_must ratelimit_filesystem_bw_read_multiple limit_bw_total=none 500 1 + +log_must ratelimit_filesystem_bw_write_multiple limit_bw_write=none 500 1 +log_must ratelimit_filesystem_bw_write_multiple limit_bw_write=1M 5 15 +log_must ratelimit_filesystem_bw_write_multiple limit_bw_write=10M 50 15 +log_must ratelimit_filesystem_bw_write_multiple limit_bw_write=100M 500 15 +log_must ratelimit_filesystem_bw_write_multiple limit_bw_write=none 500 1 + +log_must ratelimit_filesystem_bw_write_multiple limit_bw_total=none 500 1 +log_must ratelimit_filesystem_bw_write_multiple limit_bw_total=1M 5 15 +log_must ratelimit_filesystem_bw_write_multiple limit_bw_total=10M 50 15 +log_must ratelimit_filesystem_bw_write_multiple limit_bw_total=100M 500 15 +log_must ratelimit_filesystem_bw_write_multiple limit_bw_total=none 500 1 + +rm -f "$TESTDIR/file" + +log_pass diff --git a/tests/zfs-tests/tests/functional/ratelimit/filesystem_bw_recv.ksh b/tests/zfs-tests/tests/functional/ratelimit/filesystem_bw_recv.ksh new file mode 100755 index 000000000000..d079f986d757 --- /dev/null +++ b/tests/zfs-tests/tests/functional/ratelimit/filesystem_bw_recv.ksh @@ -0,0 +1,171 @@ +#! /bin/ksh -p +# +# CDDL HEADER START +# +# The contents of this file are subject to the terms of the +# Common Development and Distribution License (the "License"). +# You may not use this file except in compliance with the License. +# +# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE +# or https://opensource.org/licenses/CDDL-1.0. +# See the License for the specific language governing permissions +# and limitations under the License. +# +# When distributing Covered Code, include this CDDL HEADER in each +# file and include the License file at usr/src/OPENSOLARIS.LICENSE. +# If applicable, add the following below this CDDL HEADER, with the +# fields enclosed by brackets "[]" replaced with your own identifying +# information: Portions Copyright [yyyy] [name of copyright owner] +# +# CDDL HEADER END +# + +# +# Copyright (c) 2024 The FreeBSD Foundation +# +# This software was developed by Pawel Dawidek +# under sponsorship from the FreeBSD Foundation. +# + +. $STF_SUITE/include/libtest.shlib +. $STF_SUITE/tests/functional/ratelimit/ratelimit_common.kshlib + +verify_runnable "both" + +log_assert "Verify bandwidth limits for zfs receive" + +function ratelimit_recv +{ + typeset -r exp=$1 + typeset res + + shift + + sync_pool $TESTPOOL + stopwatch_start + zfs send "$TESTPOOL/$TESTFS/foo@snap" | zfs recv -F $@ "$TESTPOOL/$TESTFS/bar/baz" >/dev/null + stopwatch_check $exp + res=$? + destroy_snapshot "$TESTPOOL/$TESTFS/bar/baz@snap" + destroy_dataset "$TESTPOOL/$TESTFS/bar/baz" + return $res +} + +ratelimit_reset + +log_must create_dataset "$TESTPOOL/$TESTFS/foo" +log_must dd if=/dev/urandom of="$TESTDIR/foo/file" bs=1M count=16 +log_must create_snapshot "$TESTPOOL/$TESTFS/foo" "snap" +log_must create_dataset "$TESTPOOL/$TESTFS/bar" + +log_must ratelimit_recv 1 + +log_must zfs set limit_bw_write=4M "$TESTPOOL/$TESTFS/bar" +log_must ratelimit_recv 4 + +log_must zfs set limit_bw_write=none "$TESTPOOL/$TESTFS/bar" +log_must create_dataset "$TESTPOOL/$TESTFS/bar/baz" +log_must zfs set limit_bw_write=4M "$TESTPOOL/$TESTFS/bar/baz" +log_must ratelimit_recv 4 + +log_must zfs set limit_bw_write=none "$TESTPOOL/$TESTFS/bar" +log_must ratelimit_recv 4 -o limit_bw_write=4M + +log_must zfs set limit_bw_write=4M "$TESTPOOL/$TESTFS/bar" +log_must create_dataset "$TESTPOOL/$TESTFS/bar/baz" +log_must zfs set limit_bw_write=8M "$TESTPOOL/$TESTFS/bar/baz" +log_must ratelimit_recv 4 + +log_must zfs set limit_bw_write=4M "$TESTPOOL/$TESTFS/bar" +log_must ratelimit_recv 4 -o limit_bw_write=8M + +log_must zfs set limit_bw_write=8M "$TESTPOOL/$TESTFS/bar" +log_must create_dataset "$TESTPOOL/$TESTFS/bar/baz" +log_must zfs set limit_bw_write=4M "$TESTPOOL/$TESTFS/bar/baz" +log_must ratelimit_recv 4 + +log_must zfs set limit_bw_write=8M "$TESTPOOL/$TESTFS/bar" +log_must ratelimit_recv 4 -o limit_bw_write=4M + +log_must zfs set limit_bw_write=none "$TESTPOOL/$TESTFS/bar" + +log_must ratelimit_recv 1 + +log_must zfs set limit_bw_total=4M "$TESTPOOL/$TESTFS/bar" +log_must ratelimit_recv 4 + +log_must zfs set limit_bw_total=none "$TESTPOOL/$TESTFS/bar" +log_must create_dataset "$TESTPOOL/$TESTFS/bar/baz" +log_must zfs set limit_bw_total=4M "$TESTPOOL/$TESTFS/bar/baz" +log_must ratelimit_recv 4 + +log_must zfs set limit_bw_total=none "$TESTPOOL/$TESTFS/bar" +log_must ratelimit_recv 4 -o limit_bw_total=4M + +log_must zfs set limit_bw_total=4M "$TESTPOOL/$TESTFS/bar" +log_must create_dataset "$TESTPOOL/$TESTFS/bar/baz" +log_must zfs set limit_bw_total=8M "$TESTPOOL/$TESTFS/bar/baz" +log_must ratelimit_recv 4 + +log_must zfs set limit_bw_total=4M "$TESTPOOL/$TESTFS/bar" +log_must ratelimit_recv 4 -o limit_bw_total=8M + +log_must zfs set limit_bw_total=8M "$TESTPOOL/$TESTFS/bar" +log_must create_dataset "$TESTPOOL/$TESTFS/bar/baz" +log_must zfs set limit_bw_total=4M "$TESTPOOL/$TESTFS/bar/baz" +log_must ratelimit_recv 4 + +log_must zfs set limit_bw_total=8M "$TESTPOOL/$TESTFS/bar" +log_must ratelimit_recv 4 -o limit_bw_total=4M + +log_must zfs set limit_bw_total=none "$TESTPOOL/$TESTFS/bar" +log_must ratelimit_recv 1 + +log_must zfs set limit_bw_write=8M "$TESTPOOL/$TESTFS/bar" +log_must zfs set limit_bw_total=none "$TESTPOOL/$TESTFS/bar" +log_must create_dataset "$TESTPOOL/$TESTFS/bar/baz" +log_must zfs set limit_bw_write=none "$TESTPOOL/$TESTFS/bar/baz" +log_must zfs set limit_bw_total=4M "$TESTPOOL/$TESTFS/bar/baz" +log_must ratelimit_recv 4 + +log_must zfs set limit_bw_write=none "$TESTPOOL/$TESTFS/bar" +log_must zfs set limit_bw_total=8M "$TESTPOOL/$TESTFS/bar" +log_must ratelimit_recv 4 -o limit_bw_write=none -o limit_bw_total=4M + +log_must zfs set limit_bw_write=none "$TESTPOOL/$TESTFS/bar" +log_must zfs set limit_bw_total=8M "$TESTPOOL/$TESTFS/bar" +log_must create_dataset "$TESTPOOL/$TESTFS/bar/baz" +log_must zfs set limit_bw_write=4M "$TESTPOOL/$TESTFS/bar/baz" +log_must zfs set limit_bw_total=none "$TESTPOOL/$TESTFS/bar/baz" +log_must ratelimit_recv 4 + +log_must zfs set limit_bw_write=8M "$TESTPOOL/$TESTFS/bar" +log_must zfs set limit_bw_total=none "$TESTPOOL/$TESTFS/bar" +log_must ratelimit_recv 4 -o limit_bw_write=4M -o limit_bw_total=none + +log_must zfs set limit_bw_write=4M "$TESTPOOL/$TESTFS/bar" +log_must zfs set limit_bw_total=none "$TESTPOOL/$TESTFS/bar" +log_must create_dataset "$TESTPOOL/$TESTFS/bar/baz" +log_must zfs set limit_bw_write=none "$TESTPOOL/$TESTFS/bar/baz" +log_must zfs set limit_bw_total=8M "$TESTPOOL/$TESTFS/bar/baz" +log_must ratelimit_recv 4 + +log_must zfs set limit_bw_write=none "$TESTPOOL/$TESTFS/bar" +log_must zfs set limit_bw_total=4M "$TESTPOOL/$TESTFS/bar" +log_must ratelimit_recv 4 -o limit_bw_write=none -o limit_bw_total=8M + +log_must zfs set limit_bw_write=none "$TESTPOOL/$TESTFS/bar" +log_must zfs set limit_bw_total=4M "$TESTPOOL/$TESTFS/bar" +log_must create_dataset "$TESTPOOL/$TESTFS/bar/baz" +log_must zfs set limit_bw_write=8M "$TESTPOOL/$TESTFS/bar/baz" +log_must zfs set limit_bw_total=none "$TESTPOOL/$TESTFS/bar/baz" +log_must ratelimit_recv 4 + +log_must zfs set limit_bw_write=4M "$TESTPOOL/$TESTFS/bar" +log_must zfs set limit_bw_total=none "$TESTPOOL/$TESTFS/bar" +log_must ratelimit_recv 4 -o limit_bw_write=8M -o limit_bw_total=none + +destroy_snapshot "$TESTPOOL/$TESTFS/foo@snap" +log_must destroy_dataset "$TESTPOOL/$TESTFS/foo" + +log_pass diff --git a/tests/zfs-tests/tests/functional/ratelimit/filesystem_bw_send.ksh b/tests/zfs-tests/tests/functional/ratelimit/filesystem_bw_send.ksh new file mode 100755 index 000000000000..9ae3fae8ee5c --- /dev/null +++ b/tests/zfs-tests/tests/functional/ratelimit/filesystem_bw_send.ksh @@ -0,0 +1,111 @@ +#! /bin/ksh -p +# +# CDDL HEADER START +# +# The contents of this file are subject to the terms of the +# Common Development and Distribution License (the "License"). +# You may not use this file except in compliance with the License. +# +# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE +# or https://opensource.org/licenses/CDDL-1.0. +# See the License for the specific language governing permissions +# and limitations under the License. +# +# When distributing Covered Code, include this CDDL HEADER in each +# file and include the License file at usr/src/OPENSOLARIS.LICENSE. +# If applicable, add the following below this CDDL HEADER, with the +# fields enclosed by brackets "[]" replaced with your own identifying +# information: Portions Copyright [yyyy] [name of copyright owner] +# +# CDDL HEADER END +# + +# +# Copyright (c) 2024 The FreeBSD Foundation +# +# This software was developed by Pawel Dawidek +# under sponsorship from the FreeBSD Foundation. +# + +. $STF_SUITE/include/libtest.shlib +. $STF_SUITE/tests/functional/ratelimit/ratelimit_common.kshlib + +verify_runnable "both" + +log_assert "Verify bandwidth limits for zfs send" + +function ratelimit_send +{ + typeset -r exp=$1 + + stopwatch_start + zfs send "$TESTPOOL/$TESTFS/foo@snap" >/dev/null + stopwatch_check $exp +} + +ratelimit_reset + +log_must create_dataset "$TESTPOOL/$TESTFS/foo" +log_must dd if=/dev/urandom of="$TESTDIR/foo/file" bs=1M count=16 +log_must create_snapshot "$TESTPOOL/$TESTFS/foo" "snap" + +log_must ratelimit_send 1 + +log_must zfs set limit_bw_read=2M "$TESTPOOL/$TESTFS/foo" +log_must ratelimit_send 8 + +log_must zfs set limit_bw_read=4M "$TESTPOOL/$TESTFS" +log_must zfs set limit_bw_read=8M "$TESTPOOL/$TESTFS/foo" +log_must ratelimit_send 4 + +log_must zfs set limit_bw_read=8M "$TESTPOOL/$TESTFS" +log_must zfs set limit_bw_read=4M "$TESTPOOL/$TESTFS/foo" +log_must ratelimit_send 4 + +log_must zfs set limit_bw_read=none "$TESTPOOL/$TESTFS" +log_must zfs set limit_bw_read=none "$TESTPOOL/$TESTFS/foo" +log_must ratelimit_send 1 + +log_must zfs set limit_bw_total=2M "$TESTPOOL/$TESTFS/foo" +log_must ratelimit_send 8 + +log_must zfs set limit_bw_total=4M "$TESTPOOL/$TESTFS" +log_must zfs set limit_bw_total=8M "$TESTPOOL/$TESTFS/foo" +log_must ratelimit_send 4 + +log_must zfs set limit_bw_total=8M "$TESTPOOL/$TESTFS" +log_must zfs set limit_bw_total=4M "$TESTPOOL/$TESTFS/foo" +log_must ratelimit_send 4 + +log_must zfs set limit_bw_total=none "$TESTPOOL/$TESTFS" +log_must zfs set limit_bw_total=none "$TESTPOOL/$TESTFS/foo" +log_must ratelimit_send 1 + +log_must zfs set limit_bw_read=4M "$TESTPOOL/$TESTFS" +log_must zfs set limit_bw_total=none "$TESTPOOL/$TESTFS" +log_must zfs set limit_bw_read=none "$TESTPOOL/$TESTFS/foo" +log_must zfs set limit_bw_total=8M "$TESTPOOL/$TESTFS/foo" +log_must ratelimit_send 4 + +log_must zfs set limit_bw_read=none "$TESTPOOL/$TESTFS" +log_must zfs set limit_bw_total=4M "$TESTPOOL/$TESTFS" +log_must zfs set limit_bw_read=8M "$TESTPOOL/$TESTFS/foo" +log_must zfs set limit_bw_total=none "$TESTPOOL/$TESTFS/foo" +log_must ratelimit_send 4 + +log_must zfs set limit_bw_read=8M "$TESTPOOL/$TESTFS" +log_must zfs set limit_bw_total=none "$TESTPOOL/$TESTFS" +log_must zfs set limit_bw_read=none "$TESTPOOL/$TESTFS/foo" +log_must zfs set limit_bw_total=4M "$TESTPOOL/$TESTFS/foo" +log_must ratelimit_send 4 + +log_must zfs set limit_bw_read=none "$TESTPOOL/$TESTFS" +log_must zfs set limit_bw_total=8M "$TESTPOOL/$TESTFS" +log_must zfs set limit_bw_read=4M "$TESTPOOL/$TESTFS/foo" +log_must zfs set limit_bw_total=none "$TESTPOOL/$TESTFS/foo" +log_must ratelimit_send 4 + +destroy_snapshot "$TESTPOOL/$TESTFS/foo@snap" +log_must destroy_dataset "$TESTPOOL/$TESTFS/foo" + +log_pass diff --git a/tests/zfs-tests/tests/functional/ratelimit/filesystem_bw_single.ksh b/tests/zfs-tests/tests/functional/ratelimit/filesystem_bw_single.ksh new file mode 100755 index 000000000000..93d9d59efc0b --- /dev/null +++ b/tests/zfs-tests/tests/functional/ratelimit/filesystem_bw_single.ksh @@ -0,0 +1,85 @@ +#! /bin/ksh -p +# +# CDDL HEADER START +# +# The contents of this file are subject to the terms of the +# Common Development and Distribution License (the "License"). +# You may not use this file except in compliance with the License. +# +# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE +# or https://opensource.org/licenses/CDDL-1.0. +# See the License for the specific language governing permissions +# and limitations under the License. +# +# When distributing Covered Code, include this CDDL HEADER in each +# file and include the License file at usr/src/OPENSOLARIS.LICENSE. +# If applicable, add the following below this CDDL HEADER, with the +# fields enclosed by brackets "[]" replaced with your own identifying +# information: Portions Copyright [yyyy] [name of copyright owner] +# +# CDDL HEADER END +# + +# +# Copyright (c) 2024 The FreeBSD Foundation +# +# This software was developed by Pawel Dawidek +# under sponsorship from the FreeBSD Foundation. +# + +. $STF_SUITE/include/libtest.shlib +. $STF_SUITE/tests/functional/ratelimit/ratelimit_common.kshlib + +verify_runnable "both" + +log_assert "Verify various bandwidth limits for a single active process" + +ratelimit_reset + +log_must truncate -s 1G "$TESTDIR/file" + +# Bandwidth read limits. +log_must ratelimit_filesystem_bw_read_single limit_bw_read=none 500 1 +log_must ratelimit_filesystem_bw_read_single limit_bw_read=1M 5 5 +log_must ratelimit_filesystem_bw_read_single limit_bw_read=10M 50 5 +log_must ratelimit_filesystem_bw_read_single limit_bw_read=100M 500 5 +log_must ratelimit_filesystem_bw_read_single limit_bw_read=none 500 1 + +# Bandwidth total limits limit reading. +log_must ratelimit_filesystem_bw_read_single limit_bw_total=none 500 1 +log_must ratelimit_filesystem_bw_read_single limit_bw_total=1M 5 5 +log_must ratelimit_filesystem_bw_read_single limit_bw_total=10M 50 5 +log_must ratelimit_filesystem_bw_read_single limit_bw_total=100M 500 5 +log_must ratelimit_filesystem_bw_read_single limit_bw_total=none 500 1 + +# Bandwidth write limits don't affect reading. +log_must ratelimit_filesystem_bw_read_single limit_bw_write=none 500 1 +log_must ratelimit_filesystem_bw_read_single limit_bw_write=1M 5 1 +log_must ratelimit_filesystem_bw_read_single limit_bw_write=10M 50 1 +log_must ratelimit_filesystem_bw_read_single limit_bw_write=100M 500 1 +log_must ratelimit_filesystem_bw_read_single limit_bw_write=none 500 1 + +# Bandwidth write limits. +log_must ratelimit_filesystem_bw_write_single limit_bw_write=none 500 1 +log_must ratelimit_filesystem_bw_write_single limit_bw_write=1M 5 5 +log_must ratelimit_filesystem_bw_write_single limit_bw_write=10M 50 5 +log_must ratelimit_filesystem_bw_write_single limit_bw_write=100M 500 5 +log_must ratelimit_filesystem_bw_write_single limit_bw_write=none 500 1 + +# Bandwidth total limits limit writing. +log_must ratelimit_filesystem_bw_write_single limit_bw_total=none 500 1 +log_must ratelimit_filesystem_bw_write_single limit_bw_total=1M 5 5 +log_must ratelimit_filesystem_bw_write_single limit_bw_total=10M 50 5 +log_must ratelimit_filesystem_bw_write_single limit_bw_total=100M 500 5 +log_must ratelimit_filesystem_bw_write_single limit_bw_total=none 500 1 + +# Bandwidth read limits don't affect writing. +log_must ratelimit_filesystem_bw_write_single limit_bw_read=none 500 1 +log_must ratelimit_filesystem_bw_write_single limit_bw_read=1M 5 1 +log_must ratelimit_filesystem_bw_write_single limit_bw_read=10M 50 1 +log_must ratelimit_filesystem_bw_write_single limit_bw_read=100M 500 1 +log_must ratelimit_filesystem_bw_write_single limit_bw_read=none 500 1 + +rm -f "$TESTDIR/file" + +log_pass diff --git a/tests/zfs-tests/tests/functional/ratelimit/filesystem_op_multiple.ksh b/tests/zfs-tests/tests/functional/ratelimit/filesystem_op_multiple.ksh new file mode 100755 index 000000000000..5745449e39d2 --- /dev/null +++ b/tests/zfs-tests/tests/functional/ratelimit/filesystem_op_multiple.ksh @@ -0,0 +1,78 @@ +#! /bin/ksh -p +# +# CDDL HEADER START +# +# The contents of this file are subject to the terms of the +# Common Development and Distribution License (the "License"). +# You may not use this file except in compliance with the License. +# +# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE +# or https://opensource.org/licenses/CDDL-1.0. +# See the License for the specific language governing permissions +# and limitations under the License. +# +# When distributing Covered Code, include this CDDL HEADER in each +# file and include the License file at usr/src/OPENSOLARIS.LICENSE. +# If applicable, add the following below this CDDL HEADER, with the +# fields enclosed by brackets "[]" replaced with your own identifying +# information: Portions Copyright [yyyy] [name of copyright owner] +# +# CDDL HEADER END +# + +# +# Copyright (c) 2024 The FreeBSD Foundation +# +# This software was developed by Pawel Dawidek +# under sponsorship from the FreeBSD Foundation. +# + +. $STF_SUITE/include/libtest.shlib +. $STF_SUITE/tests/functional/ratelimit/ratelimit_common.kshlib + +verify_runnable "both" + +log_assert "Verify operations limits for multiple active process" + +ratelimit_reset + +log_must touch "$TESTDIR/file" +log_must ln -s foo "$TESTDIR/symlink" + +log_must ratelimit_filesystem_op_read_multiple limit_op_read=none 1024 1 +log_must ratelimit_filesystem_op_read_multiple limit_op_read=128 512 8 +log_must ratelimit_filesystem_op_read_multiple limit_op_read=256 512 4 +log_must ratelimit_filesystem_op_read_multiple limit_op_read=512 1024 4 +log_must ratelimit_filesystem_op_read_multiple limit_op_read=none 1024 1 + +log_must ratelimit_filesystem_op_read_multiple limit_op_total=none 1024 1 +log_must ratelimit_filesystem_op_read_multiple limit_op_total=128 512 8 +log_must ratelimit_filesystem_op_read_multiple limit_op_total=256 512 4 +log_must ratelimit_filesystem_op_read_multiple limit_op_total=512 1024 4 +log_must ratelimit_filesystem_op_read_multiple limit_op_total=none 1024 1 + +rm -f "$TESTDIR/file" "$TESTDIR/symlink" + +log_must touch "$TESTDIR/file0" "$TESTDIR/file1" "$TESTDIR/file2" "$TESTDIR/file3" "$TESTDIR/file4" + +log_must ratelimit_filesystem_op_write_multiple_create limit_op_write=none 1024 1 +log_must ratelimit_filesystem_op_write_multiple_remove limit_op_write=none 1024 1 +log_must ratelimit_filesystem_op_write_multiple_create limit_op_write=128 128 5 +log_must ratelimit_filesystem_op_write_multiple_remove limit_op_write=128 128 5 +log_must ratelimit_filesystem_op_write_multiple_create limit_op_write=256 512 10 +log_must ratelimit_filesystem_op_write_multiple_remove limit_op_write=256 512 10 +log_must ratelimit_filesystem_op_write_multiple_create limit_op_write=none 1024 1 +log_must ratelimit_filesystem_op_write_multiple_remove limit_op_write=none 1024 1 + +log_must ratelimit_filesystem_op_write_multiple_create limit_op_total=none 1024 1 +log_must ratelimit_filesystem_op_write_multiple_remove limit_op_total=none 1024 1 +log_must ratelimit_filesystem_op_write_multiple_create limit_op_total=128 128 5 +log_must ratelimit_filesystem_op_write_multiple_remove limit_op_total=128 128 5 +log_must ratelimit_filesystem_op_write_multiple_create limit_op_total=256 512 10 +log_must ratelimit_filesystem_op_write_multiple_remove limit_op_total=256 512 10 +log_must ratelimit_filesystem_op_write_multiple_create limit_op_total=none 1024 1 +log_must ratelimit_filesystem_op_write_multiple_remove limit_op_total=none 1024 1 + +rm -f "$TESTDIR/file0" "$TESTDIR/file1" "$TESTDIR/file2" "$TESTDIR/file3" "$TESTDIR/file4" + +log_pass diff --git a/tests/zfs-tests/tests/functional/ratelimit/filesystem_op_single.ksh b/tests/zfs-tests/tests/functional/ratelimit/filesystem_op_single.ksh new file mode 100755 index 000000000000..cdaa03efd4ff --- /dev/null +++ b/tests/zfs-tests/tests/functional/ratelimit/filesystem_op_single.ksh @@ -0,0 +1,152 @@ +#! /bin/ksh -p +# +# CDDL HEADER START +# +# The contents of this file are subject to the terms of the +# Common Development and Distribution License (the "License"). +# You may not use this file except in compliance with the License. +# +# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE +# or https://opensource.org/licenses/CDDL-1.0. +# See the License for the specific language governing permissions +# and limitations under the License. +# +# When distributing Covered Code, include this CDDL HEADER in each +# file and include the License file at usr/src/OPENSOLARIS.LICENSE. +# If applicable, add the following below this CDDL HEADER, with the +# fields enclosed by brackets "[]" replaced with your own identifying +# information: Portions Copyright [yyyy] [name of copyright owner] +# +# CDDL HEADER END +# + +# +# Copyright (c) 2024 The FreeBSD Foundation +# +# This software was developed by Pawel Dawidek +# under sponsorship from the FreeBSD Foundation. +# + +. $STF_SUITE/include/libtest.shlib +. $STF_SUITE/tests/functional/ratelimit/ratelimit_common.kshlib + +verify_runnable "both" + +log_assert "Verify operations limits for a single active process" + +ratelimit_reset + +log_must touch "$TESTDIR/file" +log_must ln -s foo "$TESTDIR/symlink" + +# Operations read limits. +log_must ratelimit_filesystem_op_single stat limit_op_read=64 512 8 "$TESTDIR/file" +log_must ratelimit_filesystem_op_single readlink limit_op_read=64 512 8 "$TESTDIR/symlink" +log_must ratelimit_filesystem_op_single stat limit_op_read=128 512 4 "$TESTDIR/file" +log_must ratelimit_filesystem_op_single readlink limit_op_read=128 512 4 "$TESTDIR/symlink" +log_must ratelimit_filesystem_op_single stat limit_op_read=none 1024 1 "$TESTDIR/file" +log_must ratelimit_filesystem_op_single readlink limit_op_read=none 1024 1 "$TESTDIR/symlink" + +# Operations total limits limit reading. +log_must ratelimit_filesystem_op_single stat limit_op_total=64 512 8 "$TESTDIR/file" +log_must ratelimit_filesystem_op_single readlink limit_op_total=64 512 8 "$TESTDIR/symlink" +log_must ratelimit_filesystem_op_single stat limit_op_total=128 512 4 "$TESTDIR/file" +log_must ratelimit_filesystem_op_single readlink limit_op_total=128 512 4 "$TESTDIR/symlink" +log_must ratelimit_filesystem_op_single stat limit_op_total=none 1024 1 "$TESTDIR/file" +log_must ratelimit_filesystem_op_single readlink limit_op_total=none 1024 1 "$TESTDIR/symlink" + +# Operations write limits don't affect reading. +log_must ratelimit_filesystem_op_single stat limit_op_write=64 512 1 "$TESTDIR/file" +log_must ratelimit_filesystem_op_single readlink limit_op_write=64 512 1 "$TESTDIR/symlink" +log_must ratelimit_filesystem_op_single stat limit_op_write=128 512 1 "$TESTDIR/file" +log_must ratelimit_filesystem_op_single readlink limit_op_write=128 512 1 "$TESTDIR/symlink" +log_must ratelimit_filesystem_op_single stat limit_op_write=none 1024 1 "$TESTDIR/file" +log_must ratelimit_filesystem_op_single readlink limit_op_write=none 1024 1 "$TESTDIR/symlink" + +# Operations write limits. +log_must ratelimit_filesystem_op_single chmod limit_op_write=64 512 8 "$TESTDIR/file" +log_must ratelimit_filesystem_op_single chown limit_op_write=128 512 4 "$TESTDIR/file" +log_must ratelimit_filesystem_op_single create limit_op_write=64 512 8 "$TESTDIR/file" +log_must ratelimit_filesystem_op_single unlink limit_op_write=128 512 4 "$TESTDIR/file" +log_must ratelimit_filesystem_op_single mkdir limit_op_write=64 512 8 "$TESTDIR/file" +log_must ratelimit_filesystem_op_single rmdir limit_op_write=128 512 4 "$TESTDIR/file" +log_must ratelimit_filesystem_op_single rename limit_op_write=64 512 8 "$TESTDIR/file" +log_must ratelimit_filesystem_op_single link limit_op_write=128 512 4 "$TESTDIR/file" +log_must ratelimit_filesystem_op_single unlink limit_op_write=64 512 8 "$TESTDIR/file" +log_must ratelimit_filesystem_op_single symlink limit_op_write=128 512 4 "$TESTDIR/file" +log_must ratelimit_filesystem_op_single unlink limit_op_write=64 512 8 "$TESTDIR/file" +log_must ratelimit_filesystem_op_single chmod limit_op_write=none 1024 1 "$TESTDIR/file" +log_must ratelimit_filesystem_op_single chown limit_op_write=none 1024 1 "$TESTDIR/file" +log_must ratelimit_filesystem_op_single create limit_op_write=none 1024 1 "$TESTDIR/file" +log_must ratelimit_filesystem_op_single unlink limit_op_write=none 1024 1 "$TESTDIR/file" +log_must ratelimit_filesystem_op_single mkdir limit_op_write=none 1024 1 "$TESTDIR/file" +log_must ratelimit_filesystem_op_single rmdir limit_op_write=none 1024 1 "$TESTDIR/file" +log_must ratelimit_filesystem_op_single rename limit_op_write=none 1024 1 "$TESTDIR/file" +log_must ratelimit_filesystem_op_single link limit_op_write=none 1024 1 "$TESTDIR/file" +log_must ratelimit_filesystem_op_single unlink limit_op_write=none 1024 1 "$TESTDIR/file" +log_must ratelimit_filesystem_op_single symlink limit_op_write=none 1024 1 "$TESTDIR/file" +log_must ratelimit_filesystem_op_single unlink limit_op_write=none 1024 1 "$TESTDIR/file" + +# Operations total limits limit writing. +log_must ratelimit_filesystem_op_single chmod limit_op_total=128 512 4 "$TESTDIR/file" +log_must ratelimit_filesystem_op_single chown limit_op_total=64 512 8 "$TESTDIR/file" +# Creating a file requires one metadata write and one metadata read operation. +# On successful open(2), zfs_freebsd_open() calls vnode_create_vobject() +# with size=0. If size=0, vnode_create_vobject() interprets this as not having +# the proper size and calls VOP_GETATTR(). +if is_freebsd; then + log_must ratelimit_filesystem_op_single create limit_op_total=128 512 8 "$TESTDIR/file" +else + log_must ratelimit_filesystem_op_single create limit_op_total=128 512 4 "$TESTDIR/file" +fi +log_must ratelimit_filesystem_op_single unlink limit_op_total=64 512 8 "$TESTDIR/file" +log_must ratelimit_filesystem_op_single mkdir limit_op_total=128 512 4 "$TESTDIR/file" +log_must ratelimit_filesystem_op_single rmdir limit_op_total=64 512 8 "$TESTDIR/file" +log_must ratelimit_filesystem_op_single rename limit_op_total=128 512 4 "$TESTDIR/file" +log_must ratelimit_filesystem_op_single link limit_op_total=64 512 8 "$TESTDIR/file" +log_must ratelimit_filesystem_op_single unlink limit_op_total=128 512 4 "$TESTDIR/file" +log_must ratelimit_filesystem_op_single symlink limit_op_total=64 512 8 "$TESTDIR/file" +log_must ratelimit_filesystem_op_single unlink limit_op_total=128 512 4 "$TESTDIR/file" +log_must ratelimit_filesystem_op_single chmod limit_op_total=none 1024 1 "$TESTDIR/file" +log_must ratelimit_filesystem_op_single chown limit_op_total=none 1024 1 "$TESTDIR/file" +log_must ratelimit_filesystem_op_single create limit_op_total=none 1024 1 "$TESTDIR/file" +log_must ratelimit_filesystem_op_single unlink limit_op_total=none 1024 1 "$TESTDIR/file" +log_must ratelimit_filesystem_op_single mkdir limit_op_total=none 1024 1 "$TESTDIR/file" +log_must ratelimit_filesystem_op_single rmdir limit_op_total=none 1024 1 "$TESTDIR/file" +log_must ratelimit_filesystem_op_single rename limit_op_total=none 1024 1 "$TESTDIR/file" +log_must ratelimit_filesystem_op_single link limit_op_total=none 1024 1 "$TESTDIR/file" +log_must ratelimit_filesystem_op_single unlink limit_op_total=none 1024 1 "$TESTDIR/file" +log_must ratelimit_filesystem_op_single symlink limit_op_total=none 1024 1 "$TESTDIR/file" +log_must ratelimit_filesystem_op_single unlink limit_op_total=none 1024 1 "$TESTDIR/file" + +# Operations read limits don't affect writing. +log_must ratelimit_filesystem_op_single chmod limit_op_read=32 1024 1 "$TESTDIR/file" +log_must ratelimit_filesystem_op_single chown limit_op_read=64 1024 1 "$TESTDIR/file" +if is_freebsd; then + log_must ratelimit_filesystem_op_single create limit_op_read=128 1024 8 "$TESTDIR/file" +else + log_must ratelimit_filesystem_op_single create limit_op_read=128 1024 1 "$TESTDIR/file" +fi +log_must ratelimit_filesystem_op_single unlink limit_op_read=256 1024 1 "$TESTDIR/file" +log_must ratelimit_filesystem_op_single mkdir limit_op_read=32 1024 1 "$TESTDIR/file" +log_must ratelimit_filesystem_op_single rmdir limit_op_read=64 1024 1 "$TESTDIR/file" +log_must ratelimit_filesystem_op_single rename limit_op_read=128 1024 1 "$TESTDIR/file" +log_must ratelimit_filesystem_op_single link limit_op_read=256 1024 1 "$TESTDIR/file" +log_must ratelimit_filesystem_op_single unlink limit_op_read=32 1024 1 "$TESTDIR/file" +log_must ratelimit_filesystem_op_single symlink limit_op_read=64 1024 1 "$TESTDIR/file" +log_must ratelimit_filesystem_op_single unlink limit_op_read=128 1024 1 "$TESTDIR/file" +log_must ratelimit_filesystem_op_single chmod limit_op_read=none 1024 1 "$TESTDIR/file" +log_must ratelimit_filesystem_op_single chown limit_op_read=none 1024 1 "$TESTDIR/file" +log_must ratelimit_filesystem_op_single create limit_op_read=none 1024 1 "$TESTDIR/file" +log_must ratelimit_filesystem_op_single unlink limit_op_read=none 1024 1 "$TESTDIR/file" +log_must ratelimit_filesystem_op_single mkdir limit_op_read=none 1024 1 "$TESTDIR/file" +log_must ratelimit_filesystem_op_single rmdir limit_op_read=none 1024 1 "$TESTDIR/file" +log_must ratelimit_filesystem_op_single rename limit_op_read=none 1024 1 "$TESTDIR/file" +log_must ratelimit_filesystem_op_single link limit_op_read=none 1024 1 "$TESTDIR/file" +log_must ratelimit_filesystem_op_single unlink limit_op_read=none 1024 1 "$TESTDIR/file" +log_must ratelimit_filesystem_op_single symlink limit_op_read=none 1024 1 "$TESTDIR/file" +log_must ratelimit_filesystem_op_single unlink limit_op_read=none 1024 1 "$TESTDIR/file" + +rm -f "$TESTDIR/file" "$TESTDIR/symlink" + +log_pass diff --git a/tests/zfs-tests/tests/functional/ratelimit/inheritance.ksh b/tests/zfs-tests/tests/functional/ratelimit/inheritance.ksh new file mode 100755 index 000000000000..05ea7a090d97 --- /dev/null +++ b/tests/zfs-tests/tests/functional/ratelimit/inheritance.ksh @@ -0,0 +1,307 @@ +#! /bin/ksh -p +# +# CDDL HEADER START +# +# The contents of this file are subject to the terms of the +# Common Development and Distribution License (the "License"). +# You may not use this file except in compliance with the License. +# +# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE +# or https://opensource.org/licenses/CDDL-1.0. +# See the License for the specific language governing permissions +# and limitations under the License. +# +# When distributing Covered Code, include this CDDL HEADER in each +# file and include the License file at usr/src/OPENSOLARIS.LICENSE. +# If applicable, add the following below this CDDL HEADER, with the +# fields enclosed by brackets "[]" replaced with your own identifying +# information: Portions Copyright [yyyy] [name of copyright owner] +# +# CDDL HEADER END +# + +# +# Copyright (c) 2024 The FreeBSD Foundation +# +# This software was developed by Pawel Dawidek +# under sponsorship from the FreeBSD Foundation. +# + +. $STF_SUITE/include/libtest.shlib +. $STF_SUITE/tests/functional/ratelimit/ratelimit_common.kshlib + +verify_runnable "both" + +log_assert "Verify that limits are properly inherited" + +ratelimit_reset + +log_must create_dataset "$TESTPOOL/$TESTFS/mother" +log_must create_dataset "$TESTPOOL/$TESTFS/father" + +log_must truncate -s 1G "$TESTDIR/mother/file" +log_must ratelimit_bw 1 5 1 "$TESTDIR/mother/file" "/dev/null" +log_must truncate -s 1G "$TESTDIR/father/file" +log_must ratelimit_bw 1 5 1 "$TESTDIR/father/file" "/dev/null" + +if false; then +# Parent configuration exists before child creation. +log_must zfs set limit_bw_read=1M "$TESTPOOL/$TESTFS/mother" +log_must ratelimit_bw 1 5 5 "$TESTDIR/mother/file" "/dev/null" +log_must create_dataset "$TESTPOOL/$TESTFS/mother/child" +log_must truncate -s 1G "$TESTDIR/mother/child/file" +log_must ratelimit_bw 1 5 5 "$TESTDIR/mother/child/file" "/dev/null" +log_must destroy_dataset "$TESTPOOL/$TESTFS/mother/child" +log_must zfs set limit_bw_read=none "$TESTPOOL/$TESTFS/mother" + +# Parent configuration is done after child creation. +log_must create_dataset "$TESTPOOL/$TESTFS/mother/child" +log_must truncate -s 1G "$TESTDIR/mother/child/file" +log_must ratelimit_bw 1 5 1 "$TESTDIR/mother/child/file" "/dev/null" +log_must zfs set limit_bw_read=1M "$TESTPOOL/$TESTFS/mother" +log_must ratelimit_bw 1 5 5 "$TESTDIR/mother/child/file" "/dev/null" +log_must destroy_dataset "$TESTPOOL/$TESTFS/mother/child" +log_must zfs set limit_bw_read=none "$TESTPOOL/$TESTFS/mother" + +# Child is moved between a parent with no configuration to a parent with limits and back. +log_must create_dataset "$TESTPOOL/$TESTFS/mother/child" +log_must truncate -s 1G "$TESTDIR/mother/child/file" +log_must ratelimit_bw 1 5 1 "$TESTDIR/mother/child/file" "/dev/null" +log_must zfs set limit_bw_read=1M "$TESTPOOL/$TESTFS/father" +log_must zfs rename "$TESTPOOL/$TESTFS/mother/child" "$TESTPOOL/$TESTFS/father/child" +log_must ratelimit_bw 1 5 5 "$TESTDIR/father/child/file" "/dev/null" +log_must zfs rename "$TESTPOOL/$TESTFS/father/child" "$TESTPOOL/$TESTFS/mother/child" +log_must ratelimit_bw 1 5 1 "$TESTDIR/mother/child/file" "/dev/null" +log_must destroy_dataset "$TESTPOOL/$TESTFS/mother/child" +log_must zfs set limit_bw_read=none "$TESTPOOL/$TESTFS/father" + +# Child is moved between a parent with limits to a parent with no limits and back. +log_must zfs set limit_bw_read=1M "$TESTPOOL/$TESTFS/mother" +log_must create_dataset "$TESTPOOL/$TESTFS/mother/child" +log_must truncate -s 1G "$TESTDIR/mother/child/file" +log_must ratelimit_bw 1 5 5 "$TESTDIR/mother/child/file" "/dev/null" +log_must zfs rename "$TESTPOOL/$TESTFS/mother/child" "$TESTPOOL/$TESTFS/father/child" +log_must ratelimit_bw 1 5 1 "$TESTDIR/father/child/file" "/dev/null" +log_must zfs rename "$TESTPOOL/$TESTFS/father/child" "$TESTPOOL/$TESTFS/mother/child" +log_must ratelimit_bw 1 5 5 "$TESTDIR/mother/child/file" "/dev/null" +log_must destroy_dataset "$TESTPOOL/$TESTFS/mother/child" +log_must zfs set limit_bw_read=none "$TESTPOOL/$TESTFS/mother" + +# Child is moved between parent with different limits and back. +log_must zfs set limit_bw_read=1M "$TESTPOOL/$TESTFS/mother" +log_must zfs set limit_bw_read=2M "$TESTPOOL/$TESTFS/father" +log_must create_dataset "$TESTPOOL/$TESTFS/mother/child" +log_must truncate -s 1G "$TESTDIR/mother/child/file" +log_must ratelimit_bw 1 8 8 "$TESTDIR/mother/child/file" "/dev/null" +log_must zfs rename "$TESTPOOL/$TESTFS/mother/child" "$TESTPOOL/$TESTFS/father/child" +log_must ratelimit_bw 1 8 4 "$TESTDIR/father/child/file" "/dev/null" +log_must zfs rename "$TESTPOOL/$TESTFS/father/child" "$TESTPOOL/$TESTFS/mother/child" +log_must ratelimit_bw 1 8 8 "$TESTDIR/mother/child/file" "/dev/null" +log_must destroy_dataset "$TESTPOOL/$TESTFS/mother/child" +log_must zfs set limit_bw_read=none "$TESTPOOL/$TESTFS/mother" +log_must zfs set limit_bw_read=none "$TESTPOOL/$TESTFS/father" + +# Repeat the tests above, but test grandchild, so its direct ancestor doesn't have limits. + +# Parent configuration exists before child and grandchild creation. +log_must zfs set limit_bw_read=1M "$TESTPOOL/$TESTFS/mother" +log_must ratelimit_bw 1 5 5 "$TESTDIR/mother/file" "/dev/null" +log_must create_dataset "$TESTPOOL/$TESTFS/mother/child" +log_must create_dataset "$TESTPOOL/$TESTFS/mother/child/grandchild" +log_must truncate -s 1G "$TESTDIR/mother/child/grandchild/file" +log_must ratelimit_bw 1 5 5 "$TESTDIR/mother/child/grandchild/file" "/dev/null" +log_must destroy_dataset "$TESTPOOL/$TESTFS/mother/child/grandchild" +log_must destroy_dataset "$TESTPOOL/$TESTFS/mother/child" +log_must zfs set limit_bw_read=none "$TESTPOOL/$TESTFS/mother" + +# Parent configuration is done after child and grandchild creation. +log_must create_dataset "$TESTPOOL/$TESTFS/mother/child" +log_must create_dataset "$TESTPOOL/$TESTFS/mother/child/grandchild" +log_must truncate -s 1G "$TESTDIR/mother/child/grandchild/file" +log_must ratelimit_bw 1 5 1 "$TESTDIR/mother/child/grandchild/file" "/dev/null" +log_must zfs set limit_bw_read=1M "$TESTPOOL/$TESTFS/mother" +log_must ratelimit_bw 1 5 5 "$TESTDIR/mother/child/grandchild/file" "/dev/null" +log_must destroy_dataset "$TESTPOOL/$TESTFS/mother/child/grandchild" +log_must destroy_dataset "$TESTPOOL/$TESTFS/mother/child" +log_must zfs set limit_bw_read=none "$TESTPOOL/$TESTFS/mother" + +# Child is moved between a parent with no configuration to a parent with limits and back. +log_must create_dataset "$TESTPOOL/$TESTFS/mother/child" +log_must create_dataset "$TESTPOOL/$TESTFS/mother/child/grandchild" +log_must truncate -s 1G "$TESTDIR/mother/child/grandchild/file" +log_must ratelimit_bw 1 5 1 "$TESTDIR/mother/child/grandchild/file" "/dev/null" +log_must zfs set limit_bw_read=1M "$TESTPOOL/$TESTFS/father" +log_must zfs rename "$TESTPOOL/$TESTFS/mother/child" "$TESTPOOL/$TESTFS/father/child" +log_must ratelimit_bw 1 5 5 "$TESTDIR/father/child/grandchild/file" "/dev/null" +log_must zfs rename "$TESTPOOL/$TESTFS/father/child" "$TESTPOOL/$TESTFS/mother/child" +log_must ratelimit_bw 1 5 1 "$TESTDIR/mother/child/grandchild/file" "/dev/null" +log_must destroy_dataset "$TESTPOOL/$TESTFS/mother/child/grandchild" +log_must destroy_dataset "$TESTPOOL/$TESTFS/mother/child" +log_must zfs set limit_bw_read=none "$TESTPOOL/$TESTFS/father" + +# Child is moved between a parent with limits to a parent with no limits and back. +log_must zfs set limit_bw_read=1M "$TESTPOOL/$TESTFS/mother" +log_must create_dataset "$TESTPOOL/$TESTFS/mother/child" +log_must create_dataset "$TESTPOOL/$TESTFS/mother/child/grandchild" +log_must truncate -s 1G "$TESTDIR/mother/child/grandchild/file" +log_must ratelimit_bw 1 5 5 "$TESTDIR/mother/child/grandchild/file" "/dev/null" +log_must zfs rename "$TESTPOOL/$TESTFS/mother/child" "$TESTPOOL/$TESTFS/father/child" +log_must ratelimit_bw 1 5 1 "$TESTDIR/father/child/grandchild/file" "/dev/null" +log_must zfs rename "$TESTPOOL/$TESTFS/father/child" "$TESTPOOL/$TESTFS/mother/child" +log_must ratelimit_bw 1 5 5 "$TESTDIR/mother/child/grandchild/file" "/dev/null" +log_must destroy_dataset "$TESTPOOL/$TESTFS/mother/child/grandchild" +log_must destroy_dataset "$TESTPOOL/$TESTFS/mother/child" +log_must zfs set limit_bw_read=none "$TESTPOOL/$TESTFS/mother" + +# Child is moved between parent with different limits and back. +log_must zfs set limit_bw_read=1M "$TESTPOOL/$TESTFS/mother" +log_must zfs set limit_bw_read=2M "$TESTPOOL/$TESTFS/father" +log_must create_dataset "$TESTPOOL/$TESTFS/mother/child" +log_must create_dataset "$TESTPOOL/$TESTFS/mother/child/grandchild" +log_must truncate -s 1G "$TESTDIR/mother/child/grandchild/file" +log_must ratelimit_bw 1 8 8 "$TESTDIR/mother/child/grandchild/file" "/dev/null" +log_must zfs rename "$TESTPOOL/$TESTFS/mother/child" "$TESTPOOL/$TESTFS/father/child" +log_must ratelimit_bw 1 8 4 "$TESTDIR/father/child/grandchild/file" "/dev/null" +log_must zfs rename "$TESTPOOL/$TESTFS/father/child" "$TESTPOOL/$TESTFS/mother/child" +log_must ratelimit_bw 1 8 8 "$TESTDIR/mother/child/grandchild/file" "/dev/null" +log_must destroy_dataset "$TESTPOOL/$TESTFS/mother/child/grandchild" +log_must destroy_dataset "$TESTPOOL/$TESTFS/mother/child" +log_must zfs set limit_bw_read=none "$TESTPOOL/$TESTFS/mother" +log_must zfs set limit_bw_read=none "$TESTPOOL/$TESTFS/father" +fi + +# Revert the order of datasets when all datasets have limits. +log_must create_dataset "$TESTPOOL/$TESTFS/mother/child" +log_must create_dataset "$TESTPOOL/$TESTFS/mother/child/grandchild" +log_must zfs set limit_bw_read=4M "$TESTPOOL/$TESTFS/mother" +log_must zfs set limit_bw_read=2M "$TESTPOOL/$TESTFS/mother/child" +log_must zfs set limit_bw_read=1M "$TESTPOOL/$TESTFS/mother/child/grandchild" +log_must truncate -s 1G "$TESTDIR/mother/child/file" +log_must truncate -s 1G "$TESTDIR/mother/child/grandchild/file" +log_must ratelimit_bw 1 12 3 "$TESTDIR/mother/file" "/dev/null" +log_must ratelimit_bw 1 12 6 "$TESTDIR/mother/child/file" "/dev/null" +log_must ratelimit_bw 1 10 10 "$TESTDIR/mother/child/grandchild/file" "/dev/null" +log_must zfs rename "$TESTPOOL/$TESTFS/mother/child/grandchild" "$TESTPOOL/$TESTFS/grandchild" +log_must ratelimit_bw 1 10 10 "$TESTDIR/grandchild/file" "/dev/null" +log_must zfs rename "$TESTPOOL/$TESTFS/mother/child" "$TESTPOOL/$TESTFS/grandchild/child" +log_must ratelimit_bw 1 10 10 "$TESTDIR/grandchild/child/file" "/dev/null" +log_must zfs rename "$TESTPOOL/$TESTFS/mother" "$TESTPOOL/$TESTFS/grandchild/child/mother" +log_must ratelimit_bw 1 10 10 "$TESTDIR/grandchild/child/mother/file" "/dev/null" +# Changing limits at the bottom doesn't affect ancestors. +log_must zfs set limit_bw_read=6M "$TESTPOOL/$TESTFS/grandchild/child/mother" +log_must ratelimit_bw 1 10 10 "$TESTDIR/grandchild/file" "/dev/null" +log_must ratelimit_bw 1 10 10 "$TESTDIR/grandchild/child/file" "/dev/null" +log_must ratelimit_bw 1 10 10 "$TESTDIR/grandchild/child/mother/file" "/dev/null" +# Changing limits at the top does affect descendants. +log_must zfs set limit_bw_read=6M "$TESTPOOL/$TESTFS/grandchild" +log_must ratelimit_bw 1 12 2 "$TESTDIR/grandchild/file" "/dev/null" +log_must ratelimit_bw 1 12 6 "$TESTDIR/grandchild/child/file" "/dev/null" +log_must ratelimit_bw 1 12 6 "$TESTDIR/grandchild/child/mother/file" "/dev/null" +log_must zfs set limit_bw_read=6M "$TESTPOOL/$TESTFS/grandchild/child" +log_must ratelimit_bw 1 12 2 "$TESTDIR/grandchild/child/file" "/dev/null" +log_must ratelimit_bw 1 12 2 "$TESTDIR/grandchild/child/mother/file" "/dev/null" +log_must zfs rename "$TESTPOOL/$TESTFS/grandchild/child/mother" "$TESTPOOL/$TESTFS/mother" +log_must zfs set limit_bw_read=none "$TESTPOOL/$TESTFS/mother" +log_must destroy_dataset "$TESTPOOL/$TESTFS/grandchild/child" +log_must destroy_dataset "$TESTPOOL/$TESTFS/grandchild" +# +# Revert the order of limits. +# +log_must create_dataset "$TESTPOOL/$TESTFS/mother/child" +log_must create_dataset "$TESTPOOL/$TESTFS/mother/child/grandchild" +log_must zfs set limit_bw_read=1M "$TESTPOOL/$TESTFS/mother" +log_must zfs set limit_bw_read=2M "$TESTPOOL/$TESTFS/mother/child" +log_must zfs set limit_bw_read=4M "$TESTPOOL/$TESTFS/mother/child/grandchild" +log_must truncate -s 1G "$TESTDIR/mother/child/file" +log_must truncate -s 1G "$TESTDIR/mother/child/grandchild/file" +log_must ratelimit_bw 1 10 10 "$TESTDIR/mother/file" "/dev/null" +log_must ratelimit_bw 1 10 10 "$TESTDIR/mother/child/file" "/dev/null" +log_must ratelimit_bw 1 10 10 "$TESTDIR/mother/child/grandchild/file" "/dev/null" +log_must zfs rename "$TESTPOOL/$TESTFS/mother/child/grandchild" "$TESTPOOL/$TESTFS/grandchild" +log_must ratelimit_bw 1 12 3 "$TESTDIR/grandchild/file" "/dev/null" +log_must zfs rename "$TESTPOOL/$TESTFS/mother/child" "$TESTPOOL/$TESTFS/grandchild/child" +log_must ratelimit_bw 1 12 6 "$TESTDIR/grandchild/child/file" "/dev/null" +log_must zfs rename "$TESTPOOL/$TESTFS/mother" "$TESTPOOL/$TESTFS/grandchild/child/mother" +log_must ratelimit_bw 1 10 10 "$TESTDIR/grandchild/child/mother/file" "/dev/null" +# Changing limits at the bottom doesn't affect ancestors. +log_must zfs set limit_bw_read=6M "$TESTPOOL/$TESTFS/grandchild/child/mother" +log_must ratelimit_bw 1 12 3 "$TESTDIR/grandchild/file" "/dev/null" +log_must ratelimit_bw 1 12 6 "$TESTDIR/grandchild/child/file" "/dev/null" +log_must ratelimit_bw 1 12 6 "$TESTDIR/grandchild/child/mother/file" "/dev/null" +# Changing limits at the top does affect descendants. +log_must zfs set limit_bw_read=2M "$TESTPOOL/$TESTFS/grandchild" +log_must ratelimit_bw 1 12 6 "$TESTDIR/grandchild/file" "/dev/null" +log_must ratelimit_bw 1 12 6 "$TESTDIR/grandchild/child/file" "/dev/null" +log_must ratelimit_bw 1 12 6 "$TESTDIR/grandchild/child/mother/file" "/dev/null" +log_must zfs set limit_bw_read=1M "$TESTPOOL/$TESTFS/grandchild/child" +log_must ratelimit_bw 1 10 10 "$TESTDIR/grandchild/child/file" "/dev/null" +log_must ratelimit_bw 1 10 10 "$TESTDIR/grandchild/child/mother/file" "/dev/null" +log_must zfs rename "$TESTPOOL/$TESTFS/grandchild/child/mother" "$TESTPOOL/$TESTFS/mother" +log_must zfs set limit_bw_read=none "$TESTPOOL/$TESTFS/mother" +log_must destroy_dataset "$TESTPOOL/$TESTFS/grandchild/child" +log_must destroy_dataset "$TESTPOOL/$TESTFS/grandchild" + +# Revert the order datasets when grandchild doesn't have limits. +log_must create_dataset "$TESTPOOL/$TESTFS/mother/child" +log_must create_dataset "$TESTPOOL/$TESTFS/mother/child/grandchild" +log_must zfs set limit_bw_read=4M "$TESTPOOL/$TESTFS/mother" +log_must zfs set limit_bw_read=2M "$TESTPOOL/$TESTFS/mother/child" +log_must zfs set limit_bw_read=none "$TESTPOOL/$TESTFS/mother/child/grandchild" +log_must truncate -s 1G "$TESTDIR/mother/child/file" +log_must truncate -s 1G "$TESTDIR/mother/child/grandchild/file" +log_must ratelimit_bw 1 12 3 "$TESTDIR/mother/file" "/dev/null" +log_must ratelimit_bw 1 12 6 "$TESTDIR/mother/child/file" "/dev/null" +log_must ratelimit_bw 1 12 6 "$TESTDIR/mother/child/grandchild/file" "/dev/null" +log_must zfs rename "$TESTPOOL/$TESTFS/mother/child/grandchild" "$TESTPOOL/$TESTFS/grandchild" +log_must ratelimit_bw 1 10 1 "$TESTDIR/grandchild/file" "/dev/null" +log_must zfs rename "$TESTPOOL/$TESTFS/mother/child" "$TESTPOOL/$TESTFS/grandchild/child" +log_must ratelimit_bw 1 12 6 "$TESTDIR/grandchild/child/file" "/dev/null" +log_must zfs rename "$TESTPOOL/$TESTFS/mother" "$TESTPOOL/$TESTFS/grandchild/child/mother" +log_must ratelimit_bw 1 12 6 "$TESTDIR/grandchild/child/mother/file" "/dev/null" +# Changing limits at the bottom doesn't affect ancestors. +log_must zfs set limit_bw_read=6M "$TESTPOOL/$TESTFS/grandchild/child/mother" +log_must ratelimit_bw 1 10 1 "$TESTDIR/grandchild/file" "/dev/null" +log_must ratelimit_bw 1 12 6 "$TESTDIR/grandchild/child/file" "/dev/null" +log_must ratelimit_bw 1 12 6 "$TESTDIR/grandchild/child/mother/file" "/dev/null" +# Changing limits at the top does affect descendants. +log_must zfs set limit_bw_read=1M "$TESTPOOL/$TESTFS/grandchild" +log_must ratelimit_bw 1 10 10 "$TESTDIR/grandchild/file" "/dev/null" +log_must ratelimit_bw 1 10 10 "$TESTDIR/grandchild/child/file" "/dev/null" +log_must ratelimit_bw 1 10 10 "$TESTDIR/grandchild/child/mother/file" "/dev/null" +log_must zfs rename "$TESTPOOL/$TESTFS/grandchild/child/mother" "$TESTPOOL/$TESTFS/mother" +log_must zfs set limit_bw_read=none "$TESTPOOL/$TESTFS/mother" +log_must destroy_dataset "$TESTPOOL/$TESTFS/grandchild/child" +log_must destroy_dataset "$TESTPOOL/$TESTFS/grandchild" +# +# Revert the order of limits. +# +log_must create_dataset "$TESTPOOL/$TESTFS/mother/child" +log_must create_dataset "$TESTPOOL/$TESTFS/mother/child/grandchild" +log_must zfs set limit_bw_read=2M "$TESTPOOL/$TESTFS/mother" +log_must zfs set limit_bw_read=4M "$TESTPOOL/$TESTFS/mother/child" +log_must zfs set limit_bw_read=none "$TESTPOOL/$TESTFS/mother/child/grandchild" +log_must truncate -s 1G "$TESTDIR/mother/child/file" +log_must truncate -s 1G "$TESTDIR/mother/child/grandchild/file" +log_must ratelimit_bw 1 12 6 "$TESTDIR/mother/file" "/dev/null" +log_must ratelimit_bw 1 12 6 "$TESTDIR/mother/child/file" "/dev/null" +log_must ratelimit_bw 1 12 6 "$TESTDIR/mother/child/grandchild/file" "/dev/null" +log_must zfs rename "$TESTPOOL/$TESTFS/mother/child/grandchild" "$TESTPOOL/$TESTFS/grandchild" +log_must ratelimit_bw 1 10 1 "$TESTDIR/grandchild/file" "/dev/null" +log_must zfs rename "$TESTPOOL/$TESTFS/mother/child" "$TESTPOOL/$TESTFS/grandchild/child" +log_must ratelimit_bw 1 12 3 "$TESTDIR/grandchild/child/file" "/dev/null" +log_must zfs rename "$TESTPOOL/$TESTFS/mother" "$TESTPOOL/$TESTFS/grandchild/child/mother" +log_must ratelimit_bw 1 12 6 "$TESTDIR/grandchild/child/mother/file" "/dev/null" +# Changing limits at the bottom doesn't affect ancestors. +log_must zfs set limit_bw_read=6M "$TESTPOOL/$TESTFS/grandchild/child/mother" +log_must ratelimit_bw 1 10 1 "$TESTDIR/grandchild/file" "/dev/null" +log_must ratelimit_bw 1 12 3 "$TESTDIR/grandchild/child/file" "/dev/null" +log_must ratelimit_bw 1 12 3 "$TESTDIR/grandchild/child/mother/file" "/dev/null" +log_must zfs rename "$TESTPOOL/$TESTFS/grandchild/child/mother" "$TESTPOOL/$TESTFS/mother" +log_must zfs set limit_bw_read=none "$TESTPOOL/$TESTFS/mother" +log_must destroy_dataset "$TESTPOOL/$TESTFS/grandchild/child" +log_must destroy_dataset "$TESTPOOL/$TESTFS/grandchild" + +log_must destroy_dataset "$TESTPOOL/$TESTFS/mother" +log_must destroy_dataset "$TESTPOOL/$TESTFS/father" + +log_pass diff --git a/tests/zfs-tests/tests/functional/ratelimit/ratelimit_common.kshlib b/tests/zfs-tests/tests/functional/ratelimit/ratelimit_common.kshlib new file mode 100644 index 000000000000..49f05d8110a9 --- /dev/null +++ b/tests/zfs-tests/tests/functional/ratelimit/ratelimit_common.kshlib @@ -0,0 +1,282 @@ +# +# CDDL HEADER START +# +# The contents of this file are subject to the terms of the +# Common Development and Distribution License (the "License"). +# You may not use this file except in compliance with the License. +# +# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE +# or https://opensource.org/licenses/CDDL-1.0. +# See the License for the specific language governing permissions +# and limitations under the License. +# +# When distributing Covered Code, include this CDDL HEADER in each +# file and include the License file at usr/src/OPENSOLARIS.LICENSE. +# If applicable, add the following below this CDDL HEADER, with the +# fields enclosed by brackets "[]" replaced with your own identifying +# information: Portions Copyright [yyyy] [name of copyright owner] +# +# CDDL HEADER END +# + +# +# Copyright (c) 2024 The FreeBSD Foundation +# +# This software was developed by Pawel Dawidek +# under sponsorship from the FreeBSD Foundation. +# + +function ratelimit_reset +{ + + for dataset in $(zfs list -H -r -t filesystem,volume -o name $TESTPOOL); do + for type in bw op; do + for io in read write total; do + log_must zfs set limit_${type}_${io}=none $dataset + done + done + done +} + +function stopwatch_start +{ + STARTTIME=$(date "+%s") +} + +function stopwatch_stop +{ + typeset -r endtime=$(date "+%s") + + echo $((endtime-STARTTIME)) +} + +function stopwatch_check +{ + typeset -r exp=$1 # Expected duration. + typeset -r delta=$(stopwatch_stop) + + min=$((exp-1)) + max=$((exp+1)) + + if [ $delta -lt $min ]; then + log_note "took less time (${delta}s) than expected (${exp}s)" + return 1 + fi + if [ $delta -gt $max ]; then + log_note "took more time (${delta}s) than expected (${exp}s)" + return 1 + fi + return 0 +} + +function ddio +{ + typeset -r src=$1 + typeset -r dst=$2 + typeset -r cnt=$3 + + dd if=$src of=$dst bs=1M count=$cnt conv=notrunc 2>/dev/null +} + +function ratelimit_bw_read +{ + typeset -r cnt=$1 + typeset -r exp=$2 + + shift 2 + + stopwatch_start + for src in $@; do + ddio $src /dev/null $cnt & + done + wait + stopwatch_check $exp +} + +function ratelimit_bw_write +{ + typeset -r cnt=$1 + typeset -r exp=$2 + + shift 2 + + sync_pool $TESTPOOL + stopwatch_start + for src in $@; do + ddio /dev/zero $src $cnt & + done + wait + stopwatch_check $exp +} + +function ratelimit_bw +{ + typeset -r prs=$1 + typeset -r cnt=$2 + typeset -r exp=$3 + typeset -r src=$4 + typeset -r dst=$5 + + stopwatch_start + for i in {1..$prs}; do + ddio $src $dst $cnt & + done + wait + stopwatch_check $exp +} + +function ratelimit_filesystem_bw_read_single +{ + typeset -r lmt=$1 + typeset -r cnt=$2 + typeset -r exp=$3 + + log_must zfs set $lmt "$TESTPOOL/$TESTFS" + ratelimit_bw 1 $cnt $exp "$TESTDIR/file" "/dev/null" +} + +function ratelimit_filesystem_bw_write_single +{ + typeset -r lmt=$1 + typeset -r cnt=$2 + typeset -r exp=$3 + + log_must zfs set $lmt "$TESTPOOL/$TESTFS" + sync_pool $TESTPOOL + ratelimit_bw 1 $cnt $exp "/dev/zero" "$TESTDIR/file" +} + +function ratelimit_filesystem_bw_read_multiple +{ + typeset -r lmt=$1 + typeset -r cnt=$2 + typeset -r exp=$3 + + log_must zfs set $lmt "$TESTPOOL/$TESTFS" + ratelimit_bw 3 $cnt $exp "$TESTDIR/file" "/dev/null" +} + +function ratelimit_filesystem_bw_write_multiple +{ + typeset -r lmt=$1 + typeset -r cnt=$2 + typeset -r exp=$3 + + log_must zfs set $lmt "$TESTPOOL/$TESTFS" + sync_pool $TESTPOOL + ratelimit_bw 3 $cnt $exp "/dev/zero" "$TESTDIR/file" +} + +function ratelimit_volume_bw_read_single +{ + typeset -r lmt=$1 + typeset -r cnt=$2 + typeset -r exp=$3 + + log_must zfs set $lmt "$TESTPOOL/$TESTFS/vol" + ratelimit_bw 1 $cnt $exp "/dev/zvol/$TESTPOOL/$TESTFS/vol" "/dev/null" +} + +function ratelimit_volume_bw_write_single +{ + typeset -r lmt=$1 + typeset -r cnt=$2 + typeset -r exp=$3 + + log_must zfs set $lmt "$TESTPOOL/$TESTFS/vol" + sync_pool $TESTPOOL + ratelimit_bw 1 $cnt $exp "/dev/zero" "/dev/zvol/$TESTPOOL/$TESTFS/vol" +} + +function ratelimit_volume_bw_read_multiple +{ + typeset -r lmt=$1 + typeset -r cnt=$2 + typeset -r exp=$3 + + log_must zfs set $lmt "$TESTPOOL/$TESTFS/vol" + ratelimit_bw 3 $cnt $exp "/dev/zvol/$TESTPOOL/$TESTFS/vol" "/dev/null" +} + +function ratelimit_volume_bw_write_multiple +{ + typeset -r lmt=$1 + typeset -r cnt=$2 + typeset -r exp=$3 + + log_must zfs set $lmt "$TESTPOOL/$TESTFS/vol" + sync_pool $TESTPOOL + ratelimit_bw 3 $cnt $exp "/dev/zero" "/dev/zvol/$TESTPOOL/$TESTFS/vol" +} + +function ratelimit_filesystem_op_single +{ + typeset -r sys=$1 + typeset -r lmt=$2 + typeset -r cnt=$3 + typeset -r exp=$4 + typeset -r pth=$5 + + log_must zfs set $lmt "$TESTPOOL/$TESTFS" + sync_pool $TESTPOOL + stopwatch_start + log_must fsop $cnt $sys $pth + stopwatch_check $exp +} + +function ratelimit_filesystem_op_read_multiple +{ + typeset -r lmt=$1 + typeset -r cnt=$2 + typeset -r exp=$3 + + log_must zfs set $lmt "$TESTPOOL/$TESTFS" + stopwatch_start + fsop $cnt stat "$TESTDIR/file" & + fsop $cnt readlink "$TESTDIR/symlink" & + wait + stopwatch_check $exp +} + +function ratelimit_filesystem_op_write_multiple_create +{ + typeset -r lmt=$1 + typeset -r cnt=$2 + typeset -r exp=$3 + + log_must zfs set $lmt "$TESTPOOL/$TESTFS" + sync_pool $TESTPOOL + stopwatch_start + fsop $cnt chmod "$TESTDIR/file0" & + # Creating a file also triggers VOP_GETATTR() on FreeBSD, + # so switch to link(2) for the time being when setting + # total limits. + if is_freebsd && echo $lmt | grep -q limit_op_total=; then + fsop $cnt link "$TESTDIR/file1" & + else + fsop $cnt create "$TESTDIR/file1" & + fi + fsop $cnt mkdir "$TESTDIR/file2" & + fsop $cnt link "$TESTDIR/file3" & + fsop $cnt symlink "$TESTDIR/file4" & + wait + stopwatch_check $exp +} + +function ratelimit_filesystem_op_write_multiple_remove +{ + typeset -r lmt=$1 + typeset -r cnt=$2 + typeset -r exp=$3 + + log_must zfs set $lmt "$TESTPOOL/$TESTFS" + sync_pool $TESTPOOL + stopwatch_start + fsop $cnt chown "$TESTDIR/file0" & + fsop $cnt unlink "$TESTDIR/file1" & + fsop $cnt rmdir "$TESTDIR/file2" & + fsop $cnt unlink "$TESTDIR/file3" & + fsop $cnt unlink "$TESTDIR/file4" & + wait + stopwatch_check $exp +} diff --git a/tests/zfs-tests/tests/functional/ratelimit/ratelimit_random.ksh b/tests/zfs-tests/tests/functional/ratelimit/ratelimit_random.ksh new file mode 100755 index 000000000000..ebe8faf6af56 --- /dev/null +++ b/tests/zfs-tests/tests/functional/ratelimit/ratelimit_random.ksh @@ -0,0 +1,42 @@ +#! /bin/ksh -p +# +# CDDL HEADER START +# +# The contents of this file are subject to the terms of the +# Common Development and Distribution License (the "License"). +# You may not use this file except in compliance with the License. +# +# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE +# or https://opensource.org/licenses/CDDL-1.0. +# See the License for the specific language governing permissions +# and limitations under the License. +# +# When distributing Covered Code, include this CDDL HEADER in each +# file and include the License file at usr/src/OPENSOLARIS.LICENSE. +# If applicable, add the following below this CDDL HEADER, with the +# fields enclosed by brackets "[]" replaced with your own identifying +# information: Portions Copyright [yyyy] [name of copyright owner] +# +# CDDL HEADER END +# + +# +# Copyright (c) 2024 The FreeBSD Foundation +# +# This software was developed by Pawel Dawidek +# under sponsorship from the FreeBSD Foundation. +# + +# All ratelimit tests take too much time, so as a part of the common.run +# runfile we will execute one random test. +# All tests can be run from the ratelimit.run runfile. + +. $STF_SUITE/include/libtest.shlib + +RATELIMIT_DIR="${STF_SUITE}/tests/functional/ratelimit" + +RATELIMIT_TEST=$(random_get $(ls -1 $RATELIMIT_DIR/*.ksh | grep -Ev '/(cleanup|setup)\.ksh$')) + +log_note "Random test choosen: ${RATELIMIT_TEST}" + +. $RATELIMIT_TEST diff --git a/tests/zfs-tests/tests/functional/ratelimit/setup.ksh b/tests/zfs-tests/tests/functional/ratelimit/setup.ksh new file mode 100755 index 000000000000..8c59b9d4e107 --- /dev/null +++ b/tests/zfs-tests/tests/functional/ratelimit/setup.ksh @@ -0,0 +1,48 @@ +#!/bin/ksh -p +# +# CDDL HEADER START +# +# The contents of this file are subject to the terms of the +# Common Development and Distribution License (the "License"). +# You may not use this file except in compliance with the License. +# +# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE +# or https://opensource.org/licenses/CDDL-1.0. +# See the License for the specific language governing permissions +# and limitations under the License. +# +# When distributing Covered Code, include this CDDL HEADER in each +# file and include the License file at usr/src/OPENSOLARIS.LICENSE. +# If applicable, add the following below this CDDL HEADER, with the +# fields enclosed by brackets "[]" replaced with your own identifying +# information: Portions Copyright [yyyy] [name of copyright owner] +# +# CDDL HEADER END +# + +# +# Copyright (c) 2024 The FreeBSD Foundation +# +# This software was developed by Pawel Dawidek +# under sponsorship from the FreeBSD Foundation. +# + +. $STF_SUITE/include/libtest.shlib + +if ! command -v fsop > /dev/null ; then + log_unsupported "fsop program required to test ratelimiting" +fi + +DISK=${DISKS%% *} + +default_setup_noexit $DISK "true" + +# Make the pool as fast as possible, so we don't have tests failing, because +# the test pool is a bit too slow. +log_must zfs set atime=off $TESTPOOL +log_must zfs set checksum=off $TESTPOOL +log_must zfs set compress=zle $TESTPOOL +log_must zfs set recordsize=1M $TESTPOOL +log_must zfs set sync=disabled $TESTPOOL + +log_pass diff --git a/tests/zfs-tests/tests/functional/ratelimit/volume_bw_combined.ksh b/tests/zfs-tests/tests/functional/ratelimit/volume_bw_combined.ksh new file mode 100755 index 000000000000..bae6e4713515 --- /dev/null +++ b/tests/zfs-tests/tests/functional/ratelimit/volume_bw_combined.ksh @@ -0,0 +1,105 @@ +#! /bin/ksh -p +# +# CDDL HEADER START +# +# The contents of this file are subject to the terms of the +# Common Development and Distribution License (the "License"). +# You may not use this file except in compliance with the License. +# +# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE +# or https://opensource.org/licenses/CDDL-1.0. +# See the License for the specific language governing permissions +# and limitations under the License. +# +# When distributing Covered Code, include this CDDL HEADER in each +# file and include the License file at usr/src/OPENSOLARIS.LICENSE. +# If applicable, add the following below this CDDL HEADER, with the +# fields enclosed by brackets "[]" replaced with your own identifying +# information: Portions Copyright [yyyy] [name of copyright owner] +# +# CDDL HEADER END +# + +# +# Copyright (c) 2024 The FreeBSD Foundation +# +# This software was developed by Pawel Dawidek +# under sponsorship from the FreeBSD Foundation. +# + +. $STF_SUITE/include/libtest.shlib +. $STF_SUITE/tests/functional/ratelimit/ratelimit_common.kshlib + +verify_runnable "both" + +log_assert "Verify configurations where multiple limit types are set" + +ratelimit_reset + +log_must create_volume "$TESTPOOL/$TESTFS/vol0" 100M +log_must create_volume "$TESTPOOL/$TESTFS/vol1" 100M + +log_must zfs set limit_bw_read=4M "$TESTPOOL/$TESTFS" +log_must zfs set limit_bw_write=5M "$TESTPOOL/$TESTFS" +log_must zfs set limit_bw_total=6M "$TESTPOOL/$TESTFS" + +log_must ratelimit_bw_read 12 3 "/dev/zvol/$TESTPOOL/$TESTFS/vol0" +log_must ratelimit_bw_write 15 3 "/dev/zvol/$TESTPOOL/$TESTFS/vol1" +stopwatch_start +ddio "/dev/zvol/$TESTPOOL/$TESTFS/vol0" "/dev/null" 36 & +ddio "/dev/zero" "/dev/zvol/$TESTPOOL/$TESTFS/vol1" 36 & +wait +stopwatch_check 12 + +log_must zfs set limit_bw_read=none "$TESTPOOL/$TESTFS" +log_must zfs set limit_bw_write=none "$TESTPOOL/$TESTFS" +log_must zfs set limit_bw_total=none "$TESTPOOL/$TESTFS" + +log_must destroy_dataset "$TESTPOOL/$TESTFS/vol0" +log_must destroy_dataset "$TESTPOOL/$TESTFS/vol1" + +log_must create_dataset "$TESTPOOL/$TESTFS/lvl0" +log_must create_dataset "$TESTPOOL/$TESTFS/lvl0/lvl1" +log_must create_dataset "$TESTPOOL/$TESTFS/lvl0/lvl1/lvl2" +log_must create_volume "$TESTPOOL/$TESTFS/lvl0/lvl1/lvl2/vol0" 100M +log_must create_volume "$TESTPOOL/$TESTFS/lvl0/lvl1/lvl2/vol1" 100M + +log_must zfs set limit_bw_read=4M "$TESTPOOL/$TESTFS/lvl0" +log_must zfs set limit_bw_write=5M "$TESTPOOL/$TESTFS/lvl0/lvl1" +log_must zfs set limit_bw_total=6M "$TESTPOOL/$TESTFS/lvl0/lvl1/lvl2" + +log_must ratelimit_bw_read 12 3 "/dev/zvol/$TESTPOOL/$TESTFS/lvl0/lvl1/lvl2/vol0" +log_must ratelimit_bw_write 15 3 "/dev/zvol/$TESTPOOL/$TESTFS/lvl0/lvl1/lvl2/vol1" +stopwatch_start +ddio "/dev/zvol/$TESTPOOL/$TESTFS/lvl0/lvl1/lvl2/vol0" "/dev/null" 36 & +ddio "/dev/zero" "/dev/zvol/$TESTPOOL/$TESTFS/lvl0/lvl1/lvl2/vol1" 36 & +wait +stopwatch_check 12 + +log_must zfs set limit_bw_read=none "$TESTPOOL/$TESTFS/lvl0" +log_must zfs set limit_bw_write=none "$TESTPOOL/$TESTFS/lvl0/lvl1" +log_must zfs set limit_bw_total=none "$TESTPOOL/$TESTFS/lvl0/lvl1/lvl2" + +log_must zfs set limit_bw_total=6M "$TESTPOOL/$TESTFS/lvl0" +log_must zfs set limit_bw_read=4M "$TESTPOOL/$TESTFS/lvl0/lvl1" +log_must zfs set limit_bw_write=5M "$TESTPOOL/$TESTFS/lvl0/lvl1/lvl2" + +log_must ratelimit_bw_read 12 3 "/dev/zvol/$TESTPOOL/$TESTFS/lvl0/lvl1/lvl2/vol0" +log_must ratelimit_bw_write 15 3 "/dev/zvol/$TESTPOOL/$TESTFS/lvl0/lvl1/lvl2/vol1" +stopwatch_start +ddio "/dev/zvol/$TESTPOOL/$TESTFS/lvl0/lvl1/lvl2/vol0" "/dev/null" 36 & +ddio "/dev/zero" "/dev/zvol/$TESTPOOL/$TESTFS/lvl0/lvl1/lvl2/vol1" 36 & +wait +stopwatch_check 12 + +log_must zfs set limit_bw_total=none "$TESTPOOL/$TESTFS/lvl0" +log_must zfs set limit_bw_read=none "$TESTPOOL/$TESTFS/lvl0/lvl1" +log_must zfs set limit_bw_write=none "$TESTPOOL/$TESTFS/lvl0/lvl1/lvl2" + +log_must destroy_dataset "$TESTPOOL/$TESTFS/lvl0/lvl1/lvl2/vol1" +log_must destroy_dataset "$TESTPOOL/$TESTFS/lvl0/lvl1/lvl2/vol0" +log_must destroy_dataset "$TESTPOOL/$TESTFS/lvl0/lvl1/lvl2" +log_must destroy_dataset "$TESTPOOL/$TESTFS/lvl0/lvl1" +log_must destroy_dataset "$TESTPOOL/$TESTFS/lvl0" + +log_pass diff --git a/tests/zfs-tests/tests/functional/ratelimit/volume_bw_hierarchical_horizontal.ksh b/tests/zfs-tests/tests/functional/ratelimit/volume_bw_hierarchical_horizontal.ksh new file mode 100755 index 000000000000..e4350efa3c6b --- /dev/null +++ b/tests/zfs-tests/tests/functional/ratelimit/volume_bw_hierarchical_horizontal.ksh @@ -0,0 +1,211 @@ +#! /bin/ksh -p +# +# CDDL HEADER START +# +# The contents of this file are subject to the terms of the +# Common Development and Distribution License (the "License"). +# You may not use this file except in compliance with the License. +# +# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE +# or https://opensource.org/licenses/CDDL-1.0. +# See the License for the specific language governing permissions +# and limitations under the License. +# +# When distributing Covered Code, include this CDDL HEADER in each +# file and include the License file at usr/src/OPENSOLARIS.LICENSE. +# If applicable, add the following below this CDDL HEADER, with the +# fields enclosed by brackets "[]" replaced with your own identifying +# information: Portions Copyright [yyyy] [name of copyright owner] +# +# CDDL HEADER END +# + +# +# Copyright (c) 2024 The FreeBSD Foundation +# +# This software was developed by Pawel Dawidek +# under sponsorship from the FreeBSD Foundation. +# + +. $STF_SUITE/include/libtest.shlib +. $STF_SUITE/tests/functional/ratelimit/ratelimit_common.kshlib + +verify_runnable "both" + +log_assert "Verify hierarchical limits for multiple ZVOLs at the same level" + +ratelimit_reset + +log_must truncate -s 1G "$TESTDIR/file" + +log_must create_volume "$TESTPOOL/$TESTFS/foo" 16M +log_must create_volume "$TESTPOOL/$TESTFS/bar" 16M +log_must create_volume "$TESTPOOL/$TESTFS/baz" 16M + +log_must zfs set limit_bw_read=none "$TESTPOOL/$TESTFS" +log_must zfs set limit_bw_read=1M "$TESTPOOL/$TESTFS/foo" +log_must zfs set limit_bw_read=1M "$TESTPOOL/$TESTFS/bar" +log_must zfs set limit_bw_read=1M "$TESTPOOL/$TESTFS/baz" +log_must ratelimit_bw_read 6 6 "/dev/zvol/$TESTPOOL/$TESTFS/foo" +log_must ratelimit_bw_read 6 6 "/dev/zvol/$TESTPOOL/$TESTFS/foo" "/dev/zvol/$TESTPOOL/$TESTFS/bar" +log_must ratelimit_bw_read 6 6 "/dev/zvol/$TESTPOOL/$TESTFS/foo" "/dev/zvol/$TESTPOOL/$TESTFS/bar" "/dev/zvol/$TESTPOOL/$TESTFS/baz" +log_must ratelimit_bw_read 6 6 "$TESTDIR/file" "/dev/zvol/$TESTPOOL/$TESTFS/foo" "/dev/zvol/$TESTPOOL/$TESTFS/bar" "/dev/zvol/$TESTPOOL/$TESTFS/baz" + +log_must zfs set limit_bw_read=2M "$TESTPOOL/$TESTFS" +log_must zfs set limit_bw_read=1M "$TESTPOOL/$TESTFS/foo" +log_must zfs set limit_bw_read=1M "$TESTPOOL/$TESTFS/bar" +log_must zfs set limit_bw_read=1M "$TESTPOOL/$TESTFS/baz" +log_must ratelimit_bw_read 6 6 "/dev/zvol/$TESTPOOL/$TESTFS/foo" "/dev/zvol/$TESTPOOL/$TESTFS/bar" +log_must ratelimit_bw_read 6 9 "/dev/zvol/$TESTPOOL/$TESTFS/foo" "/dev/zvol/$TESTPOOL/$TESTFS/bar" "/dev/zvol/$TESTPOOL/$TESTFS/baz" +log_must ratelimit_bw_read 6 12 "$TESTDIR/file" "/dev/zvol/$TESTPOOL/$TESTFS/foo" "/dev/zvol/$TESTPOOL/$TESTFS/bar" "/dev/zvol/$TESTPOOL/$TESTFS/baz" + +log_must zfs set limit_bw_read=2M "$TESTPOOL/$TESTFS" +log_must zfs set limit_bw_read=2M "$TESTPOOL/$TESTFS/foo" +log_must zfs set limit_bw_read=2M "$TESTPOOL/$TESTFS/bar" +log_must zfs set limit_bw_read=2M "$TESTPOOL/$TESTFS/baz" +log_must ratelimit_bw_read 6 3 "/dev/zvol/$TESTPOOL/$TESTFS/foo" +log_must ratelimit_bw_read 6 6 "/dev/zvol/$TESTPOOL/$TESTFS/foo" "/dev/zvol/$TESTPOOL/$TESTFS/bar" +log_must ratelimit_bw_read 6 9 "/dev/zvol/$TESTPOOL/$TESTFS/foo" "/dev/zvol/$TESTPOOL/$TESTFS/bar" "/dev/zvol/$TESTPOOL/$TESTFS/baz" +log_must ratelimit_bw_read 6 12 "$TESTDIR/file" "/dev/zvol/$TESTPOOL/$TESTFS/foo" "/dev/zvol/$TESTPOOL/$TESTFS/bar" "/dev/zvol/$TESTPOOL/$TESTFS/baz" + +log_must zfs set limit_bw_read=2M "$TESTPOOL/$TESTFS" +log_must zfs set limit_bw_read=none "$TESTPOOL/$TESTFS/foo" +log_must zfs set limit_bw_read=none "$TESTPOOL/$TESTFS/bar" +log_must zfs set limit_bw_read=none "$TESTPOOL/$TESTFS/baz" +log_must ratelimit_bw_read 6 3 "/dev/zvol/$TESTPOOL/$TESTFS/foo" +log_must ratelimit_bw_read 6 6 "/dev/zvol/$TESTPOOL/$TESTFS/foo" "/dev/zvol/$TESTPOOL/$TESTFS/bar" +log_must ratelimit_bw_read 6 9 "/dev/zvol/$TESTPOOL/$TESTFS/foo" "/dev/zvol/$TESTPOOL/$TESTFS/bar" "/dev/zvol/$TESTPOOL/$TESTFS/baz" +log_must ratelimit_bw_read 6 12 "$TESTDIR/file" "/dev/zvol/$TESTPOOL/$TESTFS/foo" "/dev/zvol/$TESTPOOL/$TESTFS/bar" "/dev/zvol/$TESTPOOL/$TESTFS/baz" + +log_must zfs set limit_bw_read=none "$TESTPOOL/$TESTFS" +log_must zfs set limit_bw_read=none "$TESTPOOL/$TESTFS/foo" +log_must zfs set limit_bw_read=none "$TESTPOOL/$TESTFS/bar" +log_must zfs set limit_bw_read=none "$TESTPOOL/$TESTFS/baz" + +log_must zfs set limit_bw_total=none "$TESTPOOL/$TESTFS" +log_must zfs set limit_bw_total=1M "$TESTPOOL/$TESTFS/foo" +log_must zfs set limit_bw_total=1M "$TESTPOOL/$TESTFS/bar" +log_must zfs set limit_bw_total=1M "$TESTPOOL/$TESTFS/baz" +log_must ratelimit_bw_read 6 6 "/dev/zvol/$TESTPOOL/$TESTFS/foo" +log_must ratelimit_bw_read 6 6 "/dev/zvol/$TESTPOOL/$TESTFS/foo" "/dev/zvol/$TESTPOOL/$TESTFS/bar" +log_must ratelimit_bw_read 6 6 "/dev/zvol/$TESTPOOL/$TESTFS/foo" "/dev/zvol/$TESTPOOL/$TESTFS/bar" "/dev/zvol/$TESTPOOL/$TESTFS/baz" +log_must ratelimit_bw_read 6 6 "$TESTDIR/file" "/dev/zvol/$TESTPOOL/$TESTFS/foo" "/dev/zvol/$TESTPOOL/$TESTFS/bar" "/dev/zvol/$TESTPOOL/$TESTFS/baz" + +log_must zfs set limit_bw_total=2M "$TESTPOOL/$TESTFS" +log_must zfs set limit_bw_total=1M "$TESTPOOL/$TESTFS/foo" +log_must zfs set limit_bw_total=1M "$TESTPOOL/$TESTFS/bar" +log_must zfs set limit_bw_total=1M "$TESTPOOL/$TESTFS/baz" +log_must ratelimit_bw_read 6 6 "/dev/zvol/$TESTPOOL/$TESTFS/foo" "/dev/zvol/$TESTPOOL/$TESTFS/bar" +log_must ratelimit_bw_read 6 9 "/dev/zvol/$TESTPOOL/$TESTFS/foo" "/dev/zvol/$TESTPOOL/$TESTFS/bar" "/dev/zvol/$TESTPOOL/$TESTFS/baz" +log_must ratelimit_bw_read 6 12 "$TESTDIR/file" "/dev/zvol/$TESTPOOL/$TESTFS/foo" "/dev/zvol/$TESTPOOL/$TESTFS/bar" "/dev/zvol/$TESTPOOL/$TESTFS/baz" + +log_must zfs set limit_bw_total=2M "$TESTPOOL/$TESTFS" +log_must zfs set limit_bw_total=2M "$TESTPOOL/$TESTFS/foo" +log_must zfs set limit_bw_total=2M "$TESTPOOL/$TESTFS/bar" +log_must zfs set limit_bw_total=2M "$TESTPOOL/$TESTFS/baz" +log_must ratelimit_bw_read 6 3 "/dev/zvol/$TESTPOOL/$TESTFS/foo" +log_must ratelimit_bw_read 6 6 "/dev/zvol/$TESTPOOL/$TESTFS/foo" "/dev/zvol/$TESTPOOL/$TESTFS/bar" +log_must ratelimit_bw_read 6 9 "/dev/zvol/$TESTPOOL/$TESTFS/foo" "/dev/zvol/$TESTPOOL/$TESTFS/bar" "/dev/zvol/$TESTPOOL/$TESTFS/baz" +log_must ratelimit_bw_read 6 12 "$TESTDIR/file" "/dev/zvol/$TESTPOOL/$TESTFS/foo" "/dev/zvol/$TESTPOOL/$TESTFS/bar" "/dev/zvol/$TESTPOOL/$TESTFS/baz" + +log_must zfs set limit_bw_total=2M "$TESTPOOL/$TESTFS" +log_must zfs set limit_bw_total=none "$TESTPOOL/$TESTFS/foo" +log_must zfs set limit_bw_total=none "$TESTPOOL/$TESTFS/bar" +log_must zfs set limit_bw_total=none "$TESTPOOL/$TESTFS/baz" +log_must ratelimit_bw_read 6 3 "/dev/zvol/$TESTPOOL/$TESTFS/foo" +log_must ratelimit_bw_read 6 6 "/dev/zvol/$TESTPOOL/$TESTFS/foo" "/dev/zvol/$TESTPOOL/$TESTFS/bar" +log_must ratelimit_bw_read 6 9 "/dev/zvol/$TESTPOOL/$TESTFS/foo" "/dev/zvol/$TESTPOOL/$TESTFS/bar" "/dev/zvol/$TESTPOOL/$TESTFS/baz" +log_must ratelimit_bw_read 6 12 "$TESTDIR/file" "/dev/zvol/$TESTPOOL/$TESTFS/foo" "/dev/zvol/$TESTPOOL/$TESTFS/bar" "/dev/zvol/$TESTPOOL/$TESTFS/baz" + +log_must zfs set limit_bw_total=none "$TESTPOOL/$TESTFS" +log_must zfs set limit_bw_total=none "$TESTPOOL/$TESTFS/foo" +log_must zfs set limit_bw_total=none "$TESTPOOL/$TESTFS/bar" +log_must zfs set limit_bw_total=none "$TESTPOOL/$TESTFS/baz" + +log_must zfs set limit_bw_write=none "$TESTPOOL/$TESTFS" +log_must zfs set limit_bw_write=1M "$TESTPOOL/$TESTFS/foo" +log_must zfs set limit_bw_write=1M "$TESTPOOL/$TESTFS/bar" +log_must zfs set limit_bw_write=1M "$TESTPOOL/$TESTFS/baz" +log_must ratelimit_bw_write 6 6 "/dev/zvol/$TESTPOOL/$TESTFS/foo" +log_must ratelimit_bw_write 6 6 "/dev/zvol/$TESTPOOL/$TESTFS/foo" "/dev/zvol/$TESTPOOL/$TESTFS/bar" +log_must ratelimit_bw_write 6 6 "/dev/zvol/$TESTPOOL/$TESTFS/foo" "/dev/zvol/$TESTPOOL/$TESTFS/bar" "/dev/zvol/$TESTPOOL/$TESTFS/baz" +log_must ratelimit_bw_write 6 6 "$TESTDIR/file" "/dev/zvol/$TESTPOOL/$TESTFS/foo" "/dev/zvol/$TESTPOOL/$TESTFS/bar" "/dev/zvol/$TESTPOOL/$TESTFS/baz" + +log_must zfs set limit_bw_write=2M "$TESTPOOL/$TESTFS" +log_must zfs set limit_bw_write=1M "$TESTPOOL/$TESTFS/foo" +log_must zfs set limit_bw_write=1M "$TESTPOOL/$TESTFS/bar" +log_must zfs set limit_bw_write=1M "$TESTPOOL/$TESTFS/baz" +log_must ratelimit_bw_write 6 6 "/dev/zvol/$TESTPOOL/$TESTFS/foo" "/dev/zvol/$TESTPOOL/$TESTFS/bar" +log_must ratelimit_bw_write 6 9 "/dev/zvol/$TESTPOOL/$TESTFS/foo" "/dev/zvol/$TESTPOOL/$TESTFS/bar" "/dev/zvol/$TESTPOOL/$TESTFS/baz" +log_must ratelimit_bw_write 6 12 "$TESTDIR/file" "/dev/zvol/$TESTPOOL/$TESTFS/foo" "/dev/zvol/$TESTPOOL/$TESTFS/bar" "/dev/zvol/$TESTPOOL/$TESTFS/baz" + +log_must zfs set limit_bw_write=2M "$TESTPOOL/$TESTFS" +log_must zfs set limit_bw_write=2M "$TESTPOOL/$TESTFS/foo" +log_must zfs set limit_bw_write=2M "$TESTPOOL/$TESTFS/bar" +log_must zfs set limit_bw_write=2M "$TESTPOOL/$TESTFS/baz" +log_must ratelimit_bw_write 6 3 "/dev/zvol/$TESTPOOL/$TESTFS/foo" +log_must ratelimit_bw_write 6 6 "/dev/zvol/$TESTPOOL/$TESTFS/foo" "/dev/zvol/$TESTPOOL/$TESTFS/bar" +log_must ratelimit_bw_write 6 9 "/dev/zvol/$TESTPOOL/$TESTFS/foo" "/dev/zvol/$TESTPOOL/$TESTFS/bar" "/dev/zvol/$TESTPOOL/$TESTFS/baz" +log_must ratelimit_bw_write 6 12 "$TESTDIR/file" "/dev/zvol/$TESTPOOL/$TESTFS/foo" "/dev/zvol/$TESTPOOL/$TESTFS/bar" "/dev/zvol/$TESTPOOL/$TESTFS/baz" + +log_must zfs set limit_bw_write=2M "$TESTPOOL/$TESTFS" +log_must zfs set limit_bw_write=none "$TESTPOOL/$TESTFS/foo" +log_must zfs set limit_bw_write=none "$TESTPOOL/$TESTFS/bar" +log_must zfs set limit_bw_write=none "$TESTPOOL/$TESTFS/baz" +log_must ratelimit_bw_write 6 3 "/dev/zvol/$TESTPOOL/$TESTFS/foo" +log_must ratelimit_bw_write 6 6 "/dev/zvol/$TESTPOOL/$TESTFS/foo" "/dev/zvol/$TESTPOOL/$TESTFS/bar" +log_must ratelimit_bw_write 6 9 "/dev/zvol/$TESTPOOL/$TESTFS/foo" "/dev/zvol/$TESTPOOL/$TESTFS/bar" "/dev/zvol/$TESTPOOL/$TESTFS/baz" +log_must ratelimit_bw_write 6 12 "$TESTDIR/file" "/dev/zvol/$TESTPOOL/$TESTFS/foo" "/dev/zvol/$TESTPOOL/$TESTFS/bar" "/dev/zvol/$TESTPOOL/$TESTFS/baz" + +log_must zfs set limit_bw_write=none "$TESTPOOL/$TESTFS" +log_must zfs set limit_bw_write=none "$TESTPOOL/$TESTFS/foo" +log_must zfs set limit_bw_write=none "$TESTPOOL/$TESTFS/bar" +log_must zfs set limit_bw_write=none "$TESTPOOL/$TESTFS/baz" + +log_must zfs set limit_bw_total=none "$TESTPOOL/$TESTFS" +log_must zfs set limit_bw_total=1M "$TESTPOOL/$TESTFS/foo" +log_must zfs set limit_bw_total=1M "$TESTPOOL/$TESTFS/bar" +log_must zfs set limit_bw_total=1M "$TESTPOOL/$TESTFS/baz" +log_must ratelimit_bw_write 6 6 "/dev/zvol/$TESTPOOL/$TESTFS/foo" +log_must ratelimit_bw_write 6 6 "/dev/zvol/$TESTPOOL/$TESTFS/foo" "/dev/zvol/$TESTPOOL/$TESTFS/bar" +log_must ratelimit_bw_write 6 6 "/dev/zvol/$TESTPOOL/$TESTFS/foo" "/dev/zvol/$TESTPOOL/$TESTFS/bar" "/dev/zvol/$TESTPOOL/$TESTFS/baz" +log_must ratelimit_bw_write 6 6 "$TESTDIR/file" "/dev/zvol/$TESTPOOL/$TESTFS/foo" "/dev/zvol/$TESTPOOL/$TESTFS/bar" "/dev/zvol/$TESTPOOL/$TESTFS/baz" + +log_must zfs set limit_bw_total=2M "$TESTPOOL/$TESTFS" +log_must zfs set limit_bw_total=1M "$TESTPOOL/$TESTFS/foo" +log_must zfs set limit_bw_total=1M "$TESTPOOL/$TESTFS/bar" +log_must zfs set limit_bw_total=1M "$TESTPOOL/$TESTFS/baz" +log_must ratelimit_bw_write 6 6 "/dev/zvol/$TESTPOOL/$TESTFS/foo" "/dev/zvol/$TESTPOOL/$TESTFS/bar" +log_must ratelimit_bw_write 6 9 "/dev/zvol/$TESTPOOL/$TESTFS/foo" "/dev/zvol/$TESTPOOL/$TESTFS/bar" "/dev/zvol/$TESTPOOL/$TESTFS/baz" +log_must ratelimit_bw_write 6 12 "$TESTDIR/file" "/dev/zvol/$TESTPOOL/$TESTFS/foo" "/dev/zvol/$TESTPOOL/$TESTFS/bar" "/dev/zvol/$TESTPOOL/$TESTFS/baz" + +log_must zfs set limit_bw_total=2M "$TESTPOOL/$TESTFS" +log_must zfs set limit_bw_total=2M "$TESTPOOL/$TESTFS/foo" +log_must zfs set limit_bw_total=2M "$TESTPOOL/$TESTFS/bar" +log_must zfs set limit_bw_total=2M "$TESTPOOL/$TESTFS/baz" +log_must ratelimit_bw_write 6 3 "/dev/zvol/$TESTPOOL/$TESTFS/foo" +log_must ratelimit_bw_write 6 6 "/dev/zvol/$TESTPOOL/$TESTFS/foo" "/dev/zvol/$TESTPOOL/$TESTFS/bar" +log_must ratelimit_bw_write 6 9 "/dev/zvol/$TESTPOOL/$TESTFS/foo" "/dev/zvol/$TESTPOOL/$TESTFS/bar" "/dev/zvol/$TESTPOOL/$TESTFS/baz" +log_must ratelimit_bw_write 6 12 "$TESTDIR/file" "/dev/zvol/$TESTPOOL/$TESTFS/foo" "/dev/zvol/$TESTPOOL/$TESTFS/bar" "/dev/zvol/$TESTPOOL/$TESTFS/baz" + +log_must zfs set limit_bw_total=2M "$TESTPOOL/$TESTFS" +log_must zfs set limit_bw_total=none "$TESTPOOL/$TESTFS/foo" +log_must zfs set limit_bw_total=none "$TESTPOOL/$TESTFS/bar" +log_must zfs set limit_bw_total=none "$TESTPOOL/$TESTFS/baz" +log_must ratelimit_bw_write 6 3 "/dev/zvol/$TESTPOOL/$TESTFS/foo" +log_must ratelimit_bw_write 6 6 "/dev/zvol/$TESTPOOL/$TESTFS/foo" "/dev/zvol/$TESTPOOL/$TESTFS/bar" +log_must ratelimit_bw_write 6 9 "/dev/zvol/$TESTPOOL/$TESTFS/foo" "/dev/zvol/$TESTPOOL/$TESTFS/bar" "/dev/zvol/$TESTPOOL/$TESTFS/baz" +log_must ratelimit_bw_write 6 12 "$TESTDIR/file" "/dev/zvol/$TESTPOOL/$TESTFS/foo" "/dev/zvol/$TESTPOOL/$TESTFS/bar" "/dev/zvol/$TESTPOOL/$TESTFS/baz" + +log_must zfs set limit_bw_total=none "$TESTPOOL/$TESTFS" +log_must zfs set limit_bw_total=none "$TESTPOOL/$TESTFS/foo" +log_must zfs set limit_bw_total=none "$TESTPOOL/$TESTFS/bar" +log_must zfs set limit_bw_total=none "$TESTPOOL/$TESTFS/baz" + +log_must destroy_dataset "$TESTPOOL/$TESTFS/foo" +log_must destroy_dataset "$TESTPOOL/$TESTFS/bar" +log_must destroy_dataset "$TESTPOOL/$TESTFS/baz" + +rm -f "$TESTDIR/file" + +log_pass diff --git a/tests/zfs-tests/tests/functional/ratelimit/volume_bw_hierarchical_vertical_read.ksh b/tests/zfs-tests/tests/functional/ratelimit/volume_bw_hierarchical_vertical_read.ksh new file mode 100755 index 000000000000..e253ce30fb94 --- /dev/null +++ b/tests/zfs-tests/tests/functional/ratelimit/volume_bw_hierarchical_vertical_read.ksh @@ -0,0 +1,63 @@ +#! /bin/ksh -p +# +# CDDL HEADER START +# +# The contents of this file are subject to the terms of the +# Common Development and Distribution License (the "License"). +# You may not use this file except in compliance with the License. +# +# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE +# or https://opensource.org/licenses/CDDL-1.0. +# See the License for the specific language governing permissions +# and limitations under the License. +# +# When distributing Covered Code, include this CDDL HEADER in each +# file and include the License file at usr/src/OPENSOLARIS.LICENSE. +# If applicable, add the following below this CDDL HEADER, with the +# fields enclosed by brackets "[]" replaced with your own identifying +# information: Portions Copyright [yyyy] [name of copyright owner] +# +# CDDL HEADER END +# + +# +# Copyright (c) 2024 The FreeBSD Foundation +# +# This software was developed by Pawel Dawidek +# under sponsorship from the FreeBSD Foundation. +# + +. $STF_SUITE/include/libtest.shlib +. $STF_SUITE/tests/functional/ratelimit/ratelimit_common.kshlib + +verify_runnable "both" + +log_assert "Verify hierarchical bandwidth read limits configured on multiple levels" + +ratelimit_reset + +log_must create_dataset "$TESTPOOL/$TESTFS/lvl0" +log_must create_dataset "$TESTPOOL/$TESTFS/lvl0/lvl1" +log_must create_volume "$TESTPOOL/$TESTFS/lvl0/lvl1/lvl2" 16M + +for lvl0 in none 1M 3M 5M; do + for lvl1 in none 1M 3M 5M; do + for lvl2 in none 1M 3M 5M; do + # We need at least one level with 1M limit. + if [ $lvl0 != "1M" ] && [ $lvl1 != "1M" ] && [ $lvl2 != "1M" ]; then + continue + fi + + log_must zfs set limit_bw_read=$lvl0 "$TESTPOOL/$TESTFS/lvl0" + log_must zfs set limit_bw_read=$lvl1 "$TESTPOOL/$TESTFS/lvl0/lvl1" + log_must zfs set limit_bw_read=$lvl2 "$TESTPOOL/$TESTFS/lvl0/lvl1/lvl2" + log_must ratelimit_bw_read 5 5 "/dev/zvol/$TESTPOOL/$TESTFS/lvl0/lvl1/lvl2" + done + done +done + +log_must destroy_dataset "$TESTPOOL/$TESTFS/lvl0/lvl1/lvl2" +log_must destroy_dataset "$TESTPOOL/$TESTFS/lvl0/lvl1" +log_must destroy_dataset "$TESTPOOL/$TESTFS/lvl0" + +log_pass diff --git a/tests/zfs-tests/tests/functional/ratelimit/volume_bw_hierarchical_vertical_total.ksh b/tests/zfs-tests/tests/functional/ratelimit/volume_bw_hierarchical_vertical_total.ksh new file mode 100755 index 000000000000..8368b1ff7038 --- /dev/null +++ b/tests/zfs-tests/tests/functional/ratelimit/volume_bw_hierarchical_vertical_total.ksh @@ -0,0 +1,64 @@ +#! /bin/ksh -p +# +# CDDL HEADER START +# +# The contents of this file are subject to the terms of the +# Common Development and Distribution License (the "License"). +# You may not use this file except in compliance with the License. +# +# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE +# or https://opensource.org/licenses/CDDL-1.0. +# See the License for the specific language governing permissions +# and limitations under the License. +# +# When distributing Covered Code, include this CDDL HEADER in each +# file and include the License file at usr/src/OPENSOLARIS.LICENSE. +# If applicable, add the following below this CDDL HEADER, with the +# fields enclosed by brackets "[]" replaced with your own identifying +# information: Portions Copyright [yyyy] [name of copyright owner] +# +# CDDL HEADER END +# + +# +# Copyright (c) 2024 The FreeBSD Foundation +# +# This software was developed by Pawel Dawidek +# under sponsorship from the FreeBSD Foundation. +# + +. $STF_SUITE/include/libtest.shlib +. $STF_SUITE/tests/functional/ratelimit/ratelimit_common.kshlib + +verify_runnable "both" + +log_assert "Verify hierarchical bandwidth total limits configured on multiple levels" + +ratelimit_reset + +log_must create_dataset "$TESTPOOL/$TESTFS/lvl0" +log_must create_dataset "$TESTPOOL/$TESTFS/lvl0/lvl1" +log_must create_volume "$TESTPOOL/$TESTFS/lvl0/lvl1/lvl2" 16M + +for lvl0 in none 1M 3M 5M; do + for lvl1 in none 1M 3M 5M; do + for lvl2 in none 1M 3M 5M; do + # We need at least one level with 1M limit. + if [ $lvl0 != "1M" ] && [ $lvl1 != "1M" ] && [ $lvl2 != "1M" ]; then + continue + fi + + log_must zfs set limit_bw_total=$lvl0 "$TESTPOOL/$TESTFS/lvl0" + log_must zfs set limit_bw_total=$lvl1 "$TESTPOOL/$TESTFS/lvl0/lvl1" + log_must zfs set limit_bw_total=$lvl2 "$TESTPOOL/$TESTFS/lvl0/lvl1/lvl2" + log_must ratelimit_bw_read 5 5 "/dev/zvol/$TESTPOOL/$TESTFS/lvl0/lvl1/lvl2" + log_must ratelimit_bw_write 5 5 "/dev/zvol/$TESTPOOL/$TESTFS/lvl0/lvl1/lvl2" + done + done +done + +log_must destroy_dataset "$TESTPOOL/$TESTFS/lvl0/lvl1/lvl2" +log_must destroy_dataset "$TESTPOOL/$TESTFS/lvl0/lvl1" +log_must destroy_dataset "$TESTPOOL/$TESTFS/lvl0" + +log_pass diff --git a/tests/zfs-tests/tests/functional/ratelimit/volume_bw_hierarchical_vertical_write.ksh b/tests/zfs-tests/tests/functional/ratelimit/volume_bw_hierarchical_vertical_write.ksh new file mode 100755 index 000000000000..dbc8f9498fb5 --- /dev/null +++ b/tests/zfs-tests/tests/functional/ratelimit/volume_bw_hierarchical_vertical_write.ksh @@ -0,0 +1,63 @@ +#! /bin/ksh -p +# +# CDDL HEADER START +# +# The contents of this file are subject to the terms of the +# Common Development and Distribution License (the "License"). +# You may not use this file except in compliance with the License. +# +# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE +# or https://opensource.org/licenses/CDDL-1.0. +# See the License for the specific language governing permissions +# and limitations under the License. +# +# When distributing Covered Code, include this CDDL HEADER in each +# file and include the License file at usr/src/OPENSOLARIS.LICENSE. +# If applicable, add the following below this CDDL HEADER, with the +# fields enclosed by brackets "[]" replaced with your own identifying +# information: Portions Copyright [yyyy] [name of copyright owner] +# +# CDDL HEADER END +# + +# +# Copyright (c) 2024 The FreeBSD Foundation +# +# This software was developed by Pawel Dawidek +# under sponsorship from the FreeBSD Foundation. +# + +. $STF_SUITE/include/libtest.shlib +. $STF_SUITE/tests/functional/ratelimit/ratelimit_common.kshlib + +verify_runnable "both" + +log_assert "Verify hierarchical bandwidth write limits configured on multiple levels" + +ratelimit_reset + +log_must create_dataset "$TESTPOOL/$TESTFS/lvl0" +log_must create_dataset "$TESTPOOL/$TESTFS/lvl0/lvl1" +log_must create_volume "$TESTPOOL/$TESTFS/lvl0/lvl1/lvl2" 16M + +for lvl0 in none 1M 3M 5M; do + for lvl1 in none 1M 3M 5M; do + for lvl2 in none 1M 3M 5M; do + # We need at least one level with 1M limit. + if [ $lvl0 != "1M" ] && [ $lvl1 != "1M" ] && [ $lvl2 != "1M" ]; then + continue + fi + + log_must zfs set limit_bw_write=$lvl0 "$TESTPOOL/$TESTFS/lvl0" + log_must zfs set limit_bw_write=$lvl1 "$TESTPOOL/$TESTFS/lvl0/lvl1" + log_must zfs set limit_bw_write=$lvl2 "$TESTPOOL/$TESTFS/lvl0/lvl1/lvl2" + log_must ratelimit_bw_write 5 5 "/dev/zvol/$TESTPOOL/$TESTFS/lvl0/lvl1/lvl2" + done + done +done + +log_must destroy_dataset "$TESTPOOL/$TESTFS/lvl0/lvl1/lvl2" +log_must destroy_dataset "$TESTPOOL/$TESTFS/lvl0/lvl1" +log_must destroy_dataset "$TESTPOOL/$TESTFS/lvl0" + +log_pass diff --git a/tests/zfs-tests/tests/functional/ratelimit/volume_bw_multiple.ksh b/tests/zfs-tests/tests/functional/ratelimit/volume_bw_multiple.ksh new file mode 100755 index 000000000000..f6abb2a0d5c6 --- /dev/null +++ b/tests/zfs-tests/tests/functional/ratelimit/volume_bw_multiple.ksh @@ -0,0 +1,67 @@ +#! /bin/ksh -p +# +# CDDL HEADER START +# +# The contents of this file are subject to the terms of the +# Common Development and Distribution License (the "License"). +# You may not use this file except in compliance with the License. +# +# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE +# or https://opensource.org/licenses/CDDL-1.0. +# See the License for the specific language governing permissions +# and limitations under the License. +# +# When distributing Covered Code, include this CDDL HEADER in each +# file and include the License file at usr/src/OPENSOLARIS.LICENSE. +# If applicable, add the following below this CDDL HEADER, with the +# fields enclosed by brackets "[]" replaced with your own identifying +# information: Portions Copyright [yyyy] [name of copyright owner] +# +# CDDL HEADER END +# + +# +# Copyright (c) 2024 The FreeBSD Foundation +# +# This software was developed by Pawel Dawidek +# under sponsorship from the FreeBSD Foundation. +# + +. $STF_SUITE/include/libtest.shlib +. $STF_SUITE/tests/functional/ratelimit/ratelimit_common.kshlib + +verify_runnable "both" + +log_assert "Verify bandwidth limits for multiple active processes" + +ratelimit_reset + +log_must create_volume "$TESTPOOL/$TESTFS/vol" 500M + +log_must ratelimit_volume_bw_read_multiple limit_bw_read=none 500 1 +log_must ratelimit_volume_bw_read_multiple limit_bw_read=1M 5 15 +log_must ratelimit_volume_bw_read_multiple limit_bw_read=10M 50 15 +log_must ratelimit_volume_bw_read_multiple limit_bw_read=100M 500 15 +log_must ratelimit_volume_bw_read_multiple limit_bw_read=none 500 1 + +log_must ratelimit_volume_bw_read_multiple limit_bw_total=none 500 1 +log_must ratelimit_volume_bw_read_multiple limit_bw_total=1M 5 15 +log_must ratelimit_volume_bw_read_multiple limit_bw_total=10M 50 15 +log_must ratelimit_volume_bw_read_multiple limit_bw_total=100M 500 15 +log_must ratelimit_volume_bw_read_multiple limit_bw_total=none 500 1 + +log_must ratelimit_volume_bw_write_multiple limit_bw_write=none 500 1 +log_must ratelimit_volume_bw_write_multiple limit_bw_write=1M 5 15 +log_must ratelimit_volume_bw_write_multiple limit_bw_write=10M 50 15 +log_must ratelimit_volume_bw_write_multiple limit_bw_write=100M 500 15 +log_must ratelimit_volume_bw_write_multiple limit_bw_write=none 500 1 + +log_must ratelimit_volume_bw_write_multiple limit_bw_total=none 500 1 +log_must ratelimit_volume_bw_write_multiple limit_bw_total=1M 5 15 +log_must ratelimit_volume_bw_write_multiple limit_bw_total=10M 50 15 +log_must ratelimit_volume_bw_write_multiple limit_bw_total=100M 500 15 +log_must ratelimit_volume_bw_write_multiple limit_bw_total=none 500 1 + +log_must destroy_dataset "$TESTPOOL/$TESTFS/vol" + +log_pass diff --git a/tests/zfs-tests/tests/functional/ratelimit/volume_bw_recv.ksh b/tests/zfs-tests/tests/functional/ratelimit/volume_bw_recv.ksh new file mode 100755 index 000000000000..715dc88419ad --- /dev/null +++ b/tests/zfs-tests/tests/functional/ratelimit/volume_bw_recv.ksh @@ -0,0 +1,171 @@ +#! /bin/ksh -p +# +# CDDL HEADER START +# +# The contents of this file are subject to the terms of the +# Common Development and Distribution License (the "License"). +# You may not use this file except in compliance with the License. +# +# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE +# or https://opensource.org/licenses/CDDL-1.0. +# See the License for the specific language governing permissions +# and limitations under the License. +# +# When distributing Covered Code, include this CDDL HEADER in each +# file and include the License file at usr/src/OPENSOLARIS.LICENSE. +# If applicable, add the following below this CDDL HEADER, with the +# fields enclosed by brackets "[]" replaced with your own identifying +# information: Portions Copyright [yyyy] [name of copyright owner] +# +# CDDL HEADER END +# + +# +# Copyright (c) 2024 The FreeBSD Foundation +# +# This software was developed by Pawel Dawidek +# under sponsorship from the FreeBSD Foundation. +# + +. $STF_SUITE/include/libtest.shlib +. $STF_SUITE/tests/functional/ratelimit/ratelimit_common.kshlib + +verify_runnable "both" + +log_assert "Verify bandwidth limits for zfs receive" + +function ratelimit_recv +{ + typeset -r exp=$1 + typeset res + + shift + + sync_pool $TESTPOOL + stopwatch_start + zfs send "$TESTPOOL/$TESTFS/foo@snap" | zfs recv -F $@ "$TESTPOOL/$TESTFS/bar/baz" >/dev/null + stopwatch_check $exp + res=$? + destroy_snapshot "$TESTPOOL/$TESTFS/bar/baz@snap" + destroy_dataset "$TESTPOOL/$TESTFS/bar/baz" + return $res +} + +ratelimit_reset + +log_must create_volume "$TESTPOOL/$TESTFS/foo" 16M +log_must dd if=/dev/urandom of="/dev/zvol/$TESTPOOL/$TESTFS/foo" bs=1M count=16 +log_must create_snapshot "$TESTPOOL/$TESTFS/foo" "snap" +log_must create_dataset "$TESTPOOL/$TESTFS/bar" + +log_must ratelimit_recv 1 + +log_must zfs set limit_bw_write=4M "$TESTPOOL/$TESTFS/bar" +log_must ratelimit_recv 4 + +log_must zfs set limit_bw_write=none "$TESTPOOL/$TESTFS/bar" +log_must create_volume "$TESTPOOL/$TESTFS/bar/baz" 16M +log_must zfs set limit_bw_write=4M "$TESTPOOL/$TESTFS/bar/baz" +log_must ratelimit_recv 4 + +log_must zfs set limit_bw_write=none "$TESTPOOL/$TESTFS/bar" +log_must ratelimit_recv 4 -o limit_bw_write=4M + +log_must zfs set limit_bw_write=4M "$TESTPOOL/$TESTFS/bar" +log_must create_volume "$TESTPOOL/$TESTFS/bar/baz" 16M +log_must zfs set limit_bw_write=8M "$TESTPOOL/$TESTFS/bar/baz" +log_must ratelimit_recv 4 + +log_must zfs set limit_bw_write=4M "$TESTPOOL/$TESTFS/bar" +log_must ratelimit_recv 4 -o limit_bw_write=8M + +log_must zfs set limit_bw_write=8M "$TESTPOOL/$TESTFS/bar" +log_must create_volume "$TESTPOOL/$TESTFS/bar/baz" 16M +log_must zfs set limit_bw_write=4M "$TESTPOOL/$TESTFS/bar/baz" +log_must ratelimit_recv 4 + +log_must zfs set limit_bw_write=8M "$TESTPOOL/$TESTFS/bar" +log_must ratelimit_recv 4 -o limit_bw_write=4M + +log_must zfs set limit_bw_write=none "$TESTPOOL/$TESTFS/bar" + +log_must ratelimit_recv 1 + +log_must zfs set limit_bw_total=4M "$TESTPOOL/$TESTFS/bar" +log_must ratelimit_recv 4 + +log_must zfs set limit_bw_total=none "$TESTPOOL/$TESTFS/bar" +log_must create_volume "$TESTPOOL/$TESTFS/bar/baz" 16M +log_must zfs set limit_bw_total=4M "$TESTPOOL/$TESTFS/bar/baz" +log_must ratelimit_recv 4 + +log_must zfs set limit_bw_total=none "$TESTPOOL/$TESTFS/bar" +log_must ratelimit_recv 4 -o limit_bw_total=4M + +log_must zfs set limit_bw_total=4M "$TESTPOOL/$TESTFS/bar" +log_must create_volume "$TESTPOOL/$TESTFS/bar/baz" 16M +log_must zfs set limit_bw_total=8M "$TESTPOOL/$TESTFS/bar/baz" +log_must ratelimit_recv 4 + +log_must zfs set limit_bw_total=4M "$TESTPOOL/$TESTFS/bar" +log_must ratelimit_recv 4 -o limit_bw_total=8M + +log_must zfs set limit_bw_total=8M "$TESTPOOL/$TESTFS/bar" +log_must create_volume "$TESTPOOL/$TESTFS/bar/baz" 16M +log_must zfs set limit_bw_total=4M "$TESTPOOL/$TESTFS/bar/baz" +log_must ratelimit_recv 4 + +log_must zfs set limit_bw_total=8M "$TESTPOOL/$TESTFS/bar" +log_must ratelimit_recv 4 -o limit_bw_total=4M + +log_must zfs set limit_bw_total=none "$TESTPOOL/$TESTFS/bar" +log_must ratelimit_recv 1 + +log_must zfs set limit_bw_write=8M "$TESTPOOL/$TESTFS/bar" +log_must zfs set limit_bw_total=none "$TESTPOOL/$TESTFS/bar" +log_must create_volume "$TESTPOOL/$TESTFS/bar/baz" 16M +log_must zfs set limit_bw_write=none "$TESTPOOL/$TESTFS/bar/baz" +log_must zfs set limit_bw_total=4M "$TESTPOOL/$TESTFS/bar/baz" +log_must ratelimit_recv 4 + +log_must zfs set limit_bw_write=none "$TESTPOOL/$TESTFS/bar" +log_must zfs set limit_bw_total=8M "$TESTPOOL/$TESTFS/bar" +log_must ratelimit_recv 4 -o limit_bw_write=none -o limit_bw_total=4M + +log_must zfs set limit_bw_write=none "$TESTPOOL/$TESTFS/bar" +log_must zfs set limit_bw_total=8M "$TESTPOOL/$TESTFS/bar" +log_must create_volume "$TESTPOOL/$TESTFS/bar/baz" 16M +log_must zfs set limit_bw_write=4M "$TESTPOOL/$TESTFS/bar/baz" +log_must zfs set limit_bw_total=none "$TESTPOOL/$TESTFS/bar/baz" +log_must ratelimit_recv 4 + +log_must zfs set limit_bw_write=8M "$TESTPOOL/$TESTFS/bar" +log_must zfs set limit_bw_total=none "$TESTPOOL/$TESTFS/bar" +log_must ratelimit_recv 4 -o limit_bw_write=4M -o limit_bw_total=none + +log_must zfs set limit_bw_write=4M "$TESTPOOL/$TESTFS/bar" +log_must zfs set limit_bw_total=none "$TESTPOOL/$TESTFS/bar" +log_must create_volume "$TESTPOOL/$TESTFS/bar/baz" 16M +log_must zfs set limit_bw_write=none "$TESTPOOL/$TESTFS/bar/baz" +log_must zfs set limit_bw_total=8M "$TESTPOOL/$TESTFS/bar/baz" +log_must ratelimit_recv 4 + +log_must zfs set limit_bw_write=none "$TESTPOOL/$TESTFS/bar" +log_must zfs set limit_bw_total=4M "$TESTPOOL/$TESTFS/bar" +log_must ratelimit_recv 4 -o limit_bw_write=none -o limit_bw_total=8M + +log_must zfs set limit_bw_write=none "$TESTPOOL/$TESTFS/bar" +log_must zfs set limit_bw_total=4M "$TESTPOOL/$TESTFS/bar" +log_must create_volume "$TESTPOOL/$TESTFS/bar/baz" 16M +log_must zfs set limit_bw_write=8M "$TESTPOOL/$TESTFS/bar/baz" +log_must zfs set limit_bw_total=none "$TESTPOOL/$TESTFS/bar/baz" +log_must ratelimit_recv 4 + +log_must zfs set limit_bw_write=4M "$TESTPOOL/$TESTFS/bar" +log_must zfs set limit_bw_total=none "$TESTPOOL/$TESTFS/bar" +log_must ratelimit_recv 4 -o limit_bw_write=8M -o limit_bw_total=none + +destroy_snapshot "$TESTPOOL/$TESTFS/foo@snap" +log_must destroy_dataset "$TESTPOOL/$TESTFS/foo" + +log_pass diff --git a/tests/zfs-tests/tests/functional/ratelimit/volume_bw_send.ksh b/tests/zfs-tests/tests/functional/ratelimit/volume_bw_send.ksh new file mode 100755 index 000000000000..9b2a31600a87 --- /dev/null +++ b/tests/zfs-tests/tests/functional/ratelimit/volume_bw_send.ksh @@ -0,0 +1,111 @@ +#! /bin/ksh -p +# +# CDDL HEADER START +# +# The contents of this file are subject to the terms of the +# Common Development and Distribution License (the "License"). +# You may not use this file except in compliance with the License. +# +# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE +# or https://opensource.org/licenses/CDDL-1.0. +# See the License for the specific language governing permissions +# and limitations under the License. +# +# When distributing Covered Code, include this CDDL HEADER in each +# file and include the License file at usr/src/OPENSOLARIS.LICENSE. +# If applicable, add the following below this CDDL HEADER, with the +# fields enclosed by brackets "[]" replaced with your own identifying +# information: Portions Copyright [yyyy] [name of copyright owner] +# +# CDDL HEADER END +# + +# +# Copyright (c) 2024 The FreeBSD Foundation +# +# This software was developed by Pawel Dawidek +# under sponsorship from the FreeBSD Foundation. +# + +. $STF_SUITE/include/libtest.shlib +. $STF_SUITE/tests/functional/ratelimit/ratelimit_common.kshlib + +verify_runnable "both" + +log_assert "Verify bandwidth limits for zfs send" + +function ratelimit_send +{ + typeset -r exp=$1 + + stopwatch_start + zfs send "$TESTPOOL/$TESTFS/foo@snap" >/dev/null + stopwatch_check $exp +} + +ratelimit_reset + +log_must create_volume "$TESTPOOL/$TESTFS/foo" 16M +log_must dd if=/dev/urandom of="/dev/zvol/$TESTPOOL/$TESTFS/foo" bs=1M count=16 +log_must create_snapshot "$TESTPOOL/$TESTFS/foo" "snap" + +log_must ratelimit_send 1 + +log_must zfs set limit_bw_read=2M "$TESTPOOL/$TESTFS/foo" +log_must ratelimit_send 8 + +log_must zfs set limit_bw_read=4M "$TESTPOOL/$TESTFS" +log_must zfs set limit_bw_read=8M "$TESTPOOL/$TESTFS/foo" +log_must ratelimit_send 4 + +log_must zfs set limit_bw_read=8M "$TESTPOOL/$TESTFS" +log_must zfs set limit_bw_read=4M "$TESTPOOL/$TESTFS/foo" +log_must ratelimit_send 4 + +log_must zfs set limit_bw_read=none "$TESTPOOL/$TESTFS" +log_must zfs set limit_bw_read=none "$TESTPOOL/$TESTFS/foo" +log_must ratelimit_send 1 + +log_must zfs set limit_bw_total=2M "$TESTPOOL/$TESTFS/foo" +log_must ratelimit_send 8 + +log_must zfs set limit_bw_total=4M "$TESTPOOL/$TESTFS" +log_must zfs set limit_bw_total=8M "$TESTPOOL/$TESTFS/foo" +log_must ratelimit_send 4 + +log_must zfs set limit_bw_total=8M "$TESTPOOL/$TESTFS" +log_must zfs set limit_bw_total=4M "$TESTPOOL/$TESTFS/foo" +log_must ratelimit_send 4 + +log_must zfs set limit_bw_total=none "$TESTPOOL/$TESTFS" +log_must zfs set limit_bw_total=none "$TESTPOOL/$TESTFS/foo" +log_must ratelimit_send 1 + +log_must zfs set limit_bw_read=4M "$TESTPOOL/$TESTFS" +log_must zfs set limit_bw_total=none "$TESTPOOL/$TESTFS" +log_must zfs set limit_bw_read=none "$TESTPOOL/$TESTFS/foo" +log_must zfs set limit_bw_total=8M "$TESTPOOL/$TESTFS/foo" +log_must ratelimit_send 4 + +log_must zfs set limit_bw_read=none "$TESTPOOL/$TESTFS" +log_must zfs set limit_bw_total=4M "$TESTPOOL/$TESTFS" +log_must zfs set limit_bw_read=8M "$TESTPOOL/$TESTFS/foo" +log_must zfs set limit_bw_total=none "$TESTPOOL/$TESTFS/foo" +log_must ratelimit_send 4 + +log_must zfs set limit_bw_read=8M "$TESTPOOL/$TESTFS" +log_must zfs set limit_bw_total=none "$TESTPOOL/$TESTFS" +log_must zfs set limit_bw_read=none "$TESTPOOL/$TESTFS/foo" +log_must zfs set limit_bw_total=4M "$TESTPOOL/$TESTFS/foo" +log_must ratelimit_send 4 + +log_must zfs set limit_bw_read=none "$TESTPOOL/$TESTFS" +log_must zfs set limit_bw_total=8M "$TESTPOOL/$TESTFS" +log_must zfs set limit_bw_read=4M "$TESTPOOL/$TESTFS/foo" +log_must zfs set limit_bw_total=none "$TESTPOOL/$TESTFS/foo" +log_must ratelimit_send 4 + +destroy_snapshot "$TESTPOOL/$TESTFS/foo@snap" +log_must destroy_dataset "$TESTPOOL/$TESTFS/foo" + +log_pass diff --git a/tests/zfs-tests/tests/functional/ratelimit/volume_bw_single.ksh b/tests/zfs-tests/tests/functional/ratelimit/volume_bw_single.ksh new file mode 100755 index 000000000000..abeb723c74f5 --- /dev/null +++ b/tests/zfs-tests/tests/functional/ratelimit/volume_bw_single.ksh @@ -0,0 +1,85 @@ +#! /bin/ksh -p +# +# CDDL HEADER START +# +# The contents of this file are subject to the terms of the +# Common Development and Distribution License (the "License"). +# You may not use this file except in compliance with the License. +# +# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE +# or https://opensource.org/licenses/CDDL-1.0. +# See the License for the specific language governing permissions +# and limitations under the License. +# +# When distributing Covered Code, include this CDDL HEADER in each +# file and include the License file at usr/src/OPENSOLARIS.LICENSE. +# If applicable, add the following below this CDDL HEADER, with the +# fields enclosed by brackets "[]" replaced with your own identifying +# information: Portions Copyright [yyyy] [name of copyright owner] +# +# CDDL HEADER END +# + +# +# Copyright (c) 2024 The FreeBSD Foundation +# +# This software was developed by Pawel Dawidek +# under sponsorship from the FreeBSD Foundation. +# + +. $STF_SUITE/include/libtest.shlib +. $STF_SUITE/tests/functional/ratelimit/ratelimit_common.kshlib + +verify_runnable "both" + +log_assert "Verify various bandwidth limits for a single active process" + +ratelimit_reset + +log_must create_volume "$TESTPOOL/$TESTFS/vol" 500M + +# Bandwidth read limits. +log_must ratelimit_volume_bw_read_single limit_bw_read=none 500 1 +log_must ratelimit_volume_bw_read_single limit_bw_read=1M 5 5 +log_must ratelimit_volume_bw_read_single limit_bw_read=10M 50 5 +log_must ratelimit_volume_bw_read_single limit_bw_read=100M 500 5 +log_must ratelimit_volume_bw_read_single limit_bw_read=none 500 1 + +# Bandwidth total limits limit reading. +log_must ratelimit_volume_bw_read_single limit_bw_total=none 500 1 +log_must ratelimit_volume_bw_read_single limit_bw_total=1M 5 5 +log_must ratelimit_volume_bw_read_single limit_bw_total=10M 50 5 +log_must ratelimit_volume_bw_read_single limit_bw_total=100M 500 5 +log_must ratelimit_volume_bw_read_single limit_bw_total=none 500 1 + +# Bandwidth write limits don't affect reading. +log_must ratelimit_volume_bw_read_single limit_bw_write=none 500 1 +log_must ratelimit_volume_bw_read_single limit_bw_write=1M 5 1 +log_must ratelimit_volume_bw_read_single limit_bw_write=10M 50 1 +log_must ratelimit_volume_bw_read_single limit_bw_write=100M 500 1 +log_must ratelimit_volume_bw_read_single limit_bw_write=none 500 1 + +# Bandwidth write limits. +log_must ratelimit_volume_bw_write_single limit_bw_write=none 500 1 +log_must ratelimit_volume_bw_write_single limit_bw_write=1M 5 5 +log_must ratelimit_volume_bw_write_single limit_bw_write=10M 50 5 +log_must ratelimit_volume_bw_write_single limit_bw_write=100M 500 5 +log_must ratelimit_volume_bw_write_single limit_bw_write=none 500 1 + +# Bandwidth total limits limit writing. +log_must ratelimit_volume_bw_write_single limit_bw_total=none 500 1 +log_must ratelimit_volume_bw_write_single limit_bw_total=1M 5 5 +log_must ratelimit_volume_bw_write_single limit_bw_total=10M 50 5 +log_must ratelimit_volume_bw_write_single limit_bw_total=100M 500 5 +log_must ratelimit_volume_bw_write_single limit_bw_total=none 500 1 + +# Bandwidth read limits don't affect writing. +log_must ratelimit_volume_bw_write_single limit_bw_read=none 500 1 +log_must ratelimit_volume_bw_write_single limit_bw_read=1M 5 1 +log_must ratelimit_volume_bw_write_single limit_bw_read=10M 50 1 +log_must ratelimit_volume_bw_write_single limit_bw_read=100M 500 1 +log_must ratelimit_volume_bw_write_single limit_bw_read=none 500 1 + +log_must destroy_dataset "$TESTPOOL/$TESTFS/vol" + +log_pass