Add zpool properties for allocation class space

The existing zpool properties accounting pool space (size, allocated,
fragmentation, expandsize, free, capacity) are based on the normal
metaslab class or are cumulative properties of several classes combined.

Add properties reporting the space accounting metrics for each metaslab
class individually.

Also introduce pool-wide AVAIL, USABLE, and USED properties reporting
values corresponding to FREE, SIZE, and ALLOC deflated for raidz.

Update ZTS to recognize the new properties and validate reported values.

While in zpool_get_parsable.cfg, add "fragmentation" to the list of
parsable properties.

Sponsored-by: Klara, Inc.
Reviewed-by: Alexander Motin <alexander.motin@TrueNAS.com>
Reviewed-by: Brian Behlendorf <behlendorf1@llnl.gov>
Reviewed-by: Ameer Hamza <ahamza@ixsystems.com>
Signed-off-by: Ryan Moeller <ryan.moeller@klarasystems.com>
Cloes #18238
This commit is contained in:
Ryan Moeller
2026-02-18 11:54:13 -05:00
committed by Brian Behlendorf
parent 6ba3f915d0
commit ac0fd40c8c
14 changed files with 1047 additions and 36 deletions
+2 -2
View File
@@ -456,8 +456,8 @@ tags = ['functional', 'cli_root', 'zpool_export']
[tests/functional/cli_root/zpool_get]
tests = ['zpool_get_001_pos', 'zpool_get_002_pos', 'zpool_get_003_pos',
'zpool_get_004_neg', 'zpool_get_005_pos', 'vdev_get_001_pos',
'vdev_get_all']
'zpool_get_004_neg', 'zpool_get_005_pos', 'zpool_get_006_pos',
'vdev_get_001_pos', 'vdev_get_all']
tags = ['functional', 'cli_root', 'zpool_get']
[tests/functional/cli_root/zpool_history]
+1
View File
@@ -1132,6 +1132,7 @@ nobase_dist_datadir_zfs_tests_tests_SCRIPTS += \
functional/cli_root/zpool_get/zpool_get_003_pos.ksh \
functional/cli_root/zpool_get/zpool_get_004_neg.ksh \
functional/cli_root/zpool_get/zpool_get_005_pos.ksh \
functional/cli_root/zpool_get/zpool_get_006_pos.ksh \
functional/cli_root/zpool_history/cleanup.ksh \
functional/cli_root/zpool_history/setup.ksh \
functional/cli_root/zpool_history/zpool_history_001_neg.ksh \
@@ -67,6 +67,63 @@ typeset -a properties=(
"last_scrubbed_txg"
"dedupused"
"dedupsaved"
"available"
"usable"
"used"
"class_normal_size"
"class_normal_capacity"
"class_normal_free"
"class_normal_allocated"
"class_normal_available"
"class_normal_usable"
"class_normal_used"
"class_normal_expandsize"
"class_normal_fragmentation"
"class_special_size"
"class_special_capacity"
"class_special_free"
"class_special_allocated"
"class_special_available"
"class_special_usable"
"class_special_used"
"class_special_expandsize"
"class_special_fragmentation"
"class_dedup_size"
"class_dedup_capacity"
"class_dedup_free"
"class_dedup_allocated"
"class_dedup_available"
"class_dedup_usable"
"class_dedup_used"
"class_dedup_expandsize"
"class_dedup_fragmentation"
"class_log_size"
"class_log_capacity"
"class_log_free"
"class_log_allocated"
"class_log_available"
"class_log_usable"
"class_log_used"
"class_log_expandsize"
"class_log_fragmentation"
"class_elog_size"
"class_elog_capacity"
"class_elog_free"
"class_elog_allocated"
"class_elog_available"
"class_elog_usable"
"class_elog_used"
"class_elog_expandsize"
"class_elog_fragmentation"
"class_special_elog_size"
"class_special_elog_capacity"
"class_special_elog_free"
"class_special_elog_allocated"
"class_special_elog_available"
"class_special_elog_usable"
"class_special_elog_used"
"class_special_elog_expandsize"
"class_special_elog_fragmentation"
"feature@async_destroy"
"feature@empty_bpobj"
"feature@lz4_compress"
@@ -47,6 +47,8 @@ if ! is_global_zone ; then
fi
typeset -i i=0
typeset class="@(normal|special|dedup|log|elog|special_elog)"
typeset optclass="@(special|dedup|log|elog|special_elog)"
while [[ $i -lt "${#properties[@]}" ]]; do
log_note "Checking for parsable ${properties[$i]} property"
@@ -61,10 +63,14 @@ while [[ $i -lt "${#properties[@]}" ]]; do
# All properties must be positive integers in order to be
# parsable (i.e. a return code of 0 or 1 from expr above).
# The only exception is "expandsize", which may be "-".
if [[ ! ($? -eq 0 || $? -eq 1 || \
("${properties[$i]}" = "expandsize" && "$v" = "-")) ]]; then
log_fail "${properties[$i]} is not parsable"
# The only exceptions are "expandsize", "class_<class>_expandsize",
# and "class_<optclass>_fragmentation", which may be "-".
if [[ ! ($? -eq 0 || $? -eq 1) ]]; then
case "${properties[$i]}" in
?(class_${class}_)expandsize) ;&
class_${optclass}_fragmentation) ;;
*) log_fail "${properties[$i]} is not parsable"
esac
fi
i=$(( $i + 1 ))
@@ -0,0 +1,416 @@
#!/bin/ksh -p
# SPDX-License-Identifier: CDDL-1.0
#
# 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) 2026 Klara, Inc.
#
. $STF_SUITE/include/libtest.shlib
verify_runnable "global"
#
# DESCRIPTION:
#
# Several zpool properties exist to expose metaslab allocation class space
# accounting.
#
# STRATEGY:
# 1. Create a pool with raidz (to validate expansion and deflation metrics).
# 2. For each allocation class:
# - Add any required vdevs for this allocation class.
# - Prepare a dataset configured to utilize this allocation class.
# - Validate metrics reported by pool properties for allocation classes.
# 3. For the whole pool, confirm that AVAIL, USABLE and USED report reasonable
# values.
#
bs=128K
count=100
function writefile
{
dd if=/dev/urandom of=$1 bs=$bs count=$count 2>/dev/null
}
nfiles=5
function writefiles # datadir [nfiles]
{
typeset datadir=$1
typeset -i n=${2:-$nfiles}
for i in {1..$n}; do
log_must writefile $TESTDIR/$datadir/file$i
done
}
pool=$TESTPOOL1
function get_class_prop
{
get_pool_prop "class_${1}_${2}" $pool
}
# Wrapper for test to give more context to logs
function check
{
shift 2 # class prop (test args)
test "$@"
}
function check_raidz_used # [mincap=1]
{
typeset -i mincap=${1:-1}
log_must check $class size $size -gt 0
log_must check $class capacity $cap -ge $mincap -a $cap -le 100
log_must check $class free $free -gt 0 -a $free -lt $size
log_must check $class allocated $alloc -gt 0 -a $alloc -lt $size
log_must check $class available $avail -gt 0 -a $avail -lt $free
log_must check $class usable $usable -gt 0 -a $usable -lt $size
log_must check $class used $used -gt 0 -a $used -lt $alloc
log_must check $class expandsize $expandsz = "-"
log_must check $class fragmentation $frag -lt 50
}
function check_raidz_unused
{
log_must check $class size $size -gt 0
log_must check $class capacity $cap -eq 0
log_must check $class free $free -eq $size
log_must check $class allocated $alloc -eq 0
log_must check $class available $avail -gt 0 -a $avail -lt $free
log_must check $class usable $usable -gt 0 -a $usable -lt $size
log_must check $class used $used -eq 0
log_must check $class expandsize $expandsz = "-"
log_must check $class fragmentation $frag -eq 0
}
function check_nonraidz_used # [mincap=1]
{
typeset -i mincap=${1:-1}
log_must check $class size $size -gt 0
log_must check $class capacity $cap -ge $mincap -a $cap -le 100
log_must check $class free $free -gt 0 -a $free -lt $size
log_must check $class allocated $alloc -gt 0 -a $alloc -lt $size
log_must check $class available $avail -eq $free
log_must check $class usable $usable -eq $size
log_must check $class used $used -eq $alloc
log_must check $class expandsize $expandsz = "-"
log_must check $class fragmentation $frag -lt 50
}
function check_nonraidz_unused
{
log_must check $class size $size -gt 0
log_must check $class capacity $cap -eq 0
log_must check $class free $free -eq $size
log_must check $class allocated $alloc -eq 0
log_must check $class available $avail -eq $free
log_must check $class usable $usable -eq $size
log_must check $class used $used -eq $alloc
log_must check $class expandsize $expandsz = "-"
log_must check $class fragmentation $frag -eq 0
}
# Log capacity tends to be >0% but <1% in these tests, so gets reported as 0.
# Let that slide and rely on allocated/free checks for sanity, rather than
# trying to tweak txg sync parameters to widen the race window.
function check_raidz_log_used
{
check_raidz_used 0
}
function check_nonraidz_log_used
{
check_nonraidz_used 0
}
function check_unavailable
{
log_must check $class size $size -eq 0
log_must check $class capacity $cap -eq 0
log_must check $class free $free -eq 0
log_must check $class allocated $alloc -eq 0
log_must check $class available $avail -eq 0
log_must check $class usable $usable -eq 0
log_must check $class used $used -eq 0
log_must check $class expandsize $expandsz = "-"
log_must check $class fragmentation $frag = "-"
}
typeset -a classes=("normal" "special" "dedup" "log" "elog" "special_elog")
normal_vdevs=$(seq -f $TEST_BASE_DIR/normal-vdev-%g 3)
normal_vdev_size=$((1 << 30)) # 1 GiB
special_vdevs=$(seq -f $TEST_BASE_DIR/special-vdev-%g 3)
special_vdev_size=$((512 << 20)) # 512 MiB
# Use a mirror for dedup to test expandsize.
dedup_vdevs=$(seq -f $TEST_BASE_DIR/dedup-vdev-%g 2)
dedup_vdev_size=$((256 << 20)) # 256 MiB
# The log class can't be raided or expanded, so we only need one vdev.
log_vdev="$TEST_BASE_DIR/log-vdev"
log_vdev_size=$((128 << 20)) # 128 MiB
embedded_slog_min_ms=$(get_tunable EMBEDDED_SLOG_MIN_MS)
function cleanup
{
zpool destroy -f $pool
rm -f $normal_vdevs $normal_expand_vdev
rm -f $special_vdevs $special_expand_vdev
rm -f $dedup_vdevs $dedup_expand_vdev
rm -f $log_vdev
set_tunable32 EMBEDDED_SLOG_MIN_MS $embedded_slog_min_ms
}
log_onexit cleanup
log_assert "zpool allocation class properties report metrics correctly"
# Lower the threshold for provisioning embedded log metaslabs on small vdevs.
log_must set_tunable32 EMBEDDED_SLOG_MIN_MS 8
log_must truncate -s $normal_vdev_size $normal_vdevs
log_must zpool create $pool \
raidz $normal_vdevs
log_must zfs set mountpoint=$TESTDIR $pool
log_note "Normal Class"
log_must zfs create \
$pool/normal
writefiles normal
sync_pool $pool
for class in "${classes[@]}"; do
typeset -il size=$(get_class_prop $class size)
typeset -i cap=$(get_class_prop $class capacity)
typeset -il free=$(get_class_prop $class free)
typeset -il alloc=$(get_class_prop $class allocated)
typeset -il avail=$(get_class_prop $class available)
typeset -il usable=$(get_class_prop $class usable)
typeset -il used=$(get_class_prop $class used)
typeset expandsz=$(get_class_prop $class expandsize)
typeset frag=$(get_class_prop $class fragmentation)
case $class in
normal)
check_raidz_used
;;
elog)
check_raidz_unused
;;
*)
check_unavailable
;;
esac
done
log_note "Embedded Log Class"
log_must zfs create \
-o sync=always \
$pool/elog
writefiles elog
for class in "${classes[@]}"; do
typeset -il size=$(get_class_prop $class size)
typeset -i cap=$(get_class_prop $class capacity)
typeset -il free=$(get_class_prop $class free)
typeset -il alloc=$(get_class_prop $class allocated)
typeset -il avail=$(get_class_prop $class available)
typeset -il usable=$(get_class_prop $class usable)
typeset -il used=$(get_class_prop $class used)
typeset expandsz=$(get_class_prop $class expandsize)
typeset frag=$(get_class_prop $class fragmentation)
case $class in
normal)
check_raidz_used
;;
elog)
check_raidz_log_used
;;
*)
check_unavailable
;;
esac
done
log_note "Special Class"
log_must truncate -s $special_vdev_size $special_vdevs
log_must zpool add $pool \
special raidz $special_vdevs
log_must zfs create \
-o recordsize=32K -o special_small_blocks=32K \
$pool/special
writefiles special
sync_pool $pool
for class in "${classes[@]}"; do
typeset -il size=$(get_class_prop $class size)
typeset -i cap=$(get_class_prop $class capacity)
typeset -il free=$(get_class_prop $class free)
typeset -il alloc=$(get_class_prop $class allocated)
typeset -il avail=$(get_class_prop $class available)
typeset -il usable=$(get_class_prop $class usable)
typeset -il used=$(get_class_prop $class used)
typeset expandsz=$(get_class_prop $class expandsize)
typeset frag=$(get_class_prop $class fragmentation)
case $class in
normal|special)
check_raidz_used
;;
elog)
check_raidz_log_used
;;
special_elog)
check_raidz_unused
;;
*)
check_unavailable
;;
esac
done
log_note "Special Embedded Log Class"
log_must zfs create \
-o recordsize=32K -o special_small_blocks=32K \
-o sync=always \
$pool/special_elog
writefiles special_elog
for class in "${classes[@]}"; do
typeset -il size=$(get_class_prop $class size)
typeset -i cap=$(get_class_prop $class capacity)
typeset -il free=$(get_class_prop $class free)
typeset -il alloc=$(get_class_prop $class allocated)
typeset -il avail=$(get_class_prop $class available)
typeset -il usable=$(get_class_prop $class usable)
typeset -il used=$(get_class_prop $class used)
typeset expandsz=$(get_class_prop $class expandsize)
typeset frag=$(get_class_prop $class fragmentation)
case $class in
normal|special)
check_raidz_used
;;
elog|special_elog)
check_raidz_log_used
;;
*)
check_unavailable
;;
esac
done
log_note "Log Class"
log_must truncate -s $log_vdev_size $log_vdev
log_must zpool add $pool \
log $log_vdev
log_must zfs create \
-o sync=always \
$pool/log
writefiles log
for class in "${classes[@]}"; do
typeset -il size=$(get_class_prop $class size)
typeset -i cap=$(get_class_prop $class capacity)
typeset -il free=$(get_class_prop $class free)
typeset -il alloc=$(get_class_prop $class allocated)
typeset -il avail=$(get_class_prop $class available)
typeset -il usable=$(get_class_prop $class usable)
typeset -il used=$(get_class_prop $class used)
typeset expandsz=$(get_class_prop $class expandsize)
typeset frag=$(get_class_prop $class fragmentation)
case $class in
normal|special)
check_raidz_used
;;
elog|special_elog)
check_raidz_log_used
;;
log)
check_nonraidz_log_used
;;
*)
check_unavailable
;;
esac
done
log_note "Dedup Class"
log_must truncate -s $dedup_vdev_size $dedup_vdevs
log_must zpool add $pool \
dedup mirror $dedup_vdevs
log_must zfs create \
-o dedup=on -o recordsize=4k \
$pool/dedup
writefiles dedup $((3 * nfiles))
sync_pool $pool
for class in "${classes[@]}"; do
typeset -il size=$(get_class_prop $class size)
typeset -i cap=$(get_class_prop $class capacity)
typeset -il free=$(get_class_prop $class free)
typeset -il alloc=$(get_class_prop $class allocated)
typeset -il avail=$(get_class_prop $class available)
typeset -il usable=$(get_class_prop $class usable)
typeset -il used=$(get_class_prop $class used)
typeset expandsz=$(get_class_prop $class expandsize)
typeset frag=$(get_class_prop $class fragmentation)
case $class in
normal|special)
check_raidz_used
;;
elog|special_elog)
check_raidz_log_used
;;
log)
check_nonraidz_log_used
;;
dedup)
check_nonraidz_used
;;
*)
log_fail "unhandled class: $class"
;;
esac
done
# Expansion
typeset -il delta=$((32 << 20)) # 32 MiB
log_must truncate -s $((dedup_vdev_size + delta)) $dedup_vdevs
typeset -a vdevs=($dedup_vdevs)
log_must zpool online -e $pool ${vdevs[0]}
typeset -il size=$(get_class_prop dedup size)
typeset -il expandsz=$(get_class_prop dedup expandsize)
log_must test $expandsz -eq $delta
# Pool-wide AVAIL/USABLE/USED
typeset -il free=$(get_pool_prop free $pool)
typeset -il avail=$(get_pool_prop available $pool)
log_must test $avail -gt 0 -a $avail -lt $free
typeset -il size=$(get_pool_prop size $pool)
typeset -il usable=$(get_pool_prop usable $pool)
log_must test $usable -gt 0 -a $usable -lt $size
typeset -il alloc=$(get_pool_prop alloc $pool)
typeset -il used=$(get_pool_prop used $pool)
log_must test $used -gt 0 -a $used -lt $alloc
cleanup
@@ -29,6 +29,75 @@
# Copyright (c) 2013, 2014 by Delphix. All rights reserved.
#
# Set the expected properties of zpool
typeset -a properties=("allocated" "bcloneused" "bclonesaved" "capacity"
"dedupused" "dedupsaved" "expandsize" "free" "freeing" "leaked" "size")
# Set the expected parsable properties of zpool
typeset -a properties=(
"size"
"capacity"
"free"
"allocated"
"expandsize"
"freeing"
"fragmentation"
"leaked"
"bcloneused"
"bclonesaved"
"dedupused"
"dedupsaved"
"available"
"usable"
"used"
"class_normal_size"
"class_normal_capacity"
"class_normal_free"
"class_normal_allocated"
"class_normal_available"
"class_normal_usable"
"class_normal_used"
"class_normal_expandsize"
"class_normal_fragmentation"
"class_special_size"
"class_special_capacity"
"class_special_free"
"class_special_allocated"
"class_special_available"
"class_special_usable"
"class_special_used"
"class_special_expandsize"
"class_special_fragmentation"
"class_dedup_size"
"class_dedup_capacity"
"class_dedup_free"
"class_dedup_allocated"
"class_dedup_available"
"class_dedup_usable"
"class_dedup_used"
"class_dedup_expandsize"
"class_dedup_fragmentation"
"class_log_size"
"class_log_capacity"
"class_log_free"
"class_log_allocated"
"class_log_available"
"class_log_usable"
"class_log_used"
"class_log_expandsize"
"class_log_fragmentation"
"class_elog_size"
"class_elog_capacity"
"class_elog_free"
"class_elog_allocated"
"class_elog_available"
"class_elog_usable"
"class_elog_used"
"class_elog_expandsize"
"class_elog_fragmentation"
"class_special_elog_size"
"class_special_elog_capacity"
"class_special_elog_free"
"class_special_elog_allocated"
"class_special_elog_available"
"class_special_elog_usable"
"class_special_elog_used"
"class_special_elog_expandsize"
"class_special_elog_fragmentation"
)