tangetools/iothrottle/iothrottle.sh
2024-12-30 23:44:44 +01:00

215 lines
4.4 KiB
Bash
Executable file

#!/bin/bash
# Edit iothrottle.sh - not iothrottle
: <<=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<--io> I<speed>
Shorthand for B<-i> I<speed> B<-o> I<speed>
=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 LIMITATIONS
B<iothrottle> intercepts calls to B<read>(2) and B<write>(2). If too
much is read/written it inserts a pause for each call. This means the
speed can go over the limit for a single call. Thus if the limit is
400 bytes/sec, but the call moves 4096 bytes, there will be a pause of
10 secs.
If the program spawns processes, each child will get the limit. In
other words: two children each with a limit of 1M may produce 2MB/s.
Not all programs use B<read>(2) and B<write>(2) for I/O, so
B<iothrottle> tries to intercept other relevant calls, too.
Tested: curl, wget, ssh, ffmpeg, cat, cp
These programs use other methods: cp (to same filesystem)
Statically linked programs will also not work.
File a bug report when you find programs that fail.
=head1 BUGS
File bugs at: 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() {
# hex encoded iothrottle.so
local hex=dummy
echo $hex
}
_hex_to_bin() {
LC_ALL=C awk '{for (i=1; i<=length($0); i+=2) printf "%c", strtonum("0x" substr($0, i, 2))}'
}
_iothrottle.so() {
local iothrottleso=$(mktemp -t iotrottle.XXXXX)
_hex | _hex_to_bin > "$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
;;
--io)
INPUT_LIMIT="$2"
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
cleanup() {
# if parent is dead: remove tmpfile
ppid=$1
while kill -0 $ppid 2>/dev/null; do sleep 1; done
rm $_so
}
cleanup $$ &
LD_PRELOAD=$_so "$@"
fi