From 46e088e7be5cafd88dc896fd1440b20c1bf6113b Mon Sep 17 00:00:00 2001 From: Brian Foster Date: Tue, 4 Aug 2015 14:10:48 +1000 Subject: [PATCH] xfs: test inode allocation with fragmented free space XFS dynamic inode allocation has a fundamental limitation in that an inode chunk requires a contiguous extent of a minimum size. Depending on the level of free space fragmentation, inode allocation can fail with ENOSPC where the filesystem might not be near 100% usage. The sparse inodes feature was implemented to provide an inode allocation strategy that maximizes the ability to allocate inodes under free space fragmentation. This test fragments free space and verifies that filesystems that support sparse inode allocation can allocate a minimum percentage of inodes on the fs. Signed-off-by: Brian Foster Reviewed-by: Dave Chinner Signed-off-by: Dave Chinner --- common/rc | 37 +++++++++++++ tests/xfs/076 | 130 ++++++++++++++++++++++++++++++++++++++++++++++ tests/xfs/076.out | 2 + tests/xfs/group | 1 + 4 files changed, 170 insertions(+) create mode 100755 tests/xfs/076 create mode 100644 tests/xfs/076.out diff --git a/common/rc b/common/rc index 610045ea..05623601 100644 --- a/common/rc +++ b/common/rc @@ -1449,6 +1449,18 @@ _require_xfs_sysfs() fi } +# this test requires the xfs sparse inode feature +# +_require_xfs_sparse_inodes() +{ + _scratch_mkfs_xfs_supported -m crc=1 -i sparse > /dev/null 2>&1 \ + || _notrun "mkfs.xfs does not support sparse inodes" + _scratch_mkfs_xfs -m crc=1 -i sparse > /dev/null 2>&1 + _scratch_mount >/dev/null 2>&1 \ + || _notrun "kernel does not support sparse inodes" + umount $SCRATCH_MNT +} + # this test requires that external log/realtime devices are not in use # _require_nonexternal() @@ -2724,6 +2736,18 @@ _get_used_inode() echo $nr_inode } +_get_used_inode_percent() +{ + if [ -z "$1" ]; then + echo "Usage: _get_used_inode_percent " + exit 1 + fi + local pct_inode; + pct_inode=`$DF_PROG -i $1 | tail -1 | awk '{ print $6 }' | \ + sed -e 's/%//'` + echo $pct_inode +} + _get_free_inode() { if [ -z "$1" ]; then @@ -2735,6 +2759,19 @@ _get_free_inode() echo $nr_inode } +# get the available space in bytes +# +_get_available_space() +{ + if [ -z "$1" ]; then + echo "Usage: _get_available_space " + exit 1 + fi + local avail_kb; + avail_kb=`$DF_PROG $1 | tail -n1 | awk '{ print $5 }'` + echo $((avail_kb * 1024)) +} + # get btrfs profile configs being tested # # A set of pre-set profile configs are exported via _btrfs_profile_configs diff --git a/tests/xfs/076 b/tests/xfs/076 new file mode 100755 index 00000000..1ecfca6f --- /dev/null +++ b/tests/xfs/076 @@ -0,0 +1,130 @@ +#!/bin/bash +# FS QA Test No. xfs/076 +# +# Verify that a filesystem with sparse inode support can allocate inodes in the +# event of free space fragmentation. This test is generic in nature but +# primarily relevant to filesystems that implement dynamic inode allocation +# (e.g., XFS). +# +# The test is inspired by inode allocation limitations on XFS when available +# free space is fragmented. XFS allocates inodes 64 at a time and thus requires +# an extent of length that depends on inode size (64 * isize / blksize). +# +# The test creates a small, sparse inode enabled filesystem. It fragments free +# space, allocates inodes to ENOSPC and then verifies that most of the available +# inodes (.i.e., free space) have been consumed. +# +#----------------------------------------------------------------------- +# Copyright (c) 2015 Red Hat, Inc. All Rights Reserved. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it would be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write the Free Software Foundation, +# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +# +#----------------------------------------------------------------------- +# + +seq=`basename $0` +seqres=$RESULT_DIR/$seq +echo "QA output created by $seq" + +here=`pwd` +tmp=/tmp/$$ +status=1 # failure is the default! + +# get standard environment, filters and checks +. ./common/rc +. ./common/filter + +_cleanup() +{ + cd / + umount $SCRATCH_MNT 2>/dev/null + rm -f $tmp.* +} +trap "_cleanup; exit \$status" 0 1 2 3 15 + +_consume_freesp() +{ + file=$1 + + # consume nearly all available space (leave ~1MB) + avail=`_get_available_space $SCRATCH_MNT` + filesizemb=$((avail / 1024 / 1024 - 1)) + $XFS_IO_PROG -fc "falloc 0 ${filesizemb}m" $file +} + +# Allocate inodes in a directory until failure. +_alloc_inodes() +{ + dir=$1 + + i=0 + while [ true ]; do + touch $dir/$i 2>> $seqres.full || break + i=$((i + 1)) + done +} + +# real QA test starts here +_supported_os Linux + +_require_scratch +_require_xfs_io_command "falloc" +_require_xfs_io_command "fpunch" +_require_xfs_sparse_inodes + +rm -f $seqres.full + +_scratch_mkfs "-d size=50m -m crc=1 -i sparse" | + _filter_mkfs > /dev/null 2> $tmp.mkfs +. $tmp.mkfs # for isize + +_scratch_mount + +# Calculate the fs inode chunk size based on the inode size and fixed 64-inode +# record. This value is used as the target level of free space fragmentation +# induced by the test (i.e., max size of free extents). We don't need to go +# smaller than a full chunk because the XFS block allocator tacks on alignment +# requirements to the size of the requested allocation. In other words, a chunk +# sized free chunk is not enough to guarantee a successful chunk sized +# allocation. +CHUNK_SIZE=$((isize * 64)) + +_consume_freesp $SCRATCH_MNT/spc + +# Now that the fs is nearly full, punch holes in every other $CHUNK_SIZE range +# of the space consumer file. This should ensure that most freed extents are not +# contiguous with any others and thus sufficiently fragment free space. After +# each hole punch, allocate as many inodes as possible into the newly freed +# space. Note that we start at the end of the file and work backwards as a +# reverse allocation pattern increases the chances of both left and right sparse +# record merges. +offset=`stat -c "%s" $SCRATCH_MNT/spc` +offset=$((offset - $CHUNK_SIZE * 2)) +while [ $offset -ge 0 ]; do + $XFS_IO_PROG -c "fpunch $offset $CHUNK_SIZE" $SCRATCH_MNT/spc \ + 2>> $seqres.full || _fail "fpunch failed" + + # allocate as many inodes as possible + mkdir -p $SCRATCH_MNT/offset.$offset > /dev/null 2>&1 + _alloc_inodes $SCRATCH_MNT/offset.$offset + + offset=$((offset - $CHUNK_SIZE * 2)) +done + +# check that we've hit at least 95% inode usage +iusepct=`_get_used_inode_percent $SCRATCH_MNT` +_within_tolerance "iusepct" $iusepct 100 5 0 -v + +status=0 +exit diff --git a/tests/xfs/076.out b/tests/xfs/076.out new file mode 100644 index 00000000..f9a0436d --- /dev/null +++ b/tests/xfs/076.out @@ -0,0 +1,2 @@ +QA output created by 076 +iusepct is in range diff --git a/tests/xfs/group b/tests/xfs/group index 612e4f4b..4c20141f 100644 --- a/tests/xfs/group +++ b/tests/xfs/group @@ -73,6 +73,7 @@ 073 copy auto 074 quick auto prealloc rw 075 auto quick mount +076 auto enospc 078 growfs auto quick 080 rw ioctl 081 deprecated # log logprint quota -- 2.30.2