Thanks for this. It was most helpful for my home media server ...
I had to make some changes for my needs though as your method for polling for limited PIDs was always returning null... I ended up rewriting abit but the key change was the line:
Code:
LIMITED_PIDS=$(ps -eo args | gawk '$1=="cpulimit" {print $3}')
## changed to (more explaination below) ##
LIMITED_PIDS=$(ps -eo args | grep cpulimited-bin |grep -v grep |awk '{print $3}')
I needed a version with start/stop built in and also wanted logging.. So my finial code which is still being tested is:
cpulimited.sh
Code:
#!/bin/bash
# ==============================================================
# CPU limit daemon - set PID's max. percentage CPU consumptions
# ==============================================================
# Variables
CPU_LIMIT=25 # Maximum percentage CPU consumption by each PID
DAEMON_INTERVAL=3 # Daemon check interval in seconds
LOG=/var/log/cuplimited.log
CPULIMITED_TMPDIR=/tmp/cpulimited
CONFIG=/etc/cpulimited/cpulimited.conf
BLACKLIST=0
WHITELIST=0
PROCESSES_LIST=
do_help() {
cat <<EOF
Usage: $0 <options> start|stop
-f :: Run in foreground
-c <config file> :: Use specified config file
EOF
exit 0
}
do_cpuwatcher() {
# Search and limit violating PIDs
while [ -z "$EXIT" ]; do
sleep $DAEMON_INTERVAL
# Check for exit
DCMD=$(cat "$CONTROL_FILE" |tail -n 1)
if [ "$DCMD" = "exit" ]; then
echo "Shutdown requested..."
PIDS="$(ps ax |grep cpulimited-bin |awk '{print $1}')"
echo "Removing CPU limits ..."
kill -9 $PIDS >/dev/null 2>&1
sleep 1
cd /tmp
echo "Cleaning up ..."
rm -rfd $CPULIMITED_TMPDIR
echo "Goodbye"
EXIT=1
exit 0
fi
NEW_PIDS=$(eval "$NEW_PIDS_COMMAND")
LIMITED_PIDS=$(ps -eo args | grep cpulimited-bin |grep -v grep |awk '{print $3}')
QUEUE_PIDS=$(comm -23 <(echo "$NEW_PIDS" | sort -u) <(echo "$LIMITED_PIDS" | sort -u) | grep -v '^$')
if [ -n "$QUEUE_PIDS" ]; then
echo "These PIDs are already limited: $LIMITED_PIDS"
echo "These PIDs are now entering the queue: $QUEUE_PIDS"
fi
for i in $QUEUE_PIDS; do
echo "Setting limits for PID: $i"
if [ -z "$FOREGROUND" ]; then
./cpulimited-bin -p $i -l $CPU_LIMIT -z >>$LOG 2>&1 &
else
./cpulimited-bin -p $i -l $CPU_LIMIT -z &
fi
done
done
exit 0
}
# Init
## Process commandline options
while [ "$1" ]; do
case "$1" in
-f)
FOREGROUND=1
shift 1
;;
-c)
CONFIG=$2
shift 2
;;
start)
CMD=start
shift 1
break
;;
stop)
CMD=stop
shift 1
break
;;
*)
do_help
break
esac
done
if [ -z "$CMD" ]; then
do_help
exit 0
fi
[ -f $CONFIG ] && . $CONFIG
if [ $BLACKLIST = 1 -a $WHITELIST = 1 ]; then
echo "Cannot use both whitelist AND blacklist. Please check your config"
exit 1
else
if [ "$BLACKLIST" = "yes" ]; then
MODE=blacklist
NEW_PIDS_COMMAND="top -b -n1 -c | grep -E '$BLACK_PROCESSES_LIST' | gawk '\$9>CPU_LIMIT {print \$1}' CPU_LIMIT=$CPU_LIMIT"
fi
if [ "$WHITELIST" = "yes" ]; then
MODE=whitelist
NEW_PIDS_COMMAND="top -b -n1 -c | gawk 'NR>6' | grep -E -v '$WHITE_PROCESSES_LIST' | gawk '\$9>CPU_LIMIT {print \$1}' CPU_LIMIT=$CPU_LIMIT"
fi
if [ -z "$MODE" ]; then
MODE=supervisor
NEW_PIDS_COMMAND="top -b -n1 -c | gawk 'NR>6 && \$9>CPU_LIMIT {print \$1}' CPU_LIMIT=$CPU_LIMIT"
fi
fi
########## END Init ############
case "$CMD" in
start)
echo "Starting up in $MODE mode ..."
if [ -d $CPULIMITED_TMPDIR ]; then
echo "Already running or stale tmp directory detected. Aborting ..."
exit 23
else
mkdir -p $CPULIMITED_TMPDIR
CONTROL_FILE="${CPULIMITED_TMPDIR}/control"
chown -R root:root $CPULIMITED_TMPDIR
chmod 700 $CPULIMITED_TMPDIR
cd $CPULIMITED_TMPDIR
CPULIMIT=$(which cpulimit)
ln -s $CPULIMIT cpulimited-bin
touch $CONTROL_FILE
if [ -n "$FOREGROUND" ]; then
# Do not fork
do_cpuwatcher
else
# Fork into the background
do_cpuwatcher >>$LOG &
fi
fi
;;
stop)
echo "Sending stop signal ..."
CONTROL_FILE="${CPULIMITED_TMPDIR}/control"
echo "exit" >$CONTROL_FILE
;;
esac
I set a temp directory up for the script and symlinked /usr/bin/cpulimit to ./cpulimited-bin to ease in grep'ing for cpulimit processes spawned by this script. I also added the ability to set changes from a config file. The other change is that BLACKLIST/WHITELIST is a mode toggle (0/1) and PROCESS_LIST would contain the strings to act on... Most all other changes were simply stylistic.
Thanks again!
Bookmarks