#! /bin/bash
# SPDX-License-Identifier: GPL-2.0
# Copyright (C) 2023 SUSE Linux Products GmbH. All Rights Reserved.
#
# FS QA Test 282
#
# Make sure scrub speed limitation works as expected.
#
. ./common/preamble
_begin_fstest auto scrub

_cleanup()
{
	[ -n "$filler_pid" ] && kill "$filler_pid" &> /dev/null
	wait
}

. ./common/filter

_wants_kernel_commit eb3b50536642 \
	"btrfs: scrub: per-device bandwidth control"

# For direct IO without falling back to buffered IO.
_require_odirect
_require_chattr C
# For data checksum verification during scrub
_require_btrfs_no_nodatasum

# We want at least 10G for the scratch device.
_require_scratch_size $(( 10 * 1024 * 1024))

# Make sure we can create scrub progress data file
if [ -e /var/lib/btrfs ]; then
	test -w /var/lib/btrfs || _notrun '/var/lib/btrfs is not writable'
else
	test -w /var/lib || _notrun '/var/lib/btrfs cannot be created'
fi

_scratch_mkfs >> $seqres.full 2>&1
_scratch_mount

uuid=$(findmnt -n -o UUID $SCRATCH_MNT)

devinfo_dir="/sys/fs/btrfs/${uuid}/devinfo/1"

# Check if we have the sysfs interface first.
if [ ! -f "${devinfo_dir}/scrub_speed_max" ]; then
	_notrun "No sysfs interface for scrub speed throttle"
fi

# Create a NOCOW file and do direct IO for 4 seconds to measure the performance.
#
# The only way to reach real disk performance is direct IO without falling back
# to buffered IO, thus requiring NOCOW.
touch $SCRATCH_MNT/filler
chattr +C $SCRATCH_MNT/filler
$XFS_IO_PROG -d -c "pwrite -b 128K 0 1E" "$SCRATCH_MNT/filler" >> $seqres.full 2>&1 &
filler_pid=$!
sleep 4
kill $filler_pid
wait
unset filler_pid

# Make sure we still have some space left, if we hit ENOSPC, this means the
# storage is too fast and the filler didn't reach full 4 seconds write before
# hitting ENOSPC. In that case we have no reliable way to calculate scrub speed
# but skip the run.
_pwrite_byte 0x00 0 1M $SCRATCH_MNT/foobar >> $seqres.full 2>&1
if [ $? -ne 0 ]; then
	_notrun "Storage too fast, unreliable scrub speed"
fi

# But above NOCOW file has no csum, thus it won't really cause much
# verification workload. Use the filesize of above run to re-create a file with data
# checksums.
size=$(_get_filesize $SCRATCH_MNT/filler)
rm $SCRATCH_MNT/filler

# Recreate one with checksums.
touch $SCRATCH_MNT/filler
chattr -C $SCRATCH_MNT/filler
$XFS_IO_PROG -c "pwrite -i /dev/urandom 0 $size" $SCRATCH_MNT/filler >> $seqres.full

# Writeback above data, as scrub only verify the committed data.
sync

# The first scrub, mostly to grab the speed of the scrub.
$BTRFS_UTIL_PROG scrub start -B $SCRATCH_MNT >> $seqres.full

# We grab the rate from "scrub status" which supports raw bytes reporting
#
# The output looks like this:
# UUID:             62eaabc5-93e8-445f-b8a7-6f027934aea7
# Scrub started:    Thu Jan  5 14:59:12 2023
# Status:           finished
# Duration:         0:00:02
# Total to scrub:   1076166656
# Rate:             538083328/s
# Error summary:    no errors found
#
# What we care is that Rate line.
init_speed=$($BTRFS_UTIL_PROG scrub status --raw $SCRATCH_MNT | grep "Rate:" |\
	     $AWK_PROG '{print $2}' | cut -f1 -d\/)

# This can happen for older progs
if [ -z "$init_speed" ]; then
	_notrun "btrfs-progs doesn't support scrub rate reporting"
fi

# Cycle mount to drop any possible cache.
_scratch_cycle_mount

target_speed=$(( $init_speed / 2 ))
echo "$target_speed" > "${devinfo_dir}/scrub_speed_max"

# The second scrub, to check the throttled speed.
$BTRFS_UTIL_PROG scrub start -B $SCRATCH_MNT >> $seqres.full
speed=$($BTRFS_UTIL_PROG scrub status --raw $SCRATCH_MNT | grep "Rate:" |\
	     $AWK_PROG '{print $2}' | cut -f1 -d\/)

# The expected runtime should be 4 and 8 seconds, and since the runtime
# accuracy is only 1 second, give it a +/- 25% tolerance
if [ "$speed" -gt "$(( $target_speed * 5 / 4 ))" -o \
     "$speed" -lt "$(( $target_speed * 3 / 4 ))" ]; then
	echo "scrub speed $speed Bytes/s is not properly throttled, target is $target_speed Bytes/s"
fi

echo "Silence is golden"

# success, all done
status=0
exit
