A few days ago I discovered cpulimit
. It's a great tool that nicely (haha) complements nice
. Where nice
is normally used to reduce the amount of CPU a process uses by changing it priority, a nice
d process can still end up using more CPU than you want, and will of course use all that it wants if nothing with a higher priority comes along.
But sometimes you want to restrict a process to using no more than some particular fraction of CPU time regardless of priority. A good example is when you don't want those noisy PC fans to kick in and you don't care how long a job takes because whether it finishes in 15 minutes or 8 hours it's still going to finish while you're asleep.
cpulimit
is perfect for this, and is simple to use. An invocation like this:
cpulimit -l 50 somecommand ...
will run somecommand
but restrict it to only use 50% of a CPU. It does this by forking a watchdog process that periodically checks to see how much work somecommand
is doing, and if it's used too much briefly pauses it by sending the STOP
signal. After a little while it will un-pause it with the CONT
signal. Of course, because the load on your machine from other processes will never quite be constant, cpulimit
rarely hits the target exactly, but it gets close enough.
But I wanted to tweak it a bit. I wanted to be able to interactively "turn the volume knob" so that I could give somecommand
more or less CPU whenever I fancied. The result is a pull request which unfortunately is unlikely to ever get merged, as the original author hasn't touched the project in years, but if any of you want the nifty new feature applying the patch and building your own custom cpulimit
is pretty easy.
How the patch works is simple. It installs signal handlers for SIGUSR1
and SIGUSR2
which respectively increase and decrease the CPU allocation by 1%. Want to turn it up by 50%? Just write a little shell loop to send the signal 50 times:
for i in $(seq 1 50); do kill -SIGUSR1 $pid; done
Determining which process to send the signal to is a bit tricky, as there are two cpulimit
processes running. There's the first one, which is just waiting in the background for somecommand
to finish, then there's the watchdog that got forked off. It's the watchdog you want to send the signals to. You can tell which is the watchdog as it will generally have a higher PID and be using a little bit of CPU. If you are cpulimit
ing multiple processes then you can tell which watchdog is related to which process because the watchdog will have the command and its arguments on its command line. For example:
$ ps aux|grep ffmpeg|grep -v grep
david 90311 103.3 0.7 36105472 485448 s011 T 6:42pm 1:07.89 ffmpeg ...
david 90312 5.6 0.0 34221044 828 s011 S 6:42pm 0:02.82 cpulimit -l 100 ffmpeg ...
david 90310 0.0 0.0 34122740 796 s011 S 6:42pm 0:00.01 cpulimit -l 100 ffmpeg ...
You can see here that I asked cpulimit
to allow ffmpeg
to only use 1 CPU of the several available on this machine (ie to use 100% of a CPU - on modern machines the maximum allowed is the number of CPU cores * 100%). My shell accordingly started process 90310 which forked and execed ffmpeg
with pid 90311 and forked the watchdog process as pid 90312. The watchdog is using a little bit of CPU. It is therefore to process 90312 that I should send signals.
$ for i in $(seq 1 400); do kill -SIGUSR1 90312; done
That will send the "turn it up a bit" signal 400 times, so ffmpeg
is now limited to at most 500% of a CPU, and a few moments later:
$ ps aux|grep ffmpeg|grep -v grep
david 90311 497.0 0.8 36105472 507160 s011 T 6:42pm 12:42.37 ffmpeg ...
...
we can see that ffmpeg
is running a lot harder, now taking just under 500% of the CPU.
Top comments (2)
Update: it annoyed me again. Exiting with code 0, which normally means "yay, I succeeded", when the process died because of a signal is a pretty serious bug. So with that second patch it will exit with code 124 instead.
Thankfully it already Did The Right Thing if the process it spawned exited of its own volition with a non-zero code.
Nice addition, I might pop a few comments on your PR ;-)
There is of course an alternative approach, which is to not start the managed process through
cpulimit
but to connect it using the-p <pid>
option. This would then allow disconnecting and reconnecting with a different priority... I've used cpulimit mostly in this fashion to turn down the volume on a process that's already started, I don't want to restart and is using too much CPU.. YYMV 😁