From 61f0f95176b38c26c418cd657277ff8cc5e69b66 Mon Sep 17 00:00:00 2001 From: Pawel Jakub Dawidek Date: Sun, 8 Jan 2023 11:31:22 -0800 Subject: [PATCH] Hierarchical bandwidth and operations rate limits. Introduce six new properties: limit_{bw,op}_{read,write,total}. The limit_bw_* properties limit 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. 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. 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). Read bandwidth consumption is based on: - read-like syscalls, eg., aio_read(2), pread(2), preadv(2), read(2), readv(2), sendfile(2) - syscalls like getdents(2) and getdirentries(2) - reading via mmaped files - zfs send Write bandwidth consumption is based on: - write-like syscalls, eg., aio_write(2), pwrite(2), pwritev(2), write(2), writev(2) - writing via mmaped files - zfs receive The limit_op_* properties limit the read, write, or both metadata operations, respectively, that dataset and its descendants can generate. Read operations consumption is based on: - read-like syscalls where the number of operations is equal to the number of blocks being read (never less than 1) - reading via mmaped files, where the number of operations is equal to the number of pages being read (never less than 1) - syscalls accessing metadata: readlink(2), stat(2) Write operations consumption is based on: - write-like syscalls where the number of operations is equal to the number of blocks being written (never less than 1) - writing via mmaped files, where the number of operations is equal to the number of pages being written (never less than 1) - syscalls modifing a directory's content: bind(2) (UNIX-domain sockets), link(2), mkdir(2), mkfifo(2), mknod(2), open(2) (file creation), rename(2), rmdir(2), symlink(2), unlink(2) - syscalls modifing metadata: chflags(2), chmod(2), chown(2), utimes(2) - updating the access time of a file when reading it Just like limit_bw_* limits, the limit_op_* limits are also hierarchical and applied at the VFS level. Signed-off-by: Pawel Jakub Dawidek --- cmd/zfs/zfs_main.c | 18 +- include/os/freebsd/zfs/sys/zfs_znode_impl.h | 9 +- include/sys/dsl_dir.h | 6 + include/sys/fs/zfs.h | 6 + include/sys/spa_impl.h | 2 + include/sys/vfs_ratelimit.h | 67 ++ lib/libzfs/libzfs.abi | 8 +- lib/libzfs/libzfs_dataset.c | 33 +- lib/libzpool/Makefile.am | 1 + man/man7/zfsprops.7 | 111 +++ module/Kbuild.in | 1 + module/Makefile.bsd | 1 + module/os/freebsd/zfs/zfs_vnops_os.c | 44 +- module/os/freebsd/zfs/zvol_os.c | 7 + module/os/linux/zfs/zfs_vnops_os.c | 60 +- module/os/linux/zfs/zvol_os.c | 13 +- module/zcommon/zfs_prop.c | 20 +- module/zfs/dmu_recv.c | 4 + module/zfs/dmu_send.c | 3 + module/zfs/dsl_dir.c | 227 +++++- module/zfs/spa_misc.c | 4 + module/zfs/vfs_ratelimit.c | 664 ++++++++++++++++++ module/zfs/zfs_ioctl.c | 16 + module/zfs/zfs_vnops.c | 9 + tests/Makefile.am | 1 + tests/runfiles/bclone.run | 4 +- tests/runfiles/common.run | 4 + tests/runfiles/ratelimit.run | 51 ++ tests/zfs-tests/cmd/.gitignore | 1 + tests/zfs-tests/cmd/Makefile.am | 1 + tests/zfs-tests/cmd/fsop.c | 243 +++++++ tests/zfs-tests/include/commands.cfg | 1 + tests/zfs-tests/include/libtest.shlib | 32 + tests/zfs-tests/tests/Makefile.am | 25 + .../tests/functional/ratelimit/cleanup.ksh | 34 + .../ratelimit/filesystem_bw_combined.ksh | 104 +++ .../filesystem_bw_hierarchical_horizontal.ksh | 214 ++++++ ...lesystem_bw_hierarchical_vertical_read.ksh | 65 ++ ...esystem_bw_hierarchical_vertical_total.ksh | 66 ++ ...esystem_bw_hierarchical_vertical_write.ksh | 65 ++ .../ratelimit/filesystem_bw_multiple.ksh | 67 ++ .../ratelimit/filesystem_bw_recv.ksh | 171 +++++ .../ratelimit/filesystem_bw_send.ksh | 111 +++ .../ratelimit/filesystem_bw_single.ksh | 85 +++ .../ratelimit/filesystem_op_multiple.ksh | 78 ++ .../ratelimit/filesystem_op_single.ksh | 152 ++++ .../functional/ratelimit/inheritance.ksh | 307 ++++++++ .../ratelimit/ratelimit_common.kshlib | 282 ++++++++ .../functional/ratelimit/ratelimit_random.ksh | 42 ++ .../tests/functional/ratelimit/setup.ksh | 48 ++ .../ratelimit/volume_bw_combined.ksh | 105 +++ .../volume_bw_hierarchical_horizontal.ksh | 211 ++++++ .../volume_bw_hierarchical_vertical_read.ksh | 63 ++ .../volume_bw_hierarchical_vertical_total.ksh | 64 ++ .../volume_bw_hierarchical_vertical_write.ksh | 63 ++ .../ratelimit/volume_bw_multiple.ksh | 67 ++ .../functional/ratelimit/volume_bw_recv.ksh | 171 +++++ .../functional/ratelimit/volume_bw_send.ksh | 111 +++ .../functional/ratelimit/volume_bw_single.ksh | 85 +++ 59 files changed, 4504 insertions(+), 24 deletions(-) create mode 100644 include/sys/vfs_ratelimit.h create mode 100644 module/zfs/vfs_ratelimit.c create mode 100644 tests/runfiles/ratelimit.run create mode 100644 tests/zfs-tests/cmd/fsop.c create mode 100755 tests/zfs-tests/tests/functional/ratelimit/cleanup.ksh create mode 100755 tests/zfs-tests/tests/functional/ratelimit/filesystem_bw_combined.ksh create mode 100755 tests/zfs-tests/tests/functional/ratelimit/filesystem_bw_hierarchical_horizontal.ksh create mode 100755 tests/zfs-tests/tests/functional/ratelimit/filesystem_bw_hierarchical_vertical_read.ksh create mode 100755 tests/zfs-tests/tests/functional/ratelimit/filesystem_bw_hierarchical_vertical_total.ksh create mode 100755 tests/zfs-tests/tests/functional/ratelimit/filesystem_bw_hierarchical_vertical_write.ksh create mode 100755 tests/zfs-tests/tests/functional/ratelimit/filesystem_bw_multiple.ksh create mode 100755 tests/zfs-tests/tests/functional/ratelimit/filesystem_bw_recv.ksh create mode 100755 tests/zfs-tests/tests/functional/ratelimit/filesystem_bw_send.ksh create mode 100755 tests/zfs-tests/tests/functional/ratelimit/filesystem_bw_single.ksh create mode 100755 tests/zfs-tests/tests/functional/ratelimit/filesystem_op_multiple.ksh create mode 100755 tests/zfs-tests/tests/functional/ratelimit/filesystem_op_single.ksh create mode 100755 tests/zfs-tests/tests/functional/ratelimit/inheritance.ksh create mode 100644 tests/zfs-tests/tests/functional/ratelimit/ratelimit_common.kshlib create mode 100755 tests/zfs-tests/tests/functional/ratelimit/ratelimit_random.ksh create mode 100755 tests/zfs-tests/tests/functional/ratelimit/setup.ksh create mode 100755 tests/zfs-tests/tests/functional/ratelimit/volume_bw_combined.ksh create mode 100755 tests/zfs-tests/tests/functional/ratelimit/volume_bw_hierarchical_horizontal.ksh create mode 100755 tests/zfs-tests/tests/functional/ratelimit/volume_bw_hierarchical_vertical_read.ksh create mode 100755 tests/zfs-tests/tests/functional/ratelimit/volume_bw_hierarchical_vertical_total.ksh create mode 100755 tests/zfs-tests/tests/functional/ratelimit/volume_bw_hierarchical_vertical_write.ksh create mode 100755 tests/zfs-tests/tests/functional/ratelimit/volume_bw_multiple.ksh create mode 100755 tests/zfs-tests/tests/functional/ratelimit/volume_bw_recv.ksh create mode 100755 tests/zfs-tests/tests/functional/ratelimit/volume_bw_send.ksh create mode 100755 tests/zfs-tests/tests/functional/ratelimit/volume_bw_single.ksh 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