SystemD Timers vs Code Loops
Tags: technology
Published: 2023-03-01 18:37:10

---
title: SystemD Timers vs Code Loops
published: true
date: 2021-03-16 11:56:16 UTC
tags: technology
canonical_url: https://blog.hadenes.io/post/systemd-timers-vs-code-loops/
---

Originally posted at https://blog.hadenes.io/post/systemd-timers-vs-code-loops/

In this post, we are going to compare the differences in effectiveness, from a computation and energy consumption perspective, between SystemD Timers and in-code loops for executing infinite loops.

We’ll focus specifically on Linux, since features such as _timerfd_ are only available on it, although similar implementations exist for BSD and OSX such as _kqueue_.

The inspiration of this post came from [this StackOverflow thread](https://serverfault.com/questions/1011270/what-is-more-effective-running-a-script-with-a-loop-or-having-systemd-execute-i).

## The case for SystemD Timers

### Pros

#### timerfd

SystemD makes use of the Linux Kernel’s [timerfd](https://man7.org/linux/man-pages/man2/timerfd_create.2.html) timers, a [POSIX timer](https://man7.org/linux/man-pages/man2/timer_create.2.html) alternative that is more event-loop friendly, since it notifies time expiration via file descriptors (hence the _fd_ suffix), which allows for the use of things such as _epoll._

Being event-loop based, it also facilitates multi-threading and is very resource friendly.

Also, POSIX timers are considered by some to [have a terrible API](https://davmac.wordpress.com/2016/08/12/posix-timer-apis-are-borked/).

#### Resource Accounting

If you add `CPUAccounting` and `MemoryAccounting` to your service’s `[Service]` block (or have those enabled by default), and also have CGroup accounting enabled (usually the default), you are able to known exactly how much resource your service unit consumed without having to resort to custom code or external tools.

By running `systemctl status` on such services, you get an output similar to the one below.

```
● docker.service - Docker Application Container Engine
     Loaded: loaded (/nix/store/j0y2wmaywsvf8hs7y4pqd4jhll0ncsa8-docker-19.03.12/etc/systemd/system/docker.service; enabled; vendor preset: enabled)
    Drop-In: /nix/store/ig74rh79479nq89dd20fjhsn82kf0xdh-system-units/docker.service.d
             └─overrides.conf
     Active: active (running) since Tue 2021-03-16 08:34:15 -03; 56min ago
TriggeredBy: ● docker.socket
       Docs: https://docs.docker.com
   Main PID: 2830 (dockerd)
         IP: 0B in, 0B out
      Tasks: 29 (limit: 4915)
     Memory: 163.3M <<<<< Memory Accounting
        CPU: 9.710s <<<<< CPU Accounting
     CGroup: /system.slice/docker.service
             ├─2830 /nix/store/j0y2wmaywsvf8hs7y4pqd4jhll0ncsa8-docker-19.03.12/libexec/docker/dockerd --group=docker --host=fd:// --log-driver=journald --live-restore
             └─2844 containerd --config /var/run/docker/containerd/containerd.toml --log-level info

```

#### Last Run and Next Run

With SystemD, by running `systemctl list-timers` you are able to conveniently check when the timer was last run, when it’s scheduled to run next and how much time is left before that happens. You are also able to see when the timer suceeded for the last time.

#### Immediate Changes

When you run a timer, any change you make to your program or script will of course be used next time it runs.

If your service where to loop in it’s on code, you would need to restart it in order to apply the changes.

A trivial matter, but worth considering.

### Cons

#### Startup Cost

A timer needs to pay the cost of starting up the application every time it runs.

The impact of this obviously depends on your specific use case.

In a particular case of mine where I use `nix-shell`, this cost in not irrelevant, although I mitigated it using [cached-nix-shell](https://github.com/xzfc/cached-nix-shell).

#### Excessive Logging

Each time the unit starts or stops, a new log entry is created on the journal.

For very frequent tasks, this may fill your journal with useless information.

Apparently this can be mitigated by using `LogLevelMax=alert` on the service definition, but I haven’t tested this.

#### Accuracy

Not exactly a con since it can be easily configured around, but by default SystemD timers have an accuracy of 1 minute. T

his means that, much like Cron, your minimum interval by default is 1 minute.

This can be fixed by setting `AccuracySec` to something lower.

For scripts that I want to run every 2 seconds or so, I set `AccuracySec=1s`, but you can go as lower as `AccuracySec=1us`, although that’s likely overkill and will generate much more wake-ups than you actually need, consuming more battery.

Set it to a sane amount based on your exact need.

## The case for Code Loops

This part is extremely difficult to summarize, since each and every programming language has their very own way of handling timers.

On Python, for example, you can use [linuxfd](https://github.com/FrankAbelbeck/linuxfd) to interface with the same _timerfd_ SystemD uses, achieving a very similar result. By default, if I’m not mistaken, calling functions such as `sleep` makes use of the default POSIX Timer interface.

On C, you obviously have easy access to any syscall you need, allowing you to use the implementation that suits you best.

For JVM-based languages, such as Java, Clojure, Kotlin (the list goes on…), it mainly depends on the specific JVM implementation, but it most likely ends up mapping to the default POSIX Timer on Unix systems.

In Shell, since each and every instruction is a command, you basically have startup costs for every line anyway, so it doesn’t matter.

As a clear pro, you are able to have much more granular control of when and how your code loops.

## Charts

TBD

## Conclusion

Resource consumption wise, it’s usually the case for in-code loops to be lighter, since you are avoiding the startup costs.

But in conclusion, use whatever suits you best.

If you just need to run a simple script every few seconds or so and don’t want to worry about treating errors etc, just put it under a SystemD timer, configure `Restart` accordingly and forget about it.

If you otherwise need more complex scenarios, use in-code loops as you normally would, and consider implementing `systemd-notify` for tighter integration.