Ограничить скорость ввода-вывода файлов

Вопрос или проблема

Я ищу команду, подобную 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, &current_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 и назначить ей пределы на скорость ввода-вывода.

  1. Создайте cgroup:

    sudo cgcreate -g blkio:/limited_io
  2. Установите лимиты ввода-вывода:
    Для установки лимитов на чтение и запись вам нужно будет установить параметры в файлы, находящиеся в директории 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 байт).

  3. Запускайте вашу команду в рамках этой 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, который позволяет перехватывать системные вызовы, как вы и описали в своем коде. Ваш пример кода может стать хорошей основой для реализации более функционального инструмента. Вот пример, как это сделать с параметрами для скоростей:

  1. Измените заголовок и добавьте параметры для скорости:

    static size_t limit_read = 5242880; // 5MB
    static size_t limit_write = 1048576; // 1MB
  2. В main функции запрашивайте эти параметры и передавайте их в глобальные переменные.

  3. Используйте подход LD_PRELOAD, как вы это уже сделали, компилируя библиотеку и используя LD_PRELOAD.

Обратите внимание, что использовать LD_PRELOAD не всегда удобно с системными командами и может не работать на статически связанных бинарных файлах.

Каждый из этих методов имеет свои преимущества и недостатки, и выбор будет зависеть от вашего конкретного случая. Если вам нужна возможность ограничивать скорость для различных программ и читателей записей, то использование cgroups или systemd будет более предпочтительным, так как они более надежны и легко настраиваются.

Оцените материал
Добавить комментарий

Капча загружается...