|
| 1 | +#!/bin/sh |
| 2 | +set -eu |
| 3 | + |
| 4 | +# ZLFS concurrent-writers stress test. |
| 5 | +# |
| 6 | +# Runs several writers in parallel, each repeatedly rewriting and |
| 7 | +# syncing its OWN file (mixed sizes spanning direct, single- and |
| 8 | +# double-indirect ranges; two also splice mid-file, exercising the |
| 9 | +# read-modify-write overlay), while a background loop churns junk to |
| 10 | +# force garbage collection and zone compaction at the same time. The |
| 11 | +# concurrent sync(2)s contend in the commit's per-inode trylock/retry |
| 12 | +# path and the compactor runs from the sync path under live load. |
| 13 | +# |
| 14 | +# Each writer's file is compared byte-for-byte against a fixed template |
| 15 | +# after the run and again after a remount; a keeper file written up |
| 16 | +# front guards against the cleaner eating live data under contention. |
| 17 | +# |
| 18 | +# Sized for the ~8 GB QEMU ZNS test device; on a much smaller disk the |
| 19 | +# concurrent churn can win the space race and a writer may see a |
| 20 | +# transient ENOSPC before GC catches up. |
| 21 | +# |
| 22 | +# WARNING: this reformats the given disk with newfs_zlfs. |
| 23 | + |
| 24 | +usage() |
| 25 | +{ |
| 26 | + echo "usage: $0 disk [mount-point [writers [iters]]]" >&2 |
| 27 | + echo "example: $0 sd1c /mnt/zlfs 8 40" >&2 |
| 28 | + echo "warning: this reformats the disk with newfs_zlfs" >&2 |
| 29 | + exit 1 |
| 30 | +} |
| 31 | + |
| 32 | +die() |
| 33 | +{ |
| 34 | + echo "$0: $*" >&2 |
| 35 | + exit 1 |
| 36 | +} |
| 37 | + |
| 38 | +section() |
| 39 | +{ |
| 40 | + echo |
| 41 | + echo "== $* ==" |
| 42 | +} |
| 43 | + |
| 44 | +disk=${1-} |
| 45 | +mnt=${2-/mnt/zlfs} |
| 46 | +writers=${3-8} |
| 47 | +iters=${4-40} |
| 48 | + |
| 49 | +[ -n "$disk" ] && [ $# -le 4 ] || usage |
| 50 | +[ "$(id -u)" -eq 0 ] || die "must run as root" |
| 51 | + |
| 52 | +dev=/dev/$disk |
| 53 | +tdir=/tmp/zlfs-par.$$ |
| 54 | +mkdir -p "$tdir" |
| 55 | +trap 'rm -rf "$tdir"' EXIT |
| 56 | + |
| 57 | +# Per-writer template block counts (4 KB blocks): a spread across the |
| 58 | +# direct (<12), single-indirect (<524) and double-indirect ranges, so |
| 59 | +# the concurrent commits build and rewrite indirect trees too. |
| 60 | +sizes="4 20 200 600 1500 40 300 4096" |
| 61 | + |
| 62 | +section "newfs + mount ($writers writers, $iters iters each)" |
| 63 | +umount "$mnt" 2>/dev/null || true |
| 64 | +newfs_zlfs "$disk" |
| 65 | +mkdir -p "$mnt" |
| 66 | +mount_zlfs "$dev" "$mnt" |
| 67 | + |
| 68 | +section "keeper file (must survive the whole run)" |
| 69 | +dd if=/dev/random of="$mnt/keeper" bs=4k count=100 2>/dev/null |
| 70 | +sync |
| 71 | +kcsum=$(cksum < "$mnt/keeper") |
| 72 | + |
| 73 | +section "build $writers templates" |
| 74 | +w=0 |
| 75 | +while [ "$w" -lt "$writers" ]; do |
| 76 | + cnt=$(echo "$sizes" | cut -d' ' -f$((w + 1))) |
| 77 | + [ -n "$cnt" ] || cnt=64 |
| 78 | + dd if=/dev/random of="$tdir/t$w" bs=4k count="$cnt" 2>/dev/null |
| 79 | + w=$((w + 1)) |
| 80 | +done |
| 81 | + |
| 82 | +# A background junk churn to keep GC and compaction busy under load. |
| 83 | +# Create the run flag BEFORE forking so the child cannot test it first |
| 84 | +# and exit immediately (which would silently drop the GC pressure). |
| 85 | +mkdir "$mnt/churn" |
| 86 | +: > "$tdir/churn_on" |
| 87 | +( |
| 88 | + while [ -f "$tdir/churn_on" ]; do |
| 89 | + dd if=/dev/zero of="$mnt/churn/j" bs=4k count=12000 \ |
| 90 | + 2>/dev/null || true |
| 91 | + sync 2>/dev/null || true |
| 92 | + rm -f "$mnt/churn/j" |
| 93 | + sync 2>/dev/null || true |
| 94 | + done |
| 95 | +) & |
| 96 | +churn_pid=$! |
| 97 | + |
| 98 | +section "launch $writers concurrent writers" |
| 99 | +pids="" |
| 100 | +w=0 |
| 101 | +while [ "$w" -lt "$writers" ]; do |
| 102 | + ( |
| 103 | + i=0 |
| 104 | + while [ "$i" -lt "$iters" ]; do |
| 105 | + cp "$tdir/t$w" "$mnt/f$w" |
| 106 | + # Half the writers splice mid-file (RMW), then |
| 107 | + # restore, so the file always ends == template. |
| 108 | + if [ $((w % 2)) -eq 1 ]; then |
| 109 | + printf 'XYZ' | dd of="$mnt/f$w" bs=1 seek=17 \ |
| 110 | + conv=notrunc 2>/dev/null |
| 111 | + cp "$tdir/t$w" "$mnt/f$w" |
| 112 | + fi |
| 113 | + sync |
| 114 | + i=$((i + 1)) |
| 115 | + done |
| 116 | + ) & |
| 117 | + pids="$pids $!" |
| 118 | + w=$((w + 1)) |
| 119 | +done |
| 120 | + |
| 121 | +section "wait for writers" |
| 122 | +fail=0 |
| 123 | +for p in $pids; do |
| 124 | + wait "$p" || fail=1 |
| 125 | +done |
| 126 | +rm -f "$tdir/churn_on" |
| 127 | +wait "$churn_pid" 2>/dev/null || true |
| 128 | +rm -f "$mnt/churn/j" |
| 129 | +rmdir "$mnt/churn" 2>/dev/null || true |
| 130 | +sync |
| 131 | +[ "$fail" -eq 0 ] || die "a writer exited non-zero" |
| 132 | +echo " ok: all $writers writers finished, no ENOSPC/errors" |
| 133 | + |
| 134 | +section "every file matches its template?" |
| 135 | +w=0 |
| 136 | +while [ "$w" -lt "$writers" ]; do |
| 137 | + cmp "$tdir/t$w" "$mnt/f$w" || die "f$w differs from its template" |
| 138 | + w=$((w + 1)) |
| 139 | +done |
| 140 | +[ "$(cksum < "$mnt/keeper")" = "$kcsum" ] || die "keeper changed under load" |
| 141 | +echo " ok: all $writers files and the keeper intact" |
| 142 | + |
| 143 | +section "everything intact after remount?" |
| 144 | +umount "$mnt" |
| 145 | +mount_zlfs "$dev" "$mnt" |
| 146 | +w=0 |
| 147 | +while [ "$w" -lt "$writers" ]; do |
| 148 | + cmp "$tdir/t$w" "$mnt/f$w" || die "f$w differs after remount" |
| 149 | + w=$((w + 1)) |
| 150 | +done |
| 151 | +[ "$(cksum < "$mnt/keeper")" = "$kcsum" ] || die "keeper lost after remount" |
| 152 | +echo " ok: all files and the keeper survive remount" |
| 153 | + |
| 154 | +section "PASS" |
0 commit comments