btrfs-progs: scrub limit: allow to set the limit

Add new options to set the per-device limit (requires root privileges as
it writes to the sysfs files).

Signed-off-by: David Sterba <dsterba@suse.com>
This commit is contained in:
David Sterba 2023-12-08 23:17:17 +01:00
parent 87dba20daf
commit 30d1a2d390
3 changed files with 91 additions and 4 deletions

View File

@ -26,10 +26,16 @@ cancel <path>|<device>
.. _man-scrub-limit:
limit [options] <path>
Show scrub limits set on devices of the given filesystem.
Show or set scrub limits on devices of the given filesystem.
``Options``
-d|--devid DEVID
select the device by DEVID to apply the limit
-l|--limit SIZE
set the limit of the device to SIZE (size units with suffix),
or 0 to reset to *unlimited*
--raw
print all numbers raw values in bytes without the *B* suffix
--human-readable

View File

@ -50,6 +50,7 @@
#include "common/open-utils.h"
#include "common/units.h"
#include "common/device-utils.h"
#include "common/parse-utils.h"
#include "common/sysfs-utils.h"
#include "common/string-table.h"
#include "common/string-utils.h"
@ -1212,6 +1213,17 @@ static u64 read_scrub_device_limit(int fd, u64 devid)
return limit;
}
static u64 write_scrub_device_limit(int fd, u64 devid, u64 limit)
{
char path[PATH_MAX] = { 0 };
int ret;
/* /sys/fs/btrfs/FSID/devinfo/1/scrub_speed_max */
snprintf(path, sizeof(path), "devinfo/%llu/scrub_speed_max", devid);
ret = sysfs_write_fsid_file_u64(fd, path, limit);
return ret;
}
static int scrub_start(const struct cmd_struct *cmd, int argc, char **argv,
bool resume)
{
@ -1970,8 +1982,10 @@ static DEFINE_SIMPLE_COMMAND(scrub_status, "status");
static const char * const cmd_scrub_limit_usage[] = {
"btrfs scrub limit [options] <path>",
"Show scrub limits set on devices of the given filesystem.",
"Show or set scrub limits on devices of the given filesystem.",
"",
OPTLINE("-d|--devid DEVID", "select the device by DEVID to apply the limit"),
OPTLINE("-l|--limit SIZE", "set the limit of the device to SIZE (size units with suffix), or 0 to reset to unlimited"),
HELPINFO_UNITS_LONG,
NULL
};
@ -1985,6 +1999,10 @@ static int cmd_scrub_limit(const struct cmd_struct *cmd, int argc, char **argv)
int fd = -1;
DIR *dirstream = NULL;
int cols, idx;
u64 opt_devid = 0;
bool devid_set = false;
u64 opt_limit = 0;
bool limit_set = false;
unit_mode = get_unit_mode_from_arg(&argc, argv, 0);
@ -1992,14 +2010,24 @@ static int cmd_scrub_limit(const struct cmd_struct *cmd, int argc, char **argv)
while (1) {
int c;
static const struct option long_options[] = {
{ "devid", required_argument, NULL, 'd' },
{ "limit", required_argument, NULL, 'l' },
{ NULL, 0, NULL, 0 }
};
c = getopt_long(argc, argv, "", long_options, NULL);
c = getopt_long(argc, argv, "d:l:", long_options, NULL);
if (c < 0)
break;
switch (c) {
case 'd':
opt_devid = arg_strtou64(optarg);
devid_set = true;
break;
case 'l':
opt_limit = parse_size_from_string(optarg);
limit_set = true;
break;
default:
usage_unknown_option(cmd, argv);
}
@ -2007,6 +2035,11 @@ static int cmd_scrub_limit(const struct cmd_struct *cmd, int argc, char **argv)
if (check_argc_exact(argc - optind, 1))
return 1;
if ((devid_set && !limit_set) || (!devid_set && limit_set)) {
error("--devid and --limit must be set together");
return 1;
}
fd = open_file_or_dir(argv[optind], &dirstream);
if (fd < 0)
return 1;
@ -2025,6 +2058,34 @@ static int cmd_scrub_limit(const struct cmd_struct *cmd, int argc, char **argv)
uuid_unparse(fi_args.fsid, fsid);
pr_verbose(LOG_DEFAULT, "UUID: %s\n", fsid);
if (devid_set) {
/* Set one device only. */
struct btrfs_ioctl_dev_info_args di_args = { 0 };
u64 limit;
ret = device_get_info(fd, opt_devid, &di_args);
if (ret == -ENODEV) {
error("device with devid %llu not found", opt_devid);
ret = 1;
goto out;
}
limit = read_scrub_device_limit(fd, opt_devid);
pr_verbose(LOG_DEFAULT, "Set scrub limit of devid %llu from %s%s to %s%s\n",
opt_devid,
limit > 0 ? pretty_size_mode(limit, unit_mode) : "unlimited",
limit > 0 ? "/s" : "",
opt_limit > 0 ? pretty_size_mode(opt_limit, unit_mode) : "unlimited",
opt_limit > 0 ? "/s" : "");
ret = write_scrub_device_limit(fd, opt_devid, opt_limit);
if (ret < 0) {
errno = -ret;
error("cannot write to the sysfs file: %m");
ret = 1;
}
ret = 0;
goto out;
}
cols = 3;
table = table_create(cols, 2 + fi_args.num_devices);
if (!table) {

View File

@ -1,5 +1,5 @@
#!/bin/bash
# Read scrub limits on a filesystem
# Read and set scrub limits on a filesystem
source "$TEST_TOP/common" || exit
@ -7,15 +7,19 @@ setup_root_helper
setup_loopdevs 4
prepare_loopdevs
TEST_DEV=${loopdevs[1]}
support=true
fsid="13411a59-ccea-4296-a6f8-1446ccf8c9be"
sysfs="/sys/fs/btrfs/13411a59-ccea-4296-a6f8-1446ccf8c9be"
run_check $SUDO_HELPER "$TOP/mkfs.btrfs" -f --uuid "$fsid" -d raid1 -m raid1 "${loopdevs[@]}"
run_check_mount_test_dev
# Set the limits directly
for i in "$sysfs"/devinfo/*/scrub_speed_max; do
if ! [ -f "$i" ]; then
_log "sysfs file scrub_speed_max not available, skip setting limits"
support=false
break;
fi
run_check cat "$i"
@ -23,6 +27,22 @@ for i in "$sysfs"/devinfo/*/scrub_speed_max; do
done
# This works even if scrub_speed_max files don't exist, this is equivalent to unlimited
run_check "$TOP/btrfs" scrub limit "$TEST_MNT"
# The rest of the test would fail
if ! $support; then
run_check_umount_test_dev
cleanup_loopdevs
fi
# Set the limits by command
here=`pwd`
cd "$sysfs/devinfo"
for i in *; do
run_check $SUDO_HELPER "$TOP/btrfs" scrub limit -d "$i" -l 20m "$TEST_MNT"
done
cd "$here"
run_check "$TOP/btrfs" scrub limit "$TEST_MNT"
run_check_umount_test_dev
cleanup_loopdevs