cpulimit annoyed me so I improved it
Tags: c, patching, signals
Published: 2023-03-25 19:07:07

[Previously](https://dev.to/drhyde/gnu-timeout-annoyed-me-so-i-replaced-it-or-an-extremely-simple-introduction-to-fork-and-signal-handling-in-perl-32f7) | [more previously](https://dev.to/drhyde/gnu-tree-annoyed-me-so-i-fixed-it-2pnk)

A few days ago I discovered [`cpulimit`](https://github.com/opsengine/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:

```shell
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](https://github.com/opsengine/cpulimit/pull/116/files) 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:

```shell
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.