Вопрос или проблема
Я ищу команду, подобную ionice
и trickle
. Мне нужно следующее:
thecommand --read 5M --write 1M cp foo bar
чтобы ограничить cp
на чтение со скоростью 5 МБ/с и запись со скоростью 1 МБ/с.
cp
— это просто пример.
Я хочу, чтобы ограничение работало независимо от того, является ли foo
локальным файлом или файлом на NFS.
Хорошо, если это будет работать только с динамически скомпилированными программами (т.е. если используется некая магия LD_PRELOAD).
Существует ли такое решение?
Я не в состоянии написать хороший ответ с примером, но cgroups
может вам в этом помочь; systemd-run
будет вашим другом, когда вы запускаете что-то в области, имеющей ограниченные ресурсы.
Плохое решение:
#define _GNU_SOURCE
#include <dlfcn.h>
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <time.h>
#include <errno.h>
#define LIMIT_MB_PER_SEC 1
#define LIMIT_BYTES_PER_SEC (LIMIT_MB_PER_SEC * 1024 * 1024)
ssize_t (*real_read)(int fd, void *buf, size_t count) = NULL;
ssize_t (*real_write)(int fd, const void *buf, size_t count) = NULL;
static size_t bytes_transferred = 0;
static struct timespec start_time;
void init() {
real_read = dlsym(RTLD_NEXT, "read");
real_write = dlsym(RTLD_NEXT, "write");
clock_gettime(CLOCK_MONOTONIC, &start_time);
}
void sleep_if_needed(size_t bytes) {
struct timespec current_time;
clock_gettime(CLOCK_MONOTONIC, ¤t_time);
size_t elapsed_sec = current_time.tv_sec - start_time.tv_sec;
size_t elapsed_nsec = current_time.tv_nsec - start_time.tv_nsec;
double elapsed_time = elapsed_sec + elapsed_nsec / 1e9;
if (elapsed_time >= 1.0) {
// Сброс на следующий секунду
start_time = current_time;
bytes_transferred = 0;
}
bytes_transferred += bytes;
if (bytes_transferred > LIMIT_BYTES_PER_SEC) {
double excess_time = (bytes_transferred - LIMIT_BYTES_PER_SEC) / (double)LIMIT_BYTES_PER_SEC;
usleep((useconds_t)(excess_time * 1e6)); // Заснуть, чтобы ограничить скорость
}
}
ssize_t read(int fd, void *buf, size_t count) {
if (!real_read) init();
ssize_t result = real_read(fd, buf, count);
if (result > 0) {
sleep_if_needed(result);
}
return result;
}
ssize_t write(int fd, const void *buf, size_t count) {
if (!real_write) init();
ssize_t result = real_write(fd, buf, count);
if (result > 0) {
sleep_if_needed(result);
}
return result;
}
Запуск:
gcc -fPIC -shared -o limitrate.so -ldl limitrate.c
LD_PRELOAD=./limitrate.so time wc /tmp/test10M
Основной недостаток: он не принимает параметры для скорости, но это может быть основой для другого ответа.
Ответ или решение
Для ограничения скорости ввода-вывода (IO) в Linux, действительно, существует несколько альтернатив, которые могут удовлетворить вашим требованиям. Одним из наиболее мощных инструментов для этого являются контроллеры групп (cgroups), а также утилита systemd-run
, которая позволяет запускать процессы в рамках ограниченных ресурсов.
Использование cgroups
Cgroups (control groups) позволяют настраивать ресурсы, такие как пропускная способность сети, память и процессор, для групп процессов. Вы можете создать cgroup и назначить ей пределы на скорость ввода-вывода.
-
Создайте cgroup:
sudo cgcreate -g blkio:/limited_io
-
Установите лимиты ввода-вывода:
Для установки лимитов на чтение и запись вам нужно будет установить параметры в файлы, находящиеся в директории cgroup. Это можно сделать следующим образом:echo "1048576" | sudo tee /sys/fs/cgroup/blkio/limited_io/blkio.throttle.write_bps_device echo "5242880" | sudo tee /sys/fs/cgroup/blkio/limited_io/blkio.throttle.read_bps_device
Здесь значения указаны в байтах в секунду (1MB = 1048576 байт, 5MB = 5242880 байт).
-
Запускайте вашу команду в рамках этой cgroup:
sudo cgexec -g blkio:limited_io cp foo bar
Это действие запустит команду cp
с установленными вами лимитами на чтение и запись.
Использование systemd-run
Если ваш дистрибутив использует systemd, вы также можете использовать systemd-run
для ограничения ресурсов:
sudo systemd-run --scope \
--property=IOReadBandwidth=5M \
--property=IOWriteBandwidth=1M \
cp foo bar
Эта команда создаст временный scope, в котором будет применяться указанное ограничение на скорость ввода-вывода.
LD_PRELOAD Решение
Другим вариантом является использование механизма LD_PRELOAD
, который позволяет перехватывать системные вызовы, как вы и описали в своем коде. Ваш пример кода может стать хорошей основой для реализации более функционального инструмента. Вот пример, как это сделать с параметрами для скоростей:
-
Измените заголовок и добавьте параметры для скорости:
static size_t limit_read = 5242880; // 5MB static size_t limit_write = 1048576; // 1MB
-
В
main
функции запрашивайте эти параметры и передавайте их в глобальные переменные. -
Используйте подход LD_PRELOAD, как вы это уже сделали, компилируя библиотеку и используя
LD_PRELOAD
.
Обратите внимание, что использовать LD_PRELOAD
не всегда удобно с системными командами и может не работать на статически связанных бинарных файлах.
Каждый из этих методов имеет свои преимущества и недостатки, и выбор будет зависеть от вашего конкретного случая. Если вам нужна возможность ограничивать скорость для различных программ и читателей записей, то использование cgroups или systemd будет более предпочтительным, так как они более надежны и легко настраиваются.