Sometimes I want just a small counter, incrementing an integer each second running somewhere in a terminal. Maybe it is because my wristwatch is in the bathroom or because I want to do more rewarding things than counting seconds manually. Maybe I want not only to know how long something takes but also for how long it already ran in the middle of its execution? There are many reason why I would want some script that does nothing else than simply counting upward or downward with some specific frequency.
- the period should be possible to give as a floating point number and especially periods of a fraction of a second would be nice
- it should be able to execute an arbitrary program after each period
- it should not matter how long the execution of this program takes for the overall counting
Now this can not be hard, right? One would probably write this line and be done with it:
while sleep 1; do echo $i; i=$((i+1)); done
or to count for a certain number of steps:
for i in `seq 1 100`; do echo $i; sleep 1; done
This would roughly do the job but in each iteration some small offset would be added and though small, this offset would quickly accumulate.
Sure that cumulative error is tiny but given that this task seems to be so damn trivial I couldn't bear anymore with running any of the above but started looking into a solution.
Sure I could just quickly hack a small C script that would check gettimeofday(2) at each iteration and would adjust the time to usleep(3) accordinly but there HAD to be people before me with the same problem who already came up with a solution.
And there was! The solution is the sleepenh(1) program which, when given the timestamp of its last invocation and the sleep time in floating point seconds, will sleep for just the right amount to keep the overall frequency stable.
The author suggests, that sleepenh is to be used in shell scripts that need to repeat an action in a regular time interval and that is just what I did.
The result is trivial and simple but does just what I want:
- the interval will stay the same on average and the counter will not "fall behind"
- count upward or downward
- specify interval length as a floating point number of seconds including fractions of one second
- begin to count at given integer and count for a specific number of times or until infinity
- execute a program at every step, optionally by forking it from the script for programs possibly running longer than the given interval
You can check it out and read how to use and what to do with it on github:
Now lets compare the periodic script with the second example from above:
$ time sh -c 'for i in `seq 1 1000`; do echo $i; sleep 1; done' 0.08s user 0.12s system 0% cpu 16:41.55 total
So after only 1000 iterations, the counter is already off by 1.55 seconds. This means that instead of having run with a frequency of 1.0 Hz, the actual frequency was 1.00155 Hz. Is it too much to not want this 0.155% of error?
$ time ./periodic -c 1000 0.32s user 0.00s system 0% cpu 16:40.00 total
1000 iterations took exactly 1000 seconds. Cool.