From 13552d754f5e12b0876470d7e205d50ad18081f4 Mon Sep 17 00:00:00 2001 From: Ameer Hamza Date: Mon, 29 Dec 2025 23:44:28 +0500 Subject: [PATCH] ZTS: Add L2ARC DWPD and parallel writes tests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add four new functional tests to validate L2ARC DWPD rate limiting and parallel write features: - l2arc_dwpd_ratelimit_pos: Verifies DWPD rate limiting with different values (0, 100, 1000, 10000) and ordering - l2arc_dwpd_reimport_pos: Verifies DWPD rate limiting persists after pool export/import - l2arc_multidev_scaling_pos: Verifies parallel write scaling ratio (dual devices achieve ~2× single device throughput) - l2arc_multidev_throughput_pos: Verifies absolute parallel write throughput scales with device count (~32MB/s per device) Reviewed-by: Alexander Motin Reviewed-by: Brian Behlendorf Signed-off-by: Ameer Hamza Closes #18093 --- tests/runfiles/common.run | 3 +- tests/zfs-tests/include/tunables.cfg | 2 +- tests/zfs-tests/tests/Makefile.am | 4 + .../tests/functional/cache/cache_012_pos.ksh | 3 + .../tests/functional/l2arc/l2arc.cfg | 3 +- .../l2arc/l2arc_dwpd_ratelimit_pos.ksh | 138 ++++++++++++++ .../l2arc/l2arc_dwpd_reimport_pos.ksh | 169 ++++++++++++++++++ .../l2arc/l2arc_multidev_scaling_pos.ksh | 162 +++++++++++++++++ .../l2arc/l2arc_multidev_throughput_pos.ksh | 133 ++++++++++++++ .../tests/functional/trim/trim_l2arc.ksh | 5 +- 10 files changed, 618 insertions(+), 4 deletions(-) create mode 100755 tests/zfs-tests/tests/functional/l2arc/l2arc_dwpd_ratelimit_pos.ksh create mode 100755 tests/zfs-tests/tests/functional/l2arc/l2arc_dwpd_reimport_pos.ksh create mode 100755 tests/zfs-tests/tests/functional/l2arc/l2arc_multidev_scaling_pos.ksh create mode 100755 tests/zfs-tests/tests/functional/l2arc/l2arc_multidev_throughput_pos.ksh diff --git a/tests/runfiles/common.run b/tests/runfiles/common.run index 62c3cb0c0..fc0ae4300 100644 --- a/tests/runfiles/common.run +++ b/tests/runfiles/common.run @@ -1130,7 +1130,8 @@ tags = ['functional', 'log_spacemap'] [tests/functional/l2arc] tests = ['l2arc_arcstats_pos', 'l2arc_mfuonly_pos', 'l2arc_l2miss_pos', - 'persist_l2arc_001_pos', 'persist_l2arc_002_pos', + 'l2arc_dwpd_ratelimit_pos', 'l2arc_dwpd_reimport_pos', 'l2arc_multidev_scaling_pos', + 'l2arc_multidev_throughput_pos', 'persist_l2arc_001_pos', 'persist_l2arc_002_pos', 'persist_l2arc_003_neg', 'persist_l2arc_004_pos', 'persist_l2arc_005_pos'] tags = ['functional', 'l2arc'] diff --git a/tests/zfs-tests/include/tunables.cfg b/tests/zfs-tests/include/tunables.cfg index 54b50c9db..36621b218 100644 --- a/tests/zfs-tests/include/tunables.cfg +++ b/tests/zfs-tests/include/tunables.cfg @@ -46,12 +46,12 @@ INITIALIZE_CHUNK_SIZE initialize_chunk_size zfs_initialize_chunk_size INITIALIZE_VALUE initialize_value zfs_initialize_value KEEP_LOG_SPACEMAPS_AT_EXPORT keep_log_spacemaps_at_export zfs_keep_log_spacemaps_at_export LUA_MAX_MEMLIMIT lua.max_memlimit zfs_lua_max_memlimit +L2ARC_DWPD_LIMIT l2arc.dwpd_limit l2arc_dwpd_limit L2ARC_MFUONLY l2arc.mfuonly l2arc_mfuonly L2ARC_NOPREFETCH l2arc.noprefetch l2arc_noprefetch L2ARC_REBUILD_BLOCKS_MIN_L2SIZE l2arc.rebuild_blocks_min_l2size l2arc_rebuild_blocks_min_l2size L2ARC_REBUILD_ENABLED l2arc.rebuild_enabled l2arc_rebuild_enabled L2ARC_TRIM_AHEAD l2arc.trim_ahead l2arc_trim_ahead -L2ARC_WRITE_BOOST l2arc.write_boost l2arc_write_boost L2ARC_WRITE_MAX l2arc.write_max l2arc_write_max LIVELIST_CONDENSE_NEW_ALLOC livelist.condense.new_alloc zfs_livelist_condense_new_alloc LIVELIST_CONDENSE_SYNC_CANCEL livelist.condense.sync_cancel zfs_livelist_condense_sync_cancel diff --git a/tests/zfs-tests/tests/Makefile.am b/tests/zfs-tests/tests/Makefile.am index f973d6061..7d4d53b2c 100644 --- a/tests/zfs-tests/tests/Makefile.am +++ b/tests/zfs-tests/tests/Makefile.am @@ -1660,6 +1660,10 @@ nobase_dist_datadir_zfs_tests_tests_SCRIPTS += \ functional/l2arc/l2arc_arcstats_pos.ksh \ functional/l2arc/l2arc_l2miss_pos.ksh \ functional/l2arc/l2arc_mfuonly_pos.ksh \ + functional/l2arc/l2arc_dwpd_ratelimit_pos.ksh \ + functional/l2arc/l2arc_dwpd_reimport_pos.ksh \ + functional/l2arc/l2arc_multidev_scaling_pos.ksh \ + functional/l2arc/l2arc_multidev_throughput_pos.ksh \ functional/l2arc/persist_l2arc_001_pos.ksh \ functional/l2arc/persist_l2arc_002_pos.ksh \ functional/l2arc/persist_l2arc_003_neg.ksh \ diff --git a/tests/zfs-tests/tests/functional/cache/cache_012_pos.ksh b/tests/zfs-tests/tests/functional/cache/cache_012_pos.ksh index e8e9ed5dd..877cbe5ed 100755 --- a/tests/zfs-tests/tests/functional/cache/cache_012_pos.ksh +++ b/tests/zfs-tests/tests/functional/cache/cache_012_pos.ksh @@ -55,12 +55,15 @@ function cleanup log_must set_tunable32 L2ARC_WRITE_MAX $write_max log_must set_tunable32 L2ARC_NOPREFETCH $noprefetch + log_must set_tunable32 L2ARC_DWPD_LIMIT $dwpd_limit } log_onexit cleanup typeset write_max=$(get_tunable L2ARC_WRITE_MAX) typeset noprefetch=$(get_tunable L2ARC_NOPREFETCH) +typeset dwpd_limit=$(get_tunable L2ARC_DWPD_LIMIT) log_must set_tunable32 L2ARC_NOPREFETCH 0 +log_must set_tunable32 L2ARC_DWPD_LIMIT 0 typeset VDEV="$VDIR/vdev.disk" typeset VDEV_SZ=$(( 4 * 1024 * 1024 * 1024 )) diff --git a/tests/zfs-tests/tests/functional/l2arc/l2arc.cfg b/tests/zfs-tests/tests/functional/l2arc/l2arc.cfg index ca61bdf2a..f01b95ee4 100644 --- a/tests/zfs-tests/tests/functional/l2arc/l2arc.cfg +++ b/tests/zfs-tests/tests/functional/l2arc/l2arc.cfg @@ -25,7 +25,8 @@ export SIZE=1G export VDIR=$TESTDIR/disk.l2arc export VDEV="$VDIR/a" export VDEV_CACHE="$VDIR/b" -export VDEV1="$VDIR/c" +export VDEV_CACHE2="$VDIR/c" +export VDEV1="$VDIR/d" # fio options export DIRECTORY=/$TESTPOOL diff --git a/tests/zfs-tests/tests/functional/l2arc/l2arc_dwpd_ratelimit_pos.ksh b/tests/zfs-tests/tests/functional/l2arc/l2arc_dwpd_ratelimit_pos.ksh new file mode 100755 index 000000000..65b2bf07a --- /dev/null +++ b/tests/zfs-tests/tests/functional/l2arc/l2arc_dwpd_ratelimit_pos.ksh @@ -0,0 +1,138 @@ +#!/bin/ksh -p +# SPDX-License-Identifier: CDDL-1.0 +# +# CDDL HEADER START +# +# 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. +# +# CDDL HEADER END +# + +# +# Copyright (c) 2024. All rights reserved. +# + +. $STF_SUITE/include/libtest.shlib +. $STF_SUITE/tests/functional/l2arc/l2arc.cfg + +# +# DESCRIPTION: +# L2ARC DWPD rate limiting correctly limits write rate. +# +# STRATEGY: +# 1. Set DWPD limit before creating pool. +# 2. Create pool with cache device (L2ARC >= arc_c_max * 2). +# 3. Populate L2ARC to complete first pass - DWPD only limits writes +# after first pass, so we must fill L2ARC first. +# 4. Delete the file to free ARC and invalidate L2ARC entries. +# 5. Write fresh data - now DWPD rate limiting controls refill rate. +# 6. Measure L2ARC writes over test period. +# 7. Repeat 1-6 for DWPD values 0, 10000, 5000, 1800. +# 8. Verify DWPD=0 > DWPD=10000 > DWPD=5000 > DWPD=1800. +# + +verify_runnable "global" + +log_assert "L2ARC DWPD rate limiting correctly limits write rate." + +function cleanup +{ + if poolexists $TESTPOOL ; then + destroy_pool $TESTPOOL + fi + + restore_tunable L2ARC_WRITE_MAX + restore_tunable L2ARC_NOPREFETCH + restore_tunable L2ARC_DWPD_LIMIT + restore_tunable ARC_MIN + restore_tunable ARC_MAX +} +log_onexit cleanup + +# Save original tunables +save_tunable L2ARC_WRITE_MAX +save_tunable L2ARC_NOPREFETCH +save_tunable L2ARC_DWPD_LIMIT +save_tunable ARC_MIN +save_tunable ARC_MAX + +# Test parameters +typeset cache_sz=900 +typeset fill_mb=1500 +typeset test_time=15 + +# Configure arc_max = 400MB so L2ARC (900MB) >= arc_c_max * 2 threshold +log_must set_tunable64 ARC_MAX $((400 * 1024 * 1024)) +log_must set_tunable64 ARC_MIN $((200 * 1024 * 1024)) +log_must set_tunable32 L2ARC_NOPREFETCH 0 +log_must set_tunable32 L2ARC_WRITE_MAX $((200 * 1024 * 1024)) + +# Create larger main vdev to accommodate fill data +log_must truncate -s 5G $VDEV +log_must truncate -s ${cache_sz}M $VDEV_CACHE + +typeset -A results + +# Test each DWPD value with fresh pool. +# Minimum DWPD=1800 (18 DWPD) gives ~192KB/s (900MB*18/86400), enough to +# write one block (128KB) + log overhead (64KB) without accumulating budget. +for dwpd in 0 10000 5000 1800; do + log_must set_tunable32 L2ARC_DWPD_LIMIT $dwpd + + if poolexists $TESTPOOL; then + destroy_pool $TESTPOOL + fi + log_must zpool create -f $TESTPOOL $VDEV cache $VDEV_CACHE + + # Populate L2ARC in chunks to complete first pass + # (DWPD only limits after first pass) + log_must dd if=/dev/urandom of=/$TESTPOOL/fill1 bs=1M count=$((fill_mb/3)) + log_must sleep 5 + log_must dd if=/dev/urandom of=/$TESTPOOL/fill2 bs=1M count=$((fill_mb/3)) + log_must sleep 5 + log_must dd if=/dev/urandom of=/$TESTPOOL/fill3 bs=1M count=$((fill_mb/3)) + log_must sleep 5 + + # Delete files to free ARC and invalidate L2ARC entries + log_must rm /$TESTPOOL/fill1 /$TESTPOOL/fill2 /$TESTPOOL/fill3 + log_must sync + + # Take baseline - L2ARC now has space for fresh data + baseline=$(kstat arcstats.l2_write_bytes) + log_note "Baseline for DWPD=$dwpd: ${baseline}" + + # Write fresh data - DWPD rate limiting now controls refill rate + # Write arc_max worth of data since that's what flows through ARC + dd if=/dev/urandom of=/$TESTPOOL/test bs=1M count=400 & + dd_pid=$! + log_must sleep $test_time + kill $dd_pid 2>/dev/null + wait $dd_pid 2>/dev/null + log_must sleep 2 + end=$(kstat arcstats.l2_write_bytes) + + results[$dwpd]=$((end - baseline)) + log_note "DWPD=$dwpd: delta=$((results[$dwpd] / 1024))KB" +done + +# Verify ordering: higher DWPD = more writes, 0 = unlimited +if [[ ${results[0]} -le ${results[10000]} ]]; then + log_fail "DWPD=0 (unlimited) should write more than DWPD=10000" +fi +if [[ ${results[10000]} -le ${results[5000]} ]]; then + log_fail "DWPD=10000 should write more than DWPD=5000" +fi +if [[ ${results[5000]} -le ${results[1800]} ]]; then + log_fail "DWPD=5000 should write more than DWPD=1800" +fi + +log_must zpool destroy $TESTPOOL + +log_pass "L2ARC DWPD rate limiting correctly limits write rate." diff --git a/tests/zfs-tests/tests/functional/l2arc/l2arc_dwpd_reimport_pos.ksh b/tests/zfs-tests/tests/functional/l2arc/l2arc_dwpd_reimport_pos.ksh new file mode 100755 index 000000000..50208bce2 --- /dev/null +++ b/tests/zfs-tests/tests/functional/l2arc/l2arc_dwpd_reimport_pos.ksh @@ -0,0 +1,169 @@ +#!/bin/ksh -p +# SPDX-License-Identifier: CDDL-1.0 +# +# CDDL HEADER START +# +# 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. +# +# CDDL HEADER END +# + +# +# Copyright (c) 2024. All rights reserved. +# + +. $STF_SUITE/include/libtest.shlib +. $STF_SUITE/tests/functional/l2arc/l2arc.cfg + +# +# DESCRIPTION: +# L2ARC DWPD rate limiting works after pool export/import. +# +# STRATEGY: +# 1. Set DWPD limit before creating pool. +# 2. Create pool with cache device (L2ARC >= arc_c_max * 2). +# 3. Fill L2ARC with staged writes and wait for first pass to complete. +# 4. Measure DWPD-limited writes with continuous workload. +# 5. Export and import pool. +# 6. Wait for rebuild, then fill L2ARC with staged writes. +# 7. Measure DWPD-limited writes again. +# 8. Verify rate limiting still works after import (non-zero writes). +# + +verify_runnable "global" + +log_assert "L2ARC DWPD rate limiting works after pool export/import." + +function cleanup +{ + if poolexists $TESTPOOL ; then + destroy_pool $TESTPOOL + fi + + restore_tunable L2ARC_WRITE_MAX + restore_tunable L2ARC_NOPREFETCH + restore_tunable L2ARC_DWPD_LIMIT + restore_tunable L2ARC_REBUILD_BLOCKS_MIN_L2SIZE + restore_tunable ARC_MIN + restore_tunable ARC_MAX +} +log_onexit cleanup + +# Save original tunables +save_tunable L2ARC_WRITE_MAX +save_tunable L2ARC_NOPREFETCH +save_tunable L2ARC_DWPD_LIMIT +save_tunable L2ARC_REBUILD_BLOCKS_MIN_L2SIZE +save_tunable ARC_MIN +save_tunable ARC_MAX + +# Test parameters +typeset cache_sz=900 +typeset fill_mb=1500 +typeset test_time=15 + +# Set DWPD before pool creation (10000 = 100 DWPD) +log_must set_tunable32 L2ARC_DWPD_LIMIT 10000 +log_must set_tunable32 L2ARC_REBUILD_BLOCKS_MIN_L2SIZE 0 + +# Configure arc_max = 400MB so L2ARC (900MB) >= arc_c_max * 2 threshold +log_must set_tunable64 ARC_MAX $((400 * 1024 * 1024)) +log_must set_tunable64 ARC_MIN $((200 * 1024 * 1024)) +log_must set_tunable32 L2ARC_NOPREFETCH 0 +log_must set_tunable32 L2ARC_WRITE_MAX $((200 * 1024 * 1024)) + +# Create larger main vdev to accommodate fill data +log_must truncate -s 8G $VDEV +log_must truncate -s ${cache_sz}M $VDEV_CACHE + +log_must zpool create -f $TESTPOOL $VDEV cache $VDEV_CACHE + +# Staged fills to allow L2ARC to drain between writes +log_must dd if=/dev/urandom of=/$TESTPOOL/file1a bs=1M count=$((fill_mb/3)) +log_must sleep 5 +log_must dd if=/dev/urandom of=/$TESTPOOL/file1b bs=1M count=$((fill_mb/3)) +log_must sleep 5 +log_must dd if=/dev/urandom of=/$TESTPOOL/file1c bs=1M count=$((fill_mb/3)) +log_must sleep 5 + +# Verify L2ARC is populated before export +typeset l2_size_before=$(kstat arcstats.l2_size) +log_note "L2ARC size before export: $((l2_size_before / 1024 / 1024))MB" +if [[ $l2_size_before -eq 0 ]]; then + log_fail "L2ARC not populated before export" +fi + +# Measure DWPD-limited writes before export +baseline1=$(kstat arcstats.l2_write_bytes) +log_note "Baseline before export: ${baseline1}" +dd if=/dev/urandom of=/$TESTPOOL/file2 bs=1M count=2000 >/dev/null 2>&1 & +dd_pid=$! +log_must sleep $test_time +kill $dd_pid 2>/dev/null +wait $dd_pid 2>/dev/null +log_must sleep 2 +end1=$(kstat arcstats.l2_write_bytes) +typeset writes_before=$((end1 - baseline1)) + +log_note "Writes before export: $((writes_before / 1024))KB" + +# Verify L2ARC actually wrote data +if [[ $writes_before -eq 0 ]]; then + log_fail "No L2ARC writes before export - DWPD may be too restrictive" +fi + +# Export and import pool +log_must zpool export $TESTPOOL +log_must zpool import -d $VDIR $TESTPOOL + +# Wait for rebuild to complete +log_must sleep 5 + +# Verify L2ARC is populated after import +typeset l2_size_after=$(kstat arcstats.l2_size) +log_note "L2ARC size after import: $((l2_size_after / 1024 / 1024))MB" +if [[ $l2_size_after -eq 0 ]]; then + log_fail "L2ARC not populated after import" +fi + +# Staged fills again after import +log_must dd if=/dev/urandom of=/$TESTPOOL/file3a bs=1M count=$((fill_mb/3)) +log_must sleep 5 +log_must dd if=/dev/urandom of=/$TESTPOOL/file3b bs=1M count=$((fill_mb/3)) +log_must sleep 5 +log_must dd if=/dev/urandom of=/$TESTPOOL/file3c bs=1M count=$((fill_mb/3)) +log_must sleep 5 + +# Verify L2ARC is still populated after refill +l2_size=$(kstat arcstats.l2_size) +log_note "L2ARC size after refill: $((l2_size / 1024 / 1024))MB" + +# Measure DWPD-limited writes after import +baseline2=$(kstat arcstats.l2_write_bytes) +log_note "Baseline after import: ${baseline2}" +dd if=/dev/urandom of=/$TESTPOOL/file4 bs=1M count=2000 >/dev/null 2>&1 & +dd_pid=$! +log_must sleep $test_time +kill $dd_pid 2>/dev/null +wait $dd_pid 2>/dev/null +log_must sleep 2 +end2=$(kstat arcstats.l2_write_bytes) +typeset writes_after=$((end2 - baseline2)) + +log_note "Writes after import: $((writes_after / 1024))KB" + +# Verify rate limiting persists after import +if [[ $writes_after -eq 0 ]]; then + log_fail "No writes after import - rate limiting may be broken" +fi + +log_must zpool destroy $TESTPOOL + +log_pass "L2ARC DWPD rate limiting works after pool export/import." diff --git a/tests/zfs-tests/tests/functional/l2arc/l2arc_multidev_scaling_pos.ksh b/tests/zfs-tests/tests/functional/l2arc/l2arc_multidev_scaling_pos.ksh new file mode 100755 index 000000000..8e8c9078c --- /dev/null +++ b/tests/zfs-tests/tests/functional/l2arc/l2arc_multidev_scaling_pos.ksh @@ -0,0 +1,162 @@ +#!/bin/ksh -p +# SPDX-License-Identifier: CDDL-1.0 +# +# CDDL HEADER START +# +# 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. +# +# CDDL HEADER END +# + +# +# Copyright (c) 2024. All rights reserved. +# + +. $STF_SUITE/include/libtest.shlib +. $STF_SUITE/tests/functional/l2arc/l2arc.cfg + +# +# DESCRIPTION: +# L2ARC parallel writes scale with number of cache devices. +# +# STRATEGY: +# 1. Configure L2ARC write rate to 16MB/s per device. +# 2. Disable DWPD rate limiting to test pure parallel throughput. +# 3. Create pool with single 2100MB cache device. +# 4. Generate continuous writes, wait for L2ARC activity, measure over 25s. +# 5. Verify single-device throughput ~400MB (16MB/s × 25s). +# 6. Recreate pool with dual 2100MB cache devices. +# 7. Generate continuous writes, wait for L2ARC activity, measure over 25s. +# 8. Verify dual-device throughput ~800MB (2×16MB/s × 25s). +# + +verify_runnable "global" + +log_assert "L2ARC parallel writes scale with number of cache devices." + +function cleanup +{ + if poolexists $TESTPOOL ; then + destroy_pool $TESTPOOL + fi + + restore_tunable L2ARC_WRITE_MAX + restore_tunable L2ARC_NOPREFETCH + restore_tunable L2ARC_DWPD_LIMIT + restore_tunable ARC_MIN + restore_tunable ARC_MAX +} +log_onexit cleanup + +# Save original tunables +save_tunable L2ARC_WRITE_MAX +save_tunable L2ARC_NOPREFETCH +save_tunable L2ARC_DWPD_LIMIT +save_tunable ARC_MIN +save_tunable ARC_MAX + +# Test parameters +typeset cache_sz=1000 +typeset fill_mb=2500 # 2.5GB initial data +typeset test_time=12 # Measurement window: 16MB/s × 12s = ~200MB per device + +# Disable DWPD to test pure parallel throughput +log_must set_tunable32 L2ARC_DWPD_LIMIT 0 + +# Set L2ARC_WRITE_MAX to 16MB/s to test parallel scaling +log_must set_tunable32 L2ARC_WRITE_MAX $((16 * 1024 * 1024)) +log_must set_tunable32 L2ARC_NOPREFETCH 0 + +# Configure arc_max so L2ARC >= arc_c_max * 2 threshold for persistent markers +log_must set_tunable64 ARC_MAX $((400 * 1024 * 1024)) +log_must set_tunable64 ARC_MIN $((200 * 1024 * 1024)) + +# Single device test +log_must truncate -s 4G $VDEV +log_must truncate -s ${cache_sz}M $VDEV_CACHE +log_must zpool create -f $TESTPOOL $VDEV cache $VDEV_CACHE + +# Generate data in background +dd if=/dev/urandom of=/$TESTPOOL/file1 bs=1M count=$fill_mb & +typeset dd_pid=$! + +# Wait for L2ARC to start writing +typeset l2_size=0 +for i in {1..30}; do + l2_size=$(kstat arcstats.l2_size) + [[ $l2_size -gt 0 ]] && break + sleep 1 +done +if [[ $l2_size -eq 0 ]]; then + kill $dd_pid 2>/dev/null + log_fail "L2ARC did not start writing (single device)" +fi + +# Measure single-device write throughput +typeset start=$(kstat arcstats.l2_write_bytes) +log_must sleep $test_time +typeset end=$(kstat arcstats.l2_write_bytes) +kill $dd_pid 2>/dev/null +wait $dd_pid 2>/dev/null +typeset single_writes=$((end - start)) + +# expected = 16MB/s * 1 device * 25s = 400MB +typeset single_expected=$((16 * 1024 * 1024 * test_time)) +log_note "Single-device writes: $((single_writes / 1024 / 1024))MB (expected ~$((single_expected / 1024 / 1024))MB)" + +# Dual device test +log_must zpool destroy $TESTPOOL +log_must truncate -s ${cache_sz}M $VDEV_CACHE +log_must truncate -s ${cache_sz}M $VDEV_CACHE2 + +log_must zpool create -f $TESTPOOL $VDEV cache $VDEV_CACHE $VDEV_CACHE2 + +# Generate data in background +dd if=/dev/urandom of=/$TESTPOOL/file2 bs=1M count=$fill_mb & +dd_pid=$! + +# Wait for L2ARC to start writing +l2_size=0 +for i in {1..30}; do + l2_size=$(kstat arcstats.l2_size) + [[ $l2_size -gt 0 ]] && break + sleep 1 +done +if [[ $l2_size -eq 0 ]]; then + kill $dd_pid 2>/dev/null + log_fail "L2ARC did not start writing (dual device)" +fi + +# Measure parallel write throughput (2 feed threads active) +start=$(kstat arcstats.l2_write_bytes) +log_must sleep $test_time +end=$(kstat arcstats.l2_write_bytes) +kill $dd_pid 2>/dev/null +wait $dd_pid 2>/dev/null +typeset dual_writes=$((end - start)) + +# expected = 16MB/s * 2 devices * 25s = 800MB +typeset dual_expected=$((16 * 1024 * 1024 * 2 * test_time)) +log_note "Dual-device writes: $((dual_writes / 1024 / 1024))MB (expected ~$((dual_expected / 1024 / 1024))MB)" + +# Verify writes are within expected range (80-150%) +typeset single_min=$((single_expected * 80 / 100)) +typeset dual_min=$((dual_expected * 80 / 100)) + +if [[ $single_writes -lt $single_min ]]; then + log_fail "Single-device writes $((single_writes / 1024 / 1024))MB below minimum $((single_min / 1024 / 1024))MB" +fi +if [[ $dual_writes -lt $dual_min ]]; then + log_fail "Dual-device writes $((dual_writes / 1024 / 1024))MB below minimum $((dual_min / 1024 / 1024))MB" +fi + +log_must zpool destroy $TESTPOOL + +log_pass "L2ARC parallel writes scale with number of cache devices." diff --git a/tests/zfs-tests/tests/functional/l2arc/l2arc_multidev_throughput_pos.ksh b/tests/zfs-tests/tests/functional/l2arc/l2arc_multidev_throughput_pos.ksh new file mode 100755 index 000000000..abdaeb41f --- /dev/null +++ b/tests/zfs-tests/tests/functional/l2arc/l2arc_multidev_throughput_pos.ksh @@ -0,0 +1,133 @@ +#!/bin/ksh -p +# SPDX-License-Identifier: CDDL-1.0 +# +# CDDL HEADER START +# +# 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. +# +# CDDL HEADER END +# + +# +# Copyright (c) 2024. All rights reserved. +# + +. $STF_SUITE/include/libtest.shlib +. $STF_SUITE/tests/functional/l2arc/l2arc.cfg + +# +# DESCRIPTION: +# L2ARC parallel writes scale with number of cache devices. +# +# STRATEGY: +# 1. Disable DWPD rate limiting. +# 2. Create pool with 2 cache devices. +# 3. Write data and measure L2ARC throughput. +# 4. Verify throughput scales with device count (~16MB/s per device). +# + +verify_runnable "global" + +log_assert "L2ARC parallel writes scale with number of cache devices." + +function cleanup +{ + if poolexists $TESTPOOL ; then + destroy_pool $TESTPOOL + fi + + restore_tunable L2ARC_WRITE_MAX + restore_tunable L2ARC_NOPREFETCH + restore_tunable L2ARC_DWPD_LIMIT + restore_tunable ARC_MIN + restore_tunable ARC_MAX +} +log_onexit cleanup + +# Save original tunables +save_tunable L2ARC_WRITE_MAX +save_tunable L2ARC_NOPREFETCH +save_tunable L2ARC_DWPD_LIMIT +save_tunable ARC_MIN +save_tunable ARC_MAX + +# Test parameters +typeset num_devs=2 +typeset cache_sz=1000 # 2000MB total > 1900MB (arc_max*2) threshold +typeset test_time=10 +typeset fill_mb=1500 +typeset expected_rate=$((32 * 1024 * 1024)) # 32 MB/s per device + +# Disable DWPD rate limiting +log_must set_tunable32 L2ARC_DWPD_LIMIT 0 + +# Set L2ARC_WRITE_MAX to 32MB/s per device (64MB/s total with 2 devices) +log_must set_tunable32 L2ARC_WRITE_MAX $expected_rate +log_must set_tunable32 L2ARC_NOPREFETCH 0 + +# Configure arc_max large enough to feed L2ARC +log_must set_tunable64 ARC_MAX $((950 * 1024 * 1024)) +log_must set_tunable64 ARC_MIN $((512 * 1024 * 1024)) + +# Create cache devices (using letters e-f to follow cfg naming convention) +typeset cache_devs="" +for letter in e f; do + typeset dev="$VDIR/$letter" + log_must truncate -s ${cache_sz}M $dev + cache_devs="$cache_devs $dev" +done + +log_must truncate -s 2G $VDEV +log_must zpool create -f $TESTPOOL $VDEV cache $cache_devs + +# Generate data in background +dd if=/dev/urandom of=/$TESTPOOL/file1 bs=1M count=$fill_mb & +typeset dd_pid=$! + +# Wait for L2ARC to start writing +typeset l2_size=0 +for i in {1..30}; do + l2_size=$(kstat arcstats.l2_size) + [[ $l2_size -gt 0 ]] && break + sleep 1 +done +if [[ $l2_size -eq 0 ]]; then + kill $dd_pid 2>/dev/null + log_fail "L2ARC did not start writing" +fi + +# Measure L2ARC throughput over test window +typeset start=$(kstat arcstats.l2_write_bytes) +log_must sleep $test_time +typeset end=$(kstat arcstats.l2_write_bytes) +kill $dd_pid 2>/dev/null +wait $dd_pid 2>/dev/null + +typeset bytes=$((end - start)) +typeset bytes_mb=$((bytes / 1024 / 1024)) +# expected = 32MB/s * 2 devices * 10 seconds = 640MB +typeset expected=$((expected_rate * num_devs * test_time)) +typeset expected_mb=$((expected / 1024 / 1024)) + +log_note "L2ARC writes: ${bytes_mb}MB (expected ~${expected_mb}MB)" + +# Verify writes are within expected range (75-150%) +typeset min_bytes=$((expected * 75 / 100)) +typeset max_bytes=$((expected * 150 / 100)) +if [[ $bytes -lt $min_bytes ]]; then + log_fail "Writes ${bytes_mb}MB below minimum $((min_bytes/1024/1024))MB" +fi +if [[ $bytes -gt $max_bytes ]]; then + log_fail "Writes ${bytes_mb}MB above maximum $((max_bytes/1024/1024))MB" +fi + +log_must zpool destroy $TESTPOOL + +log_pass "L2ARC parallel writes scale with number of cache devices." diff --git a/tests/zfs-tests/tests/functional/trim/trim_l2arc.ksh b/tests/zfs-tests/tests/functional/trim/trim_l2arc.ksh index 9b0a48655..52c0fa760 100755 --- a/tests/zfs-tests/tests/functional/trim/trim_l2arc.ksh +++ b/tests/zfs-tests/tests/functional/trim/trim_l2arc.ksh @@ -50,16 +50,19 @@ function cleanup log_must rm -f $VDEVS log_must set_tunable32 L2ARC_TRIM_AHEAD $l2arc_trimahead log_must set_tunable32 L2ARC_WRITE_MAX $l2arc_writemax + log_must set_tunable32 L2ARC_DWPD_LIMIT $l2arc_dwpdlimit } log_onexit cleanup # The cache device $TRIM_VDEV2 has to be small enough, so that -# dev->l2ad_hand loops around and dev->l2ad_first=0. Otherwise +# dev->l2ad_hand loops around and dev->l2ad_first=0. Otherwise # l2arc_evict() exits before evicting/trimming. typeset l2arc_trimahead=$(get_tunable L2ARC_TRIM_AHEAD) typeset l2arc_writemax=$(get_tunable L2ARC_WRITE_MAX) +typeset l2arc_dwpdlimit=$(get_tunable L2ARC_DWPD_LIMIT) log_must set_tunable32 L2ARC_TRIM_AHEAD 1 log_must set_tunable32 L2ARC_WRITE_MAX $((64 * 1024 * 1024)) +log_must set_tunable32 L2ARC_DWPD_LIMIT 0 VDEVS="$TRIM_VDEV1 $TRIM_VDEV2" log_must truncate -s $((MINVDEVSIZE)) $TRIM_VDEV2 log_must truncate -s $((4 * MINVDEVSIZE)) $TRIM_VDEV1