From ba369e249d560c8ca192e5969308323e6e0e06d6 Mon Sep 17 00:00:00 2001 From: Pawel Jakub Dawidek Date: Sun, 8 Jan 2023 11:31:22 -0800 Subject: [PATCH 1/2] 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/spl/sys/sdt.h | 2 +- include/os/freebsd/spl/sys/systm.h | 1 + include/os/freebsd/zfs/sys/zfs_znode_impl.h | 9 +- include/os/linux/spl/sys/timer.h | 1 + include/sys/dsl_dir.h | 6 + include/sys/fs/zfs.h | 6 + include/sys/spa_impl.h | 2 + include/sys/vfs_ratelimit.h | 72 ++ 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 | 121 ++- module/os/freebsd/zfs/zvol_os.c | 38 +- module/os/linux/zfs/zfs_vnops_os.c | 169 ++++- module/os/linux/zfs/zvol_os.c | 29 +- module/zcommon/zfs_prop.c | 20 +- module/zfs/dmu_recv.c | 8 + module/zfs/dmu_send.c | 15 +- module/zfs/dsl_dir.c | 231 +++++- module/zfs/spa_misc.c | 4 + module/zfs/vfs_ratelimit.c | 688 ++++++++++++++++++ module/zfs/zfs_ioctl.c | 16 + module/zfs/zfs_vnops.c | 29 +- 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 | 140 ++++ .../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 +++ 62 files changed, 4750 insertions(+), 72 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/spl/sys/sdt.h b/include/os/freebsd/spl/sys/sdt.h index 2daa6de1af08..6f45e036bcd4 100644 --- a/include/os/freebsd/spl/sys/sdt.h +++ b/include/os/freebsd/spl/sys/sdt.h @@ -37,7 +37,7 @@ SDT_PROBE_DECLARE(sdt, , , set__error); #define SET_ERROR(err) \ ((sdt_sdt___set__error->id ? \ (*sdt_probe_func)(sdt_sdt___set__error->id, \ - (uintptr_t)err, 0, 0, 0, 0) : 0), err) + (uintptr_t)err, 0, 0, 0, 0, 0) : 0), err) #else #define SET_ERROR(err) (err) #endif diff --git a/include/os/freebsd/spl/sys/systm.h b/include/os/freebsd/spl/sys/systm.h index 98ee9557527c..f17d820e7a99 100644 --- a/include/os/freebsd/spl/sys/systm.h +++ b/include/os/freebsd/spl/sys/systm.h @@ -39,5 +39,6 @@ #define PAGEMASK (~PAGEOFFSET) #define delay(x) pause("soldelay", (x)) +#define delay_sig(x) (pause_sig("soldelay", (x)) != EAGAIN) #endif /* _OPENSOLARIS_SYS_SYSTM_H_ */ 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/os/linux/spl/sys/timer.h b/include/os/linux/spl/sys/timer.h index 02c3c7893477..abb9ef04fe59 100644 --- a/include/os/linux/spl/sys/timer.h +++ b/include/os/linux/spl/sys/timer.h @@ -51,6 +51,7 @@ #define ddi_time_after_eq64(a, b) ddi_time_before_eq64(b, a) #define delay(ticks) schedule_timeout_uninterruptible(ticks) +#define delay_sig(ticks) (schedule_timeout_interruptible(ticks) > 0) #define SEC_TO_TICK(sec) ((sec) * HZ) #define MSEC_TO_TICK(ms) msecs_to_jiffies(ms) 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..8b92476c833d --- /dev/null +++ b/include/sys/vfs_ratelimit.h @@ -0,0 +1,72 @@ +/* + * 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); + +int vfs_ratelimit_data_read(objset_t *os, size_t blocksize, size_t bytes); +int vfs_ratelimit_data_write(objset_t *os, size_t blocksize, size_t bytes); +int vfs_ratelimit_data_copy(objset_t *srcos, objset_t *dstos, size_t blocksize, + size_t bytes); +int vfs_ratelimit_metadata_read(objset_t *os); +int vfs_ratelimit_metadata_write(objset_t *os); + +void vfs_ratelimit_data_read_spin(objset_t *os, size_t blocksize, size_t bytes); +void vfs_ratelimit_data_write_spin(objset_t *os, size_t blocksize, size_t bytes); + +#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..185cbff3d687 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 file systems, volumes and their snapshots. +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 9e44364b7584..d83727d17cc8 100644 --- a/module/Kbuild.in +++ b/module/Kbuild.in @@ -397,6 +397,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..0491cd8ef38b 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 @@ -1149,6 +1150,12 @@ zfs_create(znode_t *dzp, const char *name, vattr_t *vap, int excl, int mode, goto out; } + error = vfs_ratelimit_metadata_write(os); + if (error != 0) { + zfs_acl_ids_free(&acl_ids); + goto out; + } + getnewvnode_reserve_(); tx = dmu_tx_create(os); @@ -1282,6 +1289,11 @@ zfs_remove_(vnode_t *dvp, vnode_t *vp, const char *name, cred_t *cr) ASSERT0(error); } + error = vfs_ratelimit_metadata_write(zfsvfs->z_os); + if (error != 0) { + goto out; + } + /* * 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 @@ -1310,8 +1322,7 @@ zfs_remove_(vnode_t *dvp, vnode_t *vp, const char *name, cred_t *cr) error = dmu_tx_assign(tx, TXG_WAIT); if (error) { dmu_tx_abort(tx); - zfs_exit(zfsvfs, FTAG); - return (error); + goto out; } /* @@ -1509,6 +1520,13 @@ zfs_mkdir(znode_t *dzp, const char *dirname, vattr_t *vap, znode_t **zpp, return (SET_ERROR(EDQUOT)); } + error = vfs_ratelimit_metadata_write(zfsvfs->z_os); + if (error != 0) { + zfs_acl_ids_free(&acl_ids); + zfs_exit(zfsvfs, FTAG); + return (error); + } + /* * Add a new entry to the directory. */ @@ -1620,7 +1638,6 @@ zfs_rmdir_(vnode_t *dvp, vnode_t *vp, const char *name, cred_t *cr) } zilog = zfsvfs->z_log; - if ((error = zfs_zaccess_delete(dzp, zp, cr, NULL))) { goto out; } @@ -1630,6 +1647,11 @@ zfs_rmdir_(vnode_t *dvp, vnode_t *vp, const char *name, cred_t *cr) goto out; } + error = vfs_ratelimit_metadata_write(zfsvfs->z_os); + if (error != 0) { + goto out; + } + vnevent_rmdir(vp, dvp, name, ct); tx = dmu_tx_create(zfsvfs->z_os); @@ -1642,12 +1664,10 @@ zfs_rmdir_(vnode_t *dvp, vnode_t *vp, const char *name, cred_t *cr) error = dmu_tx_assign(tx, TXG_WAIT); if (error) { dmu_tx_abort(tx); - zfs_exit(zfsvfs, FTAG); - return (error); + goto out; } error = zfs_link_destroy(dzp, name, zp, tx, ZEXISTS, NULL); - if (error == 0) { uint64_t txtype = TX_RMDIR; zfs_log_remove(zilog, tx, txtype, dzp, name, @@ -1658,10 +1678,10 @@ zfs_rmdir_(vnode_t *dvp, vnode_t *vp, const char *name, cred_t *cr) if (zfsvfs->z_use_namecache) cache_vop_rmdir(dvp, vp); -out: + if (zfsvfs->z_os->os_sync == ZFS_SYNC_ALWAYS) zil_commit(zilog, 0); - +out: zfs_exit(zfsvfs, FTAG); return (error); } @@ -1768,6 +1788,21 @@ zfs_readdir(vnode_t *vp, zfs_uio_t *uio, cred_t *cr, int *eofp, offset = zfs_uio_offset(uio); prefetch = zp->z_zn_prefetch; + /* + * Calling vfs_ratelimit_data_read() for each directory entry would be + * way too expensive. We don't want to do that so we do the following + * instead: + * We charge here only for a single block. If there is a lot of traffic + * we are going to wait before any reading is issued. Once we read all + * directory entries we will charge the process for the rest, as this is + * when we will know how much data exactly was read. + */ + error = vfs_ratelimit_data_read(os, zp->z_blksz, zp->z_blksz); + if (error != 0) { + zfs_exit(zfsvfs, FTAG); + return (error); + } + /* * Initialize the iterator cursor. */ @@ -1788,12 +1823,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 +1959,18 @@ zfs_readdir(vnode_t *vp, zfs_uio_t *uio, cred_t *cr, int *eofp, if (ncookies != NULL) *ncookies -= ncooks; + /* + * Charge the process for the rest, if more than a single block was + * read. + */ + if (error == 0 && outcount > zp->z_blksz) { + error = vfs_ratelimit_data_read(os, zp->z_blksz, + outcount - zp->z_blksz); + if (error != 0) { + goto update; + } + } + if (zfs_uio_segflg(uio) == UIO_SYSSPACE && zfs_uio_iovcnt(uio) == 1) { iovp->iov_base += outcount; iovp->iov_len -= outcount; @@ -2017,6 +2063,12 @@ zfs_getattr(vnode_t *vp, vattr_t *vap, int flags, cred_t *cr) } } + error = vfs_ratelimit_metadata_read(zfsvfs->z_os); + if (error != 0) { + zfs_exit(zfsvfs, FTAG); + return (error); + } + /* * Return all attributes. It's cheaper to provide the answer * than to determine whether we were asked the question. @@ -2612,6 +2664,12 @@ zfs_setattr(znode_t *zp, vattr_t *vap, int flags, cred_t *cr, zidmap_t *mnt_ns) goto out2; } } + + err = vfs_ratelimit_metadata_write(os); + if (err != 0) { + goto out2; + } + tx = dmu_tx_create(os); if (mask & AT_MODE) { @@ -3348,6 +3406,11 @@ zfs_do_rename_impl(vnode_t *sdvp, vnode_t **svpp, struct componentname *scnp, } } + error = vfs_ratelimit_metadata_write(zfsvfs->z_os); + if (error != 0) { + goto out; + } + vn_seqc_write_begin(*svpp); vn_seqc_write_begin(sdvp); if (*tvpp != NULL) @@ -3557,13 +3620,19 @@ zfs_symlink(znode_t *dzp, const char *name, vattr_t *vap, return (error); } - if (zfs_acl_ids_overquota(zfsvfs, &acl_ids, - 0 /* projid */)) { + if (zfs_acl_ids_overquota(zfsvfs, &acl_ids, 0 /* projid */)) { zfs_acl_ids_free(&acl_ids); zfs_exit(zfsvfs, FTAG); return (SET_ERROR(EDQUOT)); } + error = vfs_ratelimit_metadata_write(zfsvfs->z_os); + if (error != 0) { + zfs_acl_ids_free(&acl_ids); + zfs_exit(zfsvfs, FTAG); + return (error); + } + getnewvnode_reserve_(); tx = dmu_tx_create(zfsvfs->z_os); fuid_dirtied = zfsvfs->z_fuid_dirty; @@ -3661,6 +3730,12 @@ 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); + error = vfs_ratelimit_metadata_read(zfsvfs->z_os); + if (error != 0) { + zfs_exit(zfsvfs, FTAG); + return (error); + } + if (zp->z_is_sa) error = sa_lookup_uio(zp->z_sa_hdl, SA_ZPL_SYMLINK(zfsvfs), uio); @@ -3789,6 +3864,12 @@ zfs_link(znode_t *tdzp, znode_t *szp, const char *name, cred_t *cr, return (error); } + error = vfs_ratelimit_metadata_write(zfsvfs->z_os); + if (error != 0) { + zfs_exit(zfsvfs, FTAG); + return (error); + } + 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); @@ -3804,8 +3885,7 @@ zfs_link(znode_t *tdzp, znode_t *szp, const char *name, cred_t *cr, error = zfs_link_create(tdzp, name, szp, tx, 0); if (error == 0) { - uint64_t txtype = TX_LINK; - zfs_log_link(zilog, tx, txtype, tdzp, szp, name); + zfs_log_link(zilog, tx, TX_LINK, tdzp, szp, name); } dmu_tx_commit(tx); @@ -4118,14 +4198,19 @@ zfs_getpages(struct vnode *vp, vm_page_t *ma, int count, int *rbehind, pgsin_a = MIN(*rahead, pgsin_a); } + error = 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 * ZFS will panic if we request DMU to read beyond the end of the last * allocated block. */ - error = dmu_read_pages(zfsvfs->z_os, zp->z_id, ma, count, &pgsin_b, - &pgsin_a, MIN(end, obj_size) - (end - PAGE_SIZE)); + if (error == 0) { + error = dmu_read_pages(zfsvfs->z_os, zp->z_id, ma, count, + &pgsin_b, &pgsin_a, MIN(end, obj_size) - (end - PAGE_SIZE)); + } if (lr != NULL) zfs_rangelock_exit(lr); @@ -4254,6 +4339,10 @@ zfs_putpages(struct vnode *vp, vm_page_t *ma, size_t len, int flags, goto out; } + if (vfs_ratelimit_data_write(zfsvfs->z_os, zp->z_blksz, len) != 0) { + goto out; + } + 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..3df884817282 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,10 @@ 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? */ + error = vfs_ratelimit_metadata_write(zv->zv_objset); + if (error != 0) + goto unlock; dmu_tx_t *tx = dmu_tx_create(zv->zv_objset); error = dmu_tx_assign(tx, TXG_WAIT); if (error != 0) { @@ -744,25 +749,29 @@ zvol_geom_bio_strategy(struct bio *bp) while (resid != 0 && off < volsize) { size_t size = MIN(resid, zvol_maxphys); if (doread) { + error = vfs_ratelimit_data_read(zv->zv_objset, + zv->zv_volblocksize, size); + if (error != 0) + break; error = dmu_read(os, ZVOL_OBJ, off, size, addr, DMU_READ_PREFETCH); + if (error != 0) + break; } else { + error = vfs_ratelimit_data_write(zv->zv_objset, + zv->zv_volblocksize, size); + if (error != 0) + break; 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); - if (error) { + if (error != 0) { dmu_tx_abort(tx); - } else { - dmu_write(os, ZVOL_OBJ, off, size, addr, tx); - zvol_log_write(zv, tx, off, size, commit); - dmu_tx_commit(tx); + break; } - } - if (error) { - /* Convert checksum errors into IO errors. */ - if (error == ECKSUM) - error = SET_ERROR(EIO); - break; + dmu_write(os, ZVOL_OBJ, off, size, addr, tx); + zvol_log_write(zv, tx, off, size, commit); + dmu_tx_commit(tx); } off += size; addr += size; @@ -772,7 +781,12 @@ zvol_geom_bio_strategy(struct bio *bp) zfs_rangelock_exit(lr); bp->bio_completed = bp->bio_length - resid; - if (bp->bio_completed < bp->bio_length && off > volsize) + if (error == EINTR && bp->bio_completed > 0) + error = 0; + /* Convert checksum errors into IO errors. */ + else if (error == ECKSUM) + error = SET_ERROR(EIO); + if (error == 0 && bp->bio_completed < bp->bio_length && off > volsize) error = SET_ERROR(EINVAL); switch (bp->bio_cmd) { diff --git a/module/os/linux/zfs/zfs_vnops_os.c b/module/os/linux/zfs/zfs_vnops_os.c index 1cecad9f7755..544126807cd9 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,6 +238,7 @@ 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); @@ -281,17 +283,17 @@ 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; 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 +316,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 +326,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 +679,12 @@ zfs_create(znode_t *dzp, char *name, vattr_t *vap, int excl, goto out; } + error = vfs_ratelimit_metadata_write(os); + if (error != 0) { + zfs_acl_ids_free(&acl_ids); + goto out; + } + tx = dmu_tx_create(os); dmu_tx_hold_sa_create(tx, acl_ids.z_aclp->z_acl_bytes + @@ -871,6 +879,12 @@ zfs_tmpfile(struct inode *dip, vattr_t *vap, int excl, goto out; } + error = vfs_ratelimit_metadata_write(os); + if (error != 0) { + zfs_acl_ids_free(&acl_ids); + goto out; + } + tx = dmu_tx_create(os); dmu_tx_hold_sa_create(tx, acl_ids.z_aclp->z_acl_bytes + @@ -1002,6 +1016,11 @@ zfs_remove(znode_t *dzp, char *name, cred_t *cr, int flags) goto out; } + error = vfs_ratelimit_metadata_write(zfsvfs->z_os); + if (error != 0) { + goto out; + } + mutex_enter(&zp->z_lock); may_delete_now = atomic_read(&ZTOI(zp)->i_count) == 1 && !zn_has_cached_data(zp, 0, LLONG_MAX); @@ -1278,6 +1297,14 @@ zfs_mkdir(znode_t *dzp, char *dirname, vattr_t *vap, znode_t **zpp, return (SET_ERROR(EDQUOT)); } + error = vfs_ratelimit_metadata_write(zfsvfs->z_os); + if (error != 0) { + zfs_acl_ids_free(&acl_ids); + zfs_dirent_unlock(dl); + zfs_exit(zfsvfs, FTAG); + return (error); + } + /* * Add a new entry to the directory. */ @@ -1420,6 +1447,11 @@ zfs_rmdir(znode_t *dzp, char *name, znode_t *cwd, cred_t *cr, goto out; } + error = vfs_ratelimit_metadata_write(zfsvfs->z_os); + if (error != 0) { + goto out; + } + /* * Grab a lock on the directory to make sure that no one is * trying to add (or lookup) entries while we are removing it. @@ -1519,6 +1551,7 @@ zfs_readdir(struct inode *ip, zpl_dir_context_t *ctx, cred_t *cr) int done = 0; uint64_t parent; uint64_t offset; /* must be unsigned; checks for < 1 */ + size_t nbytes; if ((error = zfs_enter_verify_zp(zfsvfs, zp, FTAG)) != 0) return (error); @@ -1537,6 +1570,21 @@ zfs_readdir(struct inode *ip, zpl_dir_context_t *ctx, cred_t *cr) os = zfsvfs->z_os; offset = ctx->pos; prefetch = zp->z_zn_prefetch; + nbytes = 0; + + /* + * Calling vfs_ratelimit_data_read() for each directory entry would be + * way too expensive. We don't want to do that so we do the following + * instead: + * We charge here only for a single block. If there is a lot of traffic + * we are going to wait before any reading is issued. Once we read all + * directory entries we will charge the process for the rest, as this is + * when we will know how much data exactly was read. + */ + error = vfs_ratelimit_data_read(os, zp->z_blksz, zp->z_blksz); + if (error != 0) { + goto out; + } /* * Initialize the iterator cursor. @@ -1629,9 +1677,22 @@ zfs_readdir(struct inode *ip, zpl_dir_context_t *ctx, cred_t *cr) offset += 1; } ctx->pos = offset; + /* + * TODO: We should be adding size of dirent structure here too. + */ + nbytes += strlen(zap.za_name); } zp->z_zn_prefetch = B_FALSE; /* a lookup will re-enable pre-fetching */ + /* + * Charge the process for the rest, if more than a single block was + * read. + */ + if (error == 0 && nbytes > zp->z_blksz) { + error = vfs_ratelimit_data_read(os, zp->z_blksz, + nbytes - zp->z_blksz); + } + update: zap_cursor_fini(&zc); if (error == ENOENT) @@ -1671,6 +1732,12 @@ 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); + error = vfs_ratelimit_metadata_read(zfsvfs->z_os); + if (error != 0) { + zfs_exit(zfsvfs, FTAG); + return (error); + } + mutex_enter(&zp->z_lock); #ifdef HAVE_GENERIC_FILLATTR_IDMAP_REQMASK @@ -2269,6 +2336,12 @@ zfs_setattr(znode_t *zp, vattr_t *vap, int flags, cred_t *cr, zidmap_t *mnt_ns) goto out2; } } + + err = vfs_ratelimit_metadata_write(os); + if (err != 0) { + goto out2; + } + tx = dmu_tx_create(os); if (mask & ATTR_MODE) { @@ -2981,6 +3054,11 @@ zfs_rename(znode_t *sdzp, char *snm, znode_t *tdzp, char *tnm, } } + error = vfs_ratelimit_metadata_write(zfsvfs->z_os); + if (error != 0) { + goto out; + } + 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 +3372,15 @@ zfs_symlink(znode_t *dzp, char *name, vattr_t *vap, char *link, zfs_exit(zfsvfs, FTAG); return (SET_ERROR(EDQUOT)); } + + error = vfs_ratelimit_metadata_write(zfsvfs->z_os); + if (error != 0) { + zfs_acl_ids_free(&acl_ids); + zfs_dirent_unlock(dl); + zfs_exit(zfsvfs, FTAG); + return (error); + } + 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 +3489,12 @@ zfs_readlink(struct inode *ip, zfs_uio_t *uio, cred_t *cr) if ((error = zfs_enter_verify_zp(zfsvfs, zp, FTAG)) != 0) return (error); + error = vfs_ratelimit_metadata_read(zfsvfs->z_os); + if (error != 0) { + zfs_exit(zfsvfs, FTAG); + return (error); + } + mutex_enter(&zp->z_lock); if (zp->z_is_sa) error = sa_lookup_uio(zp->z_sa_hdl, @@ -3539,6 +3632,12 @@ zfs_link(znode_t *tdzp, znode_t *szp, char *name, cred_t *cr, return (error); } + error = vfs_ratelimit_metadata_write(zfsvfs->z_os); + if (error != 0) { + zfs_exit(zfsvfs, FTAG); + return (error); + } + top: /* * Attempt to lock directory; fail if entry already exists. @@ -3780,6 +3879,13 @@ zfs_putpage(struct inode *ip, struct page *pp, struct writeback_control *wbc, return (0); } + if (vfs_ratelimit_data_write(zfsvfs->z_os, zp->z_blksz, pglen) != 0) { + unlock_page(pp); + zfs_rangelock_exit(lr); + zfs_exit(zfsvfs, FTAG); + return (0); + } + /* * Counterpart for redirty_page_for_writepage() above. This page * was in fact not skipped and should not be counted as if it were. @@ -3790,6 +3896,11 @@ zfs_putpage(struct inode *ip, struct page *pp, struct writeback_control *wbc, set_page_writeback(pp); unlock_page(pp); + err = vfs_ratelimit_data_write(zfsvfs->z_os, zp->z_blksz, pglen); + if (err != 0) { + goto error; + } + 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); @@ -3798,6 +3909,7 @@ zfs_putpage(struct inode *ip, struct page *pp, struct writeback_control *wbc, err = dmu_tx_assign(tx, TXG_WAIT); if (err != 0) { dmu_tx_abort(tx); +error: #ifdef HAVE_VFS_FILEMAP_DIRTY_FOLIO filemap_dirty_folio(page_mapping(pp), page_folio(pp)); #else @@ -3905,6 +4017,11 @@ zfs_dirty_inode(struct inode *ip, int flags) } #endif + error = vfs_ratelimit_metadata_write(zfsvfs->z_os); + if (error != 0) { + goto out; + } + tx = dmu_tx_create(zfsvfs->z_os); dmu_tx_hold_sa(tx, zp->z_sa_hdl, B_FALSE); @@ -3950,7 +4067,6 @@ zfs_inactive(struct inode *ip) znode_t *zp = ITOZ(ip); zfsvfs_t *zfsvfs = ITOZSB(ip); uint64_t atime[2]; - int error; int need_unlock = 0; /* Only read lock if we haven't already write locked, e.g. rollback */ @@ -3965,26 +4081,30 @@ zfs_inactive(struct inode *ip) } if (zp->z_atime_dirty && zp->z_unlinked == B_FALSE) { + if (vfs_ratelimit_metadata_write(zfsvfs->z_os) != 0) { + goto out; + } + dmu_tx_t *tx = dmu_tx_create(zfsvfs->z_os); dmu_tx_hold_sa(tx, zp->z_sa_hdl, B_FALSE); zfs_sa_upgrade_txholds(tx, zp); - error = dmu_tx_assign(tx, TXG_WAIT); - if (error) { + if (dmu_tx_assign(tx, TXG_WAIT) != 0) { dmu_tx_abort(tx); - } else { - inode_timespec_t tmp_atime; - tmp_atime = zpl_inode_get_atime(ip); - ZFS_TIME_ENCODE(&tmp_atime, atime); - mutex_enter(&zp->z_lock); - (void) sa_update(zp->z_sa_hdl, SA_ZPL_ATIME(zfsvfs), - (void *)&atime, sizeof (atime), tx); - zp->z_atime_dirty = B_FALSE; - mutex_exit(&zp->z_lock); - dmu_tx_commit(tx); + goto out; } - } + inode_timespec_t tmp_atime; + tmp_atime = zpl_inode_get_atime(ip); + ZFS_TIME_ENCODE(&tmp_atime, atime); + mutex_enter(&zp->z_lock); + (void) sa_update(zp->z_sa_hdl, SA_ZPL_ATIME(zfsvfs), + (void *)&atime, sizeof (atime), tx); + zp->z_atime_dirty = B_FALSE; + mutex_exit(&zp->z_lock); + dmu_tx_commit(tx); + } +out: zfs_zinactive(zp); if (need_unlock) rw_exit(&zfsvfs->z_teardown_inactive_lock); @@ -4000,6 +4120,7 @@ zfs_fillpage(struct inode *ip, struct page *pp) loff_t i_size = i_size_read(ip); u_offset_t io_off = page_offset(pp); size_t io_len = PAGE_SIZE; + int error; ASSERT3U(io_off, <, i_size); @@ -4007,12 +4128,12 @@ zfs_fillpage(struct inode *ip, struct page *pp) io_len = i_size - io_off; void *va = kmap(pp); - int error = dmu_read(zfsvfs->z_os, ITOZ(ip)->z_id, io_off, + error = dmu_read(zfsvfs->z_os, ITOZ(ip)->z_id, io_off, io_len, va, DMU_READ_PREFETCH); if (io_len != PAGE_SIZE) memset((char *)va + io_len, 0, PAGE_SIZE - io_len); kunmap(pp); - +out: if (error) { /* convert checksum errors into IO errors */ if (error == ECKSUM) @@ -4049,7 +4170,9 @@ zfs_getpage(struct inode *ip, struct page *pp) if ((error = zfs_enter_verify_zp(zfsvfs, zp, FTAG)) != 0) return (error); - error = zfs_fillpage(ip, pp); + error = vfs_ratelimit_data_read(zfsvfs->z_os, 0, PAGE_SIZE); + if (error == 0) + error = zfs_fillpage(ip, pp); if (error == 0) dataset_kstats_update_read_kstats(&zfsvfs->z_kstat, PAGE_SIZE); diff --git a/module/os/linux/zfs/zvol_os.c b/module/os/linux/zfs/zvol_os.c index 3e020e532263..b65064e3cc23 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,21 @@ 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; + error = vfs_ratelimit_data_write(zv->zv_objset, + zv->zv_volblocksize, bytes); + if (error != 0) { + /* XXX-PJD Is it safe to reset the error? */ + if (error == EINTR && uio.uio_resid < start_resid) + error = 0; + break; + } + + 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 +405,13 @@ 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? */ + error = vfs_ratelimit_metadata_write(zv->zv_objset); + if (error != 0) { + zfs_rangelock_exit(lr); + goto unlock; + } + tx = dmu_tx_create(zv->zv_objset); dmu_tx_mark_netfree(tx); error = dmu_tx_assign(tx, TXG_WAIT); @@ -475,6 +493,15 @@ zvol_read(zv_request_t *zvr) if (bytes > volsize - uio.uio_loffset) bytes = volsize - uio.uio_loffset; + error = vfs_ratelimit_data_read(zv->zv_objset, + zv->zv_volblocksize, bytes); + if (error != 0) { + /* XXX-PJD Is it safe to reset the error? */ + if (error == EINTR && uio.uio_resid < start_resid) + error = 0; + break; + } + 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 0119191d7920..5a756b10494f 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,13 @@ flush_write_batch_impl(struct receive_writer_arg *rwa) ASSERT3U(drrw->drr_object, ==, rwa->last_object); + /* + * vfs_ratelimit_data_write_spin() will sleep in short periods + * and return immediately when a signal is pending. + */ + vfs_ratelimit_data_write_spin(rwa->os, 0, + 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 cb2b62fed313..92c40d25e798 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 @@ -1630,6 +1631,7 @@ issue_data_read(struct send_reader_thread_arg *srta, struct send_range *range) struct srd *srdp = &range->sru.data; blkptr_t *bp = &srdp->bp; objset_t *os = srta->smta->os; + int error; ASSERT3U(range->type, ==, DATA); ASSERT3U(range->start_blkid + 1, ==, range->end_blkid); @@ -1684,9 +1686,15 @@ issue_data_read(struct send_reader_thread_arg *srta, struct send_range *range) .zb_blkid = range->start_blkid, }; + /* + * vfs_ratelimit_data_read_spin() will sleep in short periods and return + * immediately when a signal is pending. + */ + vfs_ratelimit_data_read_spin(os, 0, BP_GET_LSIZE(bp)); + arc_flags_t aflags = ARC_FLAG_CACHED_ONLY; - int arc_err = arc_read(NULL, os->os_spa, bp, + error = arc_read(NULL, os->os_spa, bp, arc_getbuf_func, &srdp->abuf, ZIO_PRIORITY_ASYNC_READ, zioflags, &aflags, &zb); /* @@ -1695,7 +1703,7 @@ issue_data_read(struct send_reader_thread_arg *srta, struct send_range *range) * entry to the ARC, and we also avoid polluting the ARC cache with * data that is not likely to be used in the future. */ - if (arc_err != 0) { + if (error != 0) { srdp->abd = abd_alloc_linear(srdp->datasz, B_FALSE); srdp->io_outstanding = B_TRUE; zio_nowait(zio_read(NULL, os->os_spa, bp, srdp->abd, @@ -2552,8 +2560,9 @@ dmu_send_impl(struct dmu_send_params *dspp) while (err == 0 && !range->eos_marker) { err = do_dump(&dsc, range); range = get_next_range(&srt_arg->q, range); - if (issig()) + if (issig()) { err = SET_ERROR(EINTR); + } } /* diff --git a/module/zfs/dsl_dir.c b/module/zfs/dsl_dir.c index baf970121a61..a5d66ac18c6e 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) @@ -288,6 +371,10 @@ dsl_dir_hold_obj(dsl_pool_t *dp, uint64_t ddobj, dd->dd_snap_cmtime = t; } + if (dd->dd_myname[0] != '$') { + dsl_dir_ratelimit_read(dd); + } + dmu_buf_init_user(&dd->dd_dbu, NULL, dsl_dir_evict_async, &dd->dd_dbuf); winner = dmu_buf_set_user_ie(dbuf, &dd->dd_dbu); @@ -297,6 +384,7 @@ dsl_dir_hold_obj(dsl_pool_t *dp, uint64_t ddobj, if (dsl_deadlist_is_open(&dd->dd_livelist)) dsl_dir_livelist_close(dd); dsl_prop_fini(dd); + vfs_ratelimit_free(dd->dd_ratelimit); cv_destroy(&dd->dd_activity_cv); mutex_destroy(&dd->dd_activity_lock); mutex_destroy(&dd->dd_lock); @@ -304,6 +392,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 +2000,125 @@ 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 +2316,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->dd_ratelimit_root; + + 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 +2383,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..d9e2ef91ff15 --- /dev/null +++ b/module/zfs/vfs_ratelimit.c @@ -0,0 +1,688 @@ +/* + * 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, + * block cloning, 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 to quickly access the ratelimit + * structure we need by just going to ds_dir->dd_ratelimit_root; + * If ratelimits are not configured on this dataset and all of its ancestors, + * 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 (16 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. + * Note that very low limits may be rounded up - 7 ops/s limit will be rounded + * up to 16 ops/s, so each time slot is assigned 1 op/s limit. This rounding up + * is done in the kernel and isn't shown in the properties. + * + * 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). + * + * For each type we use two fields to track the wait times: rl_timeslot and + * rl_reminder. rl_timeslot holds the point in time up to which the last + * processes is waiting for. If the rl_timeslot is lower than the current time, + * it means that no processes are waiting. rl_reminder is the amount of data + * modulo the limit. For example if we have a read bandwidth limit of 64MB/s, + * so it is 4MB per 1/16s. The process is trying to read 11MB. This would + * give us rl_timeslot = now + 2 (we account for 2 full time slots of 1/16s) + * and rl_reminder = 3MB. This process has to sleep for 2/16s. When immediately + * another process is trying to read 1MB, this 1MB will be added to the current + * rl_reminder giving 4MB, so full limit unit for 1/16s. Now rl_timeslot will + * be set to now + 3 and rl_reminder to 0. The last process is going to sleep + * for 3/16s. + */ + +/* + * Number of slots we divide one second into. More granularity is better for + * interactivity, but for small limits we may lose some precision. + */ +#define RATELIMIT_RESOLUTION 16 + +struct vfs_ratelimit { + kmutex_t rl_lock; + uint64_t rl_limits[ZFS_RATELIMIT_NTYPES]; + uint64_t rl_timeslot[ZFS_RATELIMIT_NTYPES]; + uint64_t rl_reminder[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_LAST; 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); + + if (limits != NULL) { + for (i = ZFS_RATELIMIT_FIRST; i <= ZFS_RATELIMIT_LAST; 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) +{ + + if (rl == NULL) { + return; + } + + 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; + + gethrestime(&ts); + + return (((hrtime_t)ts.tv_sec * RATELIMIT_RESOLUTION) + + ts.tv_nsec / (NANOSEC / RATELIMIT_RESOLUTION)); +} + +/* + * Returns bit mask of the types configured for the given ratelimit structure. + */ +static int +ratelimit_types(const uint64_t *counts) +{ + int types, type; + + types = 0; + for (type = ZFS_RATELIMIT_FIRST; type <= ZFS_RATELIMIT_LAST; type++) { + if (counts[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; + + 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); + } + if (dd->dd_ratelimit != NULL) { + int mytypes; + + mytypes = ratelimit_types(dd->dd_ratelimit->rl_limits); + 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) +{ + 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); + } + if (dd->dd_ratelimit != NULL) { + int mytypes; + + mytypes = ratelimit_types(dd->dd_ratelimit->rl_limits); + if ((mytypes & types) != 0) { + /* + * This dataset has at last one limit we are + * interested in. + */ + return (dd); + } + } + } +} + +/* + * 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 hrtime_t +ratelimit_account(struct vfs_ratelimit *rl, hrtime_t now, + const uint64_t *counts) +{ + hrtime_t timeslot; + int type; + + timeslot = now; + + mutex_enter(&rl->rl_lock); + + for (type = ZFS_RATELIMIT_FIRST; type <= ZFS_RATELIMIT_LAST; type++) { + uint64_t count; + + if (rl->rl_limits[type] == 0) { + /* This type has no limit configured on this dataset. */ + continue; + } + count = counts[type]; + if (count == 0) { + /* Not interested in this type. */ + continue; + } + + if (rl->rl_timeslot[type] < now) { + rl->rl_reminder[type] = 0; + rl->rl_timeslot[type] = now; + } else { + count += rl->rl_reminder[type]; + } + + rl->rl_timeslot[type] += count / rl->rl_limits[type]; + rl->rl_reminder[type] = count % rl->rl_limits[type]; + + if (timeslot < rl->rl_timeslot[type]) { + timeslot = rl->rl_timeslot[type]; + } + } + + mutex_exit(&rl->rl_lock); + + return (timeslot); +} + +static hrtime_t +ratelimit_account_all(objset_t *os, const uint64_t *counts) +{ + dsl_dir_t *dd; + hrtime_t now, timeslot; + int types; + + ASSERT(RRM_READ_HELD(&os->os_spa->spa_ratelimit_lock)); + + types = ratelimit_types(counts); + now = timeslot = gettimeslot(); + + for (dd = ratelimit_first(os, types); dd != NULL; + dd = ratelimit_parent(dd, types)) { + hrtime_t ts; + + ts = ratelimit_account(dd->dd_ratelimit, now, counts); + if (ts > timeslot) { + timeslot = ts; + } + } + + return (timeslot); +} + +static int +ratelimit_sleep(hrtime_t timeslot) +{ + hrtime_t now; + int error = 0; + + now = gettimeslot(); + + if (timeslot > now) { + /* + * Too much traffic, slow it down. + */ +#ifdef _KERNEL + if (delay_sig((hz / RATELIMIT_RESOLUTION) * (timeslot - now))) { + error = SET_ERROR(EINTR); + } +#else + delay((hz / RATELIMIT_RESOLUTION) * (timeslot - now)); +#endif + } + + return (error); +} + +static int +vfs_ratelimit_sleep(objset_t *os, const uint64_t *counts) +{ + hrtime_t timeslot; + + /* + * Prevents configuration changes when we have requests in-flight. + */ + rrm_enter_read(&os->os_spa->spa_ratelimit_lock, FTAG); + + timeslot = ratelimit_account_all(os, counts); + + rrm_exit(&os->os_spa->spa_ratelimit_lock, FTAG); + + return (ratelimit_sleep(timeslot)); +} + +/* + * For every data read we charge: + * - bytes of read bandwidth + * - bytes of total bandwidth + * - (bytes + blocksize - 1) / blocksize of read operations + * - (bytes + blocksize - 1) / blocksize of total operations + */ +int +vfs_ratelimit_data_read(objset_t *os, size_t blocksize, size_t bytes) +{ + uint64_t counts[ZFS_RATELIMIT_NTYPES]; + size_t operations; + + if (bytes == 0) { + return (0); + } + if (blocksize == 0) { + blocksize = bytes; + } + operations = (bytes + blocksize - 1) / blocksize; + + memset(counts, 0, sizeof (counts)); + counts[ZFS_RATELIMIT_BW_READ] = bytes; + counts[ZFS_RATELIMIT_BW_TOTAL] = bytes; + counts[ZFS_RATELIMIT_OP_READ] = operations; + counts[ZFS_RATELIMIT_OP_TOTAL] = operations; + + return (vfs_ratelimit_sleep(os, counts)); +} + +/* + * For every data write we charge: + * - bytes of write bandwidth + * - bytes of total bandwidth + * - (bytes + blocksize - 1) / blocksize of read operations + * - (bytes + blocksize - 1) / blocksize of total operations + */ +int +vfs_ratelimit_data_write(objset_t *os, size_t blocksize, size_t bytes) +{ + uint64_t counts[ZFS_RATELIMIT_NTYPES]; + size_t operations; + + if (bytes == 0) { + return (0); + } + if (blocksize == 0) { + blocksize = bytes; + } + operations = (bytes + blocksize - 1) / blocksize; + + memset(counts, 0, sizeof (counts)); + counts[ZFS_RATELIMIT_BW_WRITE] = bytes; + counts[ZFS_RATELIMIT_BW_TOTAL] = bytes; + counts[ZFS_RATELIMIT_OP_WRITE] = operations; + counts[ZFS_RATELIMIT_OP_TOTAL] = operations; + + return (vfs_ratelimit_sleep(os, counts)); +} + +int +vfs_ratelimit_data_copy(objset_t *srcos, objset_t *dstos, size_t blocksize, + size_t bytes) +{ + uint64_t counts[ZFS_RATELIMIT_NTYPES]; + size_t operations; + hrtime_t dstts, srcts; + spa_t *spa = srcos->os_spa; + + if (bytes == 0) { + return (0); + } + if (blocksize == 0) { + blocksize = bytes; + } + operations = (bytes + blocksize - 1) / blocksize; + + /* + * Prevents configuration changes when we have requests in-flight. + */ + rrm_enter_read(&spa->spa_ratelimit_lock, FTAG); + + memset(counts, 0, sizeof (counts)); + counts[ZFS_RATELIMIT_BW_READ] = bytes; + counts[ZFS_RATELIMIT_BW_TOTAL] = bytes; + counts[ZFS_RATELIMIT_OP_READ] = operations; + counts[ZFS_RATELIMIT_OP_TOTAL] = operations; + + srcts = ratelimit_account_all(srcos, counts); + + memset(counts, 0, sizeof (counts)); + counts[ZFS_RATELIMIT_BW_WRITE] = bytes; + counts[ZFS_RATELIMIT_BW_TOTAL] = bytes; + counts[ZFS_RATELIMIT_OP_WRITE] = operations; + counts[ZFS_RATELIMIT_OP_TOTAL] = operations; + + dstts = ratelimit_account_all(dstos, counts); + + rrm_exit(&spa->spa_ratelimit_lock, FTAG); + + return (ratelimit_sleep(dstts > srcts ? dstts : srcts)); +} + +/* + * For every metadata read we charge: + * - one read operation + * - one total operation + */ +int +vfs_ratelimit_metadata_read(objset_t *os) +{ + uint64_t counts[ZFS_RATELIMIT_NTYPES]; + + memset(counts, 0, sizeof (counts)); + counts[ZFS_RATELIMIT_OP_READ] = 1; + counts[ZFS_RATELIMIT_OP_TOTAL] = 1; + + return (vfs_ratelimit_sleep(os, counts)); +} + +/* + * For every metadata write we charge: + * - one read operation + * - one total operation + */ +int +vfs_ratelimit_metadata_write(objset_t *os) +{ + uint64_t counts[ZFS_RATELIMIT_NTYPES]; + + memset(counts, 0, sizeof (counts)); + counts[ZFS_RATELIMIT_OP_WRITE] = 1; + counts[ZFS_RATELIMIT_OP_TOTAL] = 1; + + return (vfs_ratelimit_sleep(os, counts)); +} + +/* + * Function spins until timeout is reached or the process received a signal. + * This function is different than ratelimit_sleep(), because pause_sig() + * might not be woken up by a signal if the process has multiple threads. + * We use *_spin() functions for zfs send/recv where kernel starts additional + * kernel threads and interrupting userland process with CTRL+C (SIGINT) + * doesn't interrupt pause_sig() waiting in another kernel thread. + */ +static void +ratelimit_spin(objset_t *os, const uint64_t *counts) +{ + hrtime_t timeslot; + + /* + * Prevents configuration changes when we have requests in-flight. + */ + rrm_enter_read(&os->os_spa->spa_ratelimit_lock, FTAG); + + timeslot = ratelimit_account_all(os, counts); + + rrm_exit(&os->os_spa->spa_ratelimit_lock, FTAG); + + while (timeslot > gettimeslot() && !issig()) { + delay(hz / RATELIMIT_RESOLUTION); + } +} + +void +vfs_ratelimit_data_read_spin(objset_t *os, size_t blocksize, size_t bytes) +{ + uint64_t counts[ZFS_RATELIMIT_NTYPES]; + size_t operations; + + if (bytes == 0) { + return; + } + + if (blocksize == 0) { + blocksize = bytes; + } + operations = (bytes + blocksize - 1) / blocksize; + + memset(counts, 0, sizeof (counts)); + counts[ZFS_RATELIMIT_BW_READ] = bytes; + counts[ZFS_RATELIMIT_BW_TOTAL] = bytes; + counts[ZFS_RATELIMIT_OP_READ] = operations; + counts[ZFS_RATELIMIT_OP_TOTAL] = operations; + + ratelimit_spin(os, counts); +} + +void +vfs_ratelimit_data_write_spin(objset_t *os, size_t blocksize, size_t bytes) +{ + uint64_t counts[ZFS_RATELIMIT_NTYPES]; + size_t operations; + + if (bytes == 0) { + return; + } + + if (blocksize == 0) { + blocksize = bytes; + } + operations = (bytes + blocksize - 1) / blocksize; + + memset(counts, 0, sizeof (counts)); + counts[ZFS_RATELIMIT_BW_WRITE] = bytes; + counts[ZFS_RATELIMIT_BW_TOTAL] = bytes; + counts[ZFS_RATELIMIT_OP_WRITE] = operations; + counts[ZFS_RATELIMIT_OP_TOTAL] = operations; + + ratelimit_spin(os, counts); +} diff --git a/module/zfs/zfs_ioctl.c b/module/zfs/zfs_ioctl.c index 7b527eb75e83..97db6907574f 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 f3db953eab46..56c3a4316973 100644 --- a/module/zfs/zfs_vnops.c +++ b/module/zfs/zfs_vnops.c @@ -53,6 +53,7 @@ #include #include #include +#include #include #include #include @@ -77,7 +78,8 @@ static int zfs_bclone_wait_dirty = 0; /* * Maximum bytes to read per chunk in zfs_read(). */ -static uint64_t zfs_vnops_read_chunk_size = 1024 * 1024; +//static uint64_t zfs_vnops_read_chunk_size = 1024 * 1024; +static uint64_t zfs_vnops_read_chunk_size = 1024 * 512; int zfs_fsync(znode_t *zp, int syncflag, cred_t *cr) @@ -297,6 +299,16 @@ 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)); + + error = vfs_ratelimit_data_read(zfsvfs->z_os, zp->z_blksz, + nbytes); + if (error != 0) { + if (error == EINTR && n < start_resid) { + error = 0; + } + break; + } + #ifdef UIO_NOCOPY if (zfs_uio_segflg(uio) == UIO_NOCOPY) error = mappedread_sf(zp, nbytes, uio); @@ -610,6 +622,16 @@ zfs_write(znode_t *zp, zfs_uio_t *uio, int ioflag, cred_t *cr) } } + error = vfs_ratelimit_data_write(zfsvfs->z_os, blksz, nbytes); + if (error != 0) { + if (error == EINTR && n < start_resid) { + error = 0; + } + if (abuf != NULL) + dmu_return_arcbuf(abuf); + break; + } + /* * Start a transaction. */ @@ -1309,6 +1331,11 @@ zfs_clone_range(znode_t *inzp, uint64_t *inoffp, znode_t *outzp, break; } + error = vfs_ratelimit_data_copy(inos, outos, inblksz, size); + if (error != 0) { + break; + } + 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..957f4c0e224e --- /dev/null +++ b/tests/zfs-tests/tests/functional/ratelimit/filesystem_op_single.ksh @@ -0,0 +1,140 @@ +#! /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" +log_must ratelimit_filesystem_op_single create limit_op_total=128 512 4 "$TESTDIR/file" +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" +log_must ratelimit_filesystem_op_single create limit_op_read=128 1024 1 "$TESTDIR/file" +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 From bea936dce8404599b1d0d92b1505d2bcf502e628 Mon Sep 17 00:00:00 2001 From: Pawel Jakub Dawidek Date: Sun, 6 Oct 2024 11:39:20 -0700 Subject: [PATCH 2/2] Simplify the code: remove redundant variables. Signed-off-by: Pawel Jakub Dawidek --- module/os/freebsd/zfs/zfs_vnops_os.c | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/module/os/freebsd/zfs/zfs_vnops_os.c b/module/os/freebsd/zfs/zfs_vnops_os.c index 0491cd8ef38b..21ac14f13983 100644 --- a/module/os/freebsd/zfs/zfs_vnops_os.c +++ b/module/os/freebsd/zfs/zfs_vnops_os.c @@ -1249,7 +1249,6 @@ zfs_remove_(vnode_t *dvp, vnode_t *vp, const char *name, cred_t *cr) uint64_t obj = 0; dmu_tx_t *tx; boolean_t unlinked; - uint64_t txtype; int error; @@ -1340,8 +1339,7 @@ zfs_remove_(vnode_t *dvp, vnode_t *vp, const char *name, cred_t *cr) vp->v_vflag |= VV_NOSYNC; } /* XXX check changes to linux vnops */ - txtype = TX_REMOVE; - zfs_log_remove(zilog, tx, txtype, dzp, name, obj, unlinked); + zfs_log_remove(zilog, tx, TX_REMOVE, dzp, name, obj, unlinked); dmu_tx_commit(tx); out: @@ -1669,8 +1667,7 @@ zfs_rmdir_(vnode_t *dvp, vnode_t *vp, const char *name, cred_t *cr) error = zfs_link_destroy(dzp, name, zp, tx, ZEXISTS, NULL); if (error == 0) { - uint64_t txtype = TX_RMDIR; - zfs_log_remove(zilog, tx, txtype, dzp, name, + zfs_log_remove(zilog, tx, TX_RMDIR, dzp, name, ZFS_NO_OBJECT, B_FALSE); } @@ -3579,7 +3576,6 @@ zfs_symlink(znode_t *dzp, const char *name, vattr_t *vap, int error; zfs_acl_ids_t acl_ids; boolean_t fuid_dirtied; - uint64_t txtype = TX_SYMLINK; ASSERT3S(vap->va_type, ==, VLNK); @@ -3683,7 +3679,7 @@ zfs_symlink(znode_t *dzp, const char *name, vattr_t *vap, VOP_UNLOCK1(ZTOV(zp)); zrele(zp); } else { - zfs_log_symlink(zilog, tx, txtype, dzp, zp, name, link); + zfs_log_symlink(zilog, tx, TX_SYMLINK, dzp, zp, name, link); } zfs_acl_ids_free(&acl_ids);