iothrottle: Initial version.
This commit is contained in:
parent
8f7d700812
commit
3b8d990b5d
21
Makefile
21
Makefile
|
@ -2,11 +2,11 @@ CMD = 2grep 2search audioping blink burncpu bwlimit clipboard drac \
|
|||
duplicate-packets em emoticons encdir fanspeed field \
|
||||
find-first-fail find-optimal forever ft-udvalg fxkill G \
|
||||
gitdiffdir gitedit gitnext gitundo goodpasswd histogram \
|
||||
Loffice mtrr mirrorpdf neno not off pdfman pidcmd pidtree \
|
||||
plotpipe puniq ramusage rand rclean rina rn rrm seekmaniac \
|
||||
shython sound-reload splitvideo stdout summer swapout T \
|
||||
teetime timestamp tracefile transpose twitsplit ctc upsidedown \
|
||||
vid w4it-for-port-open whitehash wifi-reload wssh \
|
||||
iothrottle Loffice mtrr mirrorpdf neno not off pdfman pidcmd \
|
||||
pidtree plotpipe puniq ramusage rand rclean rina rn rrm \
|
||||
seekmaniac shython sound-reload splitvideo stdout summer \
|
||||
swapout T teetime timestamp tracefile transpose twitsplit ctc \
|
||||
upsidedown vid w4it-for-port-open whitehash wifi-reload wssh \
|
||||
youtube-lbry ytv yyyymmdd
|
||||
|
||||
all: 2search/2grep.1 2search/2search.1 blink/blink.1 \
|
||||
|
@ -16,10 +16,10 @@ all: 2search/2grep.1 2search/2search.1 blink/blink.1 \
|
|||
ft-udvalg/ft-udvalg.1 G/G.1 gitdiffdir/gitdiffdir.1 \
|
||||
gitedit/gitedit.1 gitnext/gitnext.1 gitundo/gitundo.1 \
|
||||
goodpasswd/goodpasswd.1 histogram/histogram.1 \
|
||||
mirrorpdf/mirrorpdf.1 neno/neno.1 off/off.1 pdfman/pdfman.1 \
|
||||
pidcmd/pidcmd.1 pidtree/pidtree.1 plotpipe/plotpipe.1 \
|
||||
puniq/puniq.1 rand/rand.1 rina/rina.1 rn/rn.1 rrm/rrm.1 \
|
||||
seekmaniac/seekmaniac.1 shython/shython.1 \
|
||||
iothrottle/iothrottle.1 mirrorpdf/mirrorpdf.1 neno/neno.1 \
|
||||
off/off.1 pdfman/pdfman.1 pidcmd/pidcmd.1 pidtree/pidtree.1 \
|
||||
plotpipe/plotpipe.1 puniq/puniq.1 rand/rand.1 rina/rina.1 \
|
||||
rn/rn.1 rrm/rrm.1 seekmaniac/seekmaniac.1 shython/shython.1 \
|
||||
sound-reload/sound-reload.1 splitvideo/splitvideo.1 \
|
||||
stdout/stdout.1 teetime/teetime.1 timestamp/timestamp.1 \
|
||||
tracefile/tracefile.1 transpose/transpose.1 \
|
||||
|
@ -30,6 +30,9 @@ all: 2search/2grep.1 2search/2search.1 blink/blink.1 \
|
|||
%.1: %
|
||||
pod2man $< > $@
|
||||
|
||||
iothrottle/iothrottle:
|
||||
cd iothrottle; make
|
||||
|
||||
install:
|
||||
mkdir -p /usr/local/bin
|
||||
parallel eval ln -sf `pwd`/*/{} /usr/local/bin/{} ::: $(CMD)
|
||||
|
|
291
iothrottle/iothrottle.c
Normal file
291
iothrottle/iothrottle.c
Normal file
|
@ -0,0 +1,291 @@
|
|||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <dlfcn.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <fcntl.h>
|
||||
#include <time.h>
|
||||
#include <sys/uio.h>
|
||||
#include <sys/sendfile.h>
|
||||
#include <linux/fs.h>
|
||||
|
||||
// Function pointers for original read and write and other relevant calls
|
||||
ssize_t (*original_read)(int fd, void *buf, size_t count);
|
||||
ssize_t (*original_write)(int fd, const void *buf, size_t count);
|
||||
size_t (*original_fwrite)(const void *ptr, size_t size, size_t nmemb, FILE *stream);
|
||||
int (*original_fflush)(FILE *stream);
|
||||
ssize_t (*original_pread)(int fd, void *buf, size_t count, off_t offset);
|
||||
ssize_t (*original_pwrite)(int fd, const void *buf, size_t count, off_t offset);
|
||||
ssize_t (*original_readv)(int fd, const struct iovec *iov, int iovcnt);
|
||||
ssize_t (*original_writev)(int fd, const struct iovec *iov, int iovcnt);
|
||||
off_t (*original_sendfile)(int out_fd, int in_fd, off_t *offset, size_t count);
|
||||
ssize_t (*original_splice)(int fd_in, loff_t *off_in, int fd_out, loff_t *off_out, size_t len, unsigned int flags);
|
||||
ssize_t (*original_copy_file_range)(int fd_in, loff_t *off_in, int fd_out, loff_t *off_out, size_t len, unsigned int flags);
|
||||
|
||||
// Rate limits in bytes per second
|
||||
static size_t read_limit = 0; // 0 = no limit
|
||||
static size_t write_limit = 0;
|
||||
static int debug_mode = 0;
|
||||
|
||||
// Tracking variables
|
||||
static size_t read_bytes = 0;
|
||||
static size_t write_bytes = 0;
|
||||
static struct timespec read_start, write_start;
|
||||
|
||||
void throttle(size_t *bytes, size_t limit, struct timespec *start) {
|
||||
if (limit == 0) {
|
||||
return; // No limit applied
|
||||
}
|
||||
|
||||
struct timespec now;
|
||||
clock_gettime(CLOCK_MONOTONIC, &now);
|
||||
|
||||
double elapsed = (now.tv_sec - start->tv_sec) + (now.tv_nsec - start->tv_nsec) / 1e9;
|
||||
|
||||
double allowed_bytes = limit * elapsed;
|
||||
if (debug_mode) fprintf(stderr, "allowed: %ld written: %ld\n",
|
||||
(long)allowed_bytes, *bytes);
|
||||
if (*bytes > allowed_bytes) {
|
||||
*bytes -= allowed_bytes;
|
||||
double sleep_time = *bytes / (double)limit;
|
||||
|
||||
if (debug_mode) fprintf(stderr, "sleep: %f\n", sleep_time);
|
||||
usleep((useconds_t)(sleep_time * 1e6));
|
||||
*start = now;
|
||||
}
|
||||
}
|
||||
|
||||
size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream) {
|
||||
if (!original_fwrite) {
|
||||
original_fwrite = dlsym(RTLD_NEXT, "fwrite");
|
||||
if (!original_fwrite) {
|
||||
if (debug_mode) fprintf(stderr, "Error loading original fwrite: %s\n", dlerror());
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
}
|
||||
|
||||
size_t total_bytes = size * nmemb;
|
||||
throttle(&write_bytes, write_limit, &write_start);
|
||||
|
||||
size_t result = original_fwrite(ptr, size, nmemb, stream);
|
||||
if (result > 0) {
|
||||
write_bytes += total_bytes;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
int fflush(FILE *stream) {
|
||||
if (!original_fflush) {
|
||||
original_fflush = dlsym(RTLD_NEXT, "fflush");
|
||||
if (!original_fflush) {
|
||||
if (debug_mode) fprintf(stderr, "Error loading original fflush: %s\n", dlerror());
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
}
|
||||
|
||||
throttle(&write_bytes, write_limit, &write_start);
|
||||
|
||||
return original_fflush(stream);
|
||||
}
|
||||
|
||||
ssize_t read(int fd, void *buf, size_t count) {
|
||||
if (!original_read) {
|
||||
original_read = dlsym(RTLD_NEXT, "read");
|
||||
if (!original_read) {
|
||||
if (debug_mode) fprintf(stderr, "Error loading original read: %s\n", dlerror());
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
}
|
||||
|
||||
throttle(&read_bytes, read_limit, &read_start);
|
||||
|
||||
ssize_t result = original_read(fd, buf, count);
|
||||
if (result > 0) {
|
||||
read_bytes += result;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
ssize_t write(int fd, const void *buf, size_t count) {
|
||||
if (!original_write) {
|
||||
original_write = dlsym(RTLD_NEXT, "write");
|
||||
if (!original_write) {
|
||||
if (debug_mode) fprintf(stderr, "Error loading original write: %s\n", dlerror());
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
}
|
||||
|
||||
throttle(&write_bytes, write_limit, &write_start);
|
||||
|
||||
ssize_t result = original_write(fd, buf, count);
|
||||
if (result > 0) {
|
||||
write_bytes += result;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
ssize_t pread(int fd, void *buf, size_t count, off_t offset) {
|
||||
if (!original_pread) {
|
||||
original_pread = dlsym(RTLD_NEXT, "pread");
|
||||
if (!original_pread) {
|
||||
if (debug_mode) fprintf(stderr, "Error loading original pread: %s\n", dlerror());
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
}
|
||||
|
||||
throttle(&read_bytes, read_limit, &read_start);
|
||||
|
||||
ssize_t result = original_pread(fd, buf, count, offset);
|
||||
if (result > 0) {
|
||||
read_bytes += result;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
ssize_t pwrite(int fd, const void *buf, size_t count, off_t offset) {
|
||||
if (!original_pwrite) {
|
||||
original_pwrite = dlsym(RTLD_NEXT, "pwrite");
|
||||
if (!original_pwrite) {
|
||||
if (debug_mode) fprintf(stderr, "Error loading original pwrite: %s\n", dlerror());
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
}
|
||||
|
||||
throttle(&write_bytes, write_limit, &write_start);
|
||||
|
||||
ssize_t result = original_pwrite(fd, buf, count, offset);
|
||||
if (result > 0) {
|
||||
write_bytes += result;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
ssize_t readv(int fd, const struct iovec *iov, int iovcnt) {
|
||||
if (!original_readv) {
|
||||
original_readv = dlsym(RTLD_NEXT, "readv");
|
||||
if (!original_readv) {
|
||||
if (debug_mode) fprintf(stderr, "Error loading original readv: %s\n", dlerror());
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
}
|
||||
|
||||
throttle(&read_bytes, read_limit, &read_start);
|
||||
|
||||
ssize_t result = original_readv(fd, iov, iovcnt);
|
||||
if (result > 0) {
|
||||
read_bytes += result;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
ssize_t writev(int fd, const struct iovec *iov, int iovcnt) {
|
||||
if (!original_writev) {
|
||||
original_writev = dlsym(RTLD_NEXT, "writev");
|
||||
if (!original_writev) {
|
||||
if (debug_mode) fprintf(stderr, "Error loading original writev: %s\n", dlerror());
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
}
|
||||
|
||||
throttle(&write_bytes, write_limit, &write_start);
|
||||
|
||||
ssize_t result = original_writev(fd, iov, iovcnt);
|
||||
if (result > 0) {
|
||||
write_bytes += result;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
off_t sendfile(int out_fd, int in_fd, off_t *offset, size_t count) {
|
||||
if (!original_sendfile) {
|
||||
original_sendfile = dlsym(RTLD_NEXT, "sendfile");
|
||||
if (!original_sendfile) {
|
||||
if (debug_mode) fprintf(stderr, "Error loading original sendfile: %s\n", dlerror());
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
}
|
||||
|
||||
throttle(&read_bytes, read_limit, &read_start);
|
||||
throttle(&write_bytes, write_limit, &write_start);
|
||||
|
||||
off_t result = original_sendfile(out_fd, in_fd, offset, count);
|
||||
if (result > 0) {
|
||||
read_bytes += result;
|
||||
write_bytes += result;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
ssize_t splice(int fd_in, loff_t *off_in, int fd_out, loff_t *off_out, size_t len, unsigned int flags) {
|
||||
if (!original_splice) {
|
||||
original_splice = dlsym(RTLD_NEXT, "splice");
|
||||
if (!original_splice) {
|
||||
if (debug_mode) fprintf(stderr, "Error loading original splice: %s\n", dlerror());
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
}
|
||||
|
||||
throttle(&read_bytes, read_limit, &read_start);
|
||||
throttle(&write_bytes, write_limit, &write_start);
|
||||
|
||||
ssize_t result = original_splice(fd_in, off_in, fd_out, off_out, len, flags);
|
||||
if (result > 0) {
|
||||
read_bytes += result;
|
||||
write_bytes += result;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
ssize_t copy_file_range(int fd_in, loff_t *off_in, int fd_out, loff_t *off_out, size_t len, unsigned int flags) {
|
||||
if (!original_copy_file_range) {
|
||||
original_copy_file_range = dlsym(RTLD_NEXT, "copy_file_range");
|
||||
if (!original_copy_file_range) {
|
||||
if (debug_mode) fprintf(stderr, "Error loading original copy_file_range: %s\n", dlerror());
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
}
|
||||
|
||||
throttle(&read_bytes, read_limit, &read_start);
|
||||
throttle(&write_bytes, write_limit, &write_start);
|
||||
|
||||
ssize_t result = original_copy_file_range(fd_in, off_in, fd_out, off_out, len, flags);
|
||||
if (result > 0) {
|
||||
read_bytes += result;
|
||||
write_bytes += result;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
__attribute__((constructor))
|
||||
void init() {
|
||||
clock_gettime(CLOCK_MONOTONIC, &read_start);
|
||||
clock_gettime(CLOCK_MONOTONIC, &write_start);
|
||||
|
||||
// Initialize limits from environment variables (bytes/s)
|
||||
char *read_env = getenv("IOTHROTTLE_READ");
|
||||
char *write_env = getenv("IOTHROTTLE_WRITE");
|
||||
char *debug_env = getenv("IOTHROTTLE_DEBUG");
|
||||
|
||||
if (debug_env && atoi(debug_env) != 0) {
|
||||
debug_mode = 1;
|
||||
}
|
||||
|
||||
if (debug_mode) fprintf(stderr, "init called: default read_limit=%zu, write_limit=%zu\n", read_limit, write_limit);
|
||||
|
||||
if (read_env) {
|
||||
if (debug_mode) fprintf(stderr, "IOTHROTTLE_READ=%s\n", read_env);
|
||||
read_limit = strtoull(read_env, NULL, 10);
|
||||
} else {
|
||||
if (debug_mode) fprintf(stderr, "IOTHROTTLE_READ not set\n");
|
||||
}
|
||||
|
||||
if (write_env) {
|
||||
if (debug_mode) fprintf(stderr, "IOTHROTTLE_WRITE=%s\n", write_env);
|
||||
write_limit = strtoull(write_env, NULL, 10);
|
||||
} else {
|
||||
if (debug_mode) fprintf(stderr, "IOTHROTTLE_WRITE not set\n");
|
||||
}
|
||||
|
||||
if (debug_mode) fprintf(stderr, "Final read_limit=%zu, write_limit=%zu\n", read_limit, write_limit);
|
||||
}
|
174
iothrottle/iothrottle.in
Executable file
174
iothrottle/iothrottle.in
Executable file
|
@ -0,0 +1,174 @@
|
|||
#!/bin/bash
|
||||
|
||||
: <<=cut
|
||||
=pod
|
||||
|
||||
=head1 NAME
|
||||
|
||||
iothrottle - Limit IO to a given speed
|
||||
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
B<iothrottle> [-i read-speed] [-o write-speed] I<command>
|
||||
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
B<iothrottle> limits B<read>(2) and B<write>(2) to a given speed.
|
||||
|
||||
|
||||
=head1 OPTIONS
|
||||
|
||||
=over 4
|
||||
|
||||
=item B<-i> I<input-speed>
|
||||
|
||||
Throttle B<read>(2) to I<input-speed> bytes per second.
|
||||
|
||||
To I<input-speed> you can append k, K, m, M, g, G to multiply by 1000,
|
||||
1024, 1000000, 1048576 respectively.
|
||||
|
||||
|
||||
=item B<-o> I<output-speed>
|
||||
|
||||
Throttle B<write>(2) to I<output-speed> bytes per second.
|
||||
|
||||
To I<output-speed> you can append k, K, m, M, g, G to multiply by 1000,
|
||||
1024, 1000000, 1048576 respectively.
|
||||
|
||||
=back
|
||||
|
||||
|
||||
=head1 EXAMPLE
|
||||
|
||||
Copy mydir at 1MB/s:
|
||||
|
||||
iothrottle -i 1M cp -a mydir/ /other/filesystem/
|
||||
|
||||
|
||||
=head1 BUGS
|
||||
|
||||
Not all program use B<read>(2) and B<write>(2) for I/O.
|
||||
|
||||
These all use other methods: cp (to same filesystem), seq
|
||||
|
||||
File a bug report when you find other programs:
|
||||
https://git.data.coop/tange/tangetools/issues
|
||||
|
||||
|
||||
=head1 ENVIRONMENT VARIABLES
|
||||
|
||||
=over 9
|
||||
|
||||
=item $IOTHROTTLE_DEBUG=1
|
||||
|
||||
Turn on debugging.
|
||||
|
||||
=back
|
||||
|
||||
|
||||
=head1 AUTHOR
|
||||
|
||||
Copyright (C) 2024 Ole Tange,
|
||||
http://ole.tange.dk and Free Software Foundation, Inc.
|
||||
|
||||
|
||||
=head1 LICENSE
|
||||
|
||||
Copyright (C) 2012 Free Software Foundation, Inc.
|
||||
|
||||
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; either version 3 of the License, or
|
||||
at your option any later version.
|
||||
|
||||
This program is distributed in the hope that it will 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, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
=head1 DEPENDENCIES
|
||||
|
||||
B<iothrottle> uses B<bash>.
|
||||
|
||||
|
||||
=head1 SEE ALSO
|
||||
|
||||
B<ionice>, B<bwlimit>
|
||||
|
||||
|
||||
=cut
|
||||
|
||||
|
||||
_hex() {
|
||||
local hex=dummy
|
||||
echo $hex
|
||||
}
|
||||
|
||||
_iothrottle.so() {
|
||||
local iothrottleso=$(mktemp)
|
||||
_hex |
|
||||
LC_ALL=C awk '{for (i=1; i<=length($0); i+=2) printf "%c", strtonum("0x" substr($0, i, 2))}' > "$iothrottleso"
|
||||
echo "$iothrottleso"
|
||||
}
|
||||
|
||||
# Default values for input and output limits
|
||||
INPUT_LIMIT=""
|
||||
OUTPUT_LIMIT=""
|
||||
|
||||
# Parse command-line arguments
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
-i)
|
||||
INPUT_LIMIT="$2"
|
||||
shift 2
|
||||
;;
|
||||
-o)
|
||||
OUTPUT_LIMIT="$2"
|
||||
shift 2
|
||||
;;
|
||||
*)
|
||||
break
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
# Convert limits to bytes
|
||||
convert_to_bytes() {
|
||||
local value="$1"
|
||||
if [[ "$value" =~ ^[0-9]+K$ ]]; then
|
||||
value=$(( ${value%K*} * 1024 ))
|
||||
fi
|
||||
if [[ "$value" =~ ^[0-9]+k$ ]]; then
|
||||
value=$(( ${value%k*} * 1000 ))
|
||||
fi
|
||||
if [[ "$value" =~ ^[0-9]+M$ ]]; then
|
||||
value=$(( ${value%M*} * 1024 * 1024 ))
|
||||
fi
|
||||
if [[ "$value" =~ ^[0-9]+m$ ]]; then
|
||||
value=$(( ${value%m*} * 1000 * 1000 ))
|
||||
fi
|
||||
echo "$value"
|
||||
}
|
||||
|
||||
IOTHROTTLE_READ=$(convert_to_bytes "$INPUT_LIMIT")
|
||||
IOTHROTTLE_WRITE=$(convert_to_bytes "$OUTPUT_LIMIT")
|
||||
|
||||
# Export environment variables
|
||||
export IOTHROTTLE_READ=${IOTHROTTLE_READ:-0}
|
||||
export IOTHROTTLE_WRITE=${IOTHROTTLE_WRITE:-0}
|
||||
|
||||
# Preload the library and execute the remaining command
|
||||
_so=$(_iothrottle.so)
|
||||
if [ IOTHROTTLE_DEBUG = 1 ] ; then
|
||||
echo LD_PRELOAD=$_so
|
||||
LD_PRELOAD=$_so "$@"
|
||||
else
|
||||
LD_PRELOAD=$_so "$@"
|
||||
rm $_so
|
||||
fi
|
Loading…
Reference in a new issue