Roland's homepage

My random knot in the Web

Statusline program in C for i3 on FreeBSD

This program started out as a Python script, which generally worked fine.

It would however crash every now and then after running for a long time. In an effort to find what was causing that I started logging exceptions to syslog. This did not result in any clues.

It is a bit silly to have a complete python interpreter running to produce a statusline. So I decided to rewrite it in C.

FreeBSD only

Since this program uses sysctlbyname(3) and sysctl(3) to gather system information, it only works on FreeBSD.

Description

The statusline-i3 utility shows system status information in the i3 status bar. It outputs a line containing the following items to standard output every second. From left to right, separated by a vertical bar:

  • Network traffic over the available interfaces. Incoming and outgoing directions separated by a forward slash. The traffic can use the suffix b for [bytes/s], k for [kB/s] and M for [Mb/s].
  • Total of unread messages in at most twenty mailboxes provided via the [-m] option. Maildirs are not supported.
  • The percentage of RAM that is in use.
  • The percentage of total cores usage, followed by the average temperature of all cores in degrees Centigrade.
  • Battery charge percentage and status (if the system has a battery).
  • Abbreviated weekday, date in ISO 8601 format and time in ISO 8601 format separated by spaces.
  • (Optional) the time that it takes to perform one iteration of the main loop.

It accepts the following options:

-d, --debug Show how long each iteration of the main loop takes.
-h, --help Show a help message and exit.
-m, --mailbox At most twenty mailbox paths separated by ‘:’.
-v, --version Show program’s version number and exit.

It is started by the i3 window manager.

For the rewrite in C dynamically allocated memory is avoided by using statically allocated buffers instead.

For counting e-mails, only mbox format is supported.

Structure of the program

After initialization, the program runs in an endless loop. This loop starts with acquiring the current time, using a monotonic timer. It then calls several functions that gather information (using sysctl(3)) and append it to the output buffer. It then writes the gathered information to stdout and flushes that stream. From measurements, all this takes at most 280 μs. Then it sleeps until a full second has passed since the start of the loop.

Data structure

A string buffer is the only data structure that is defined.

typedef struct {
  size_t used;
  char data[PATH_MAX];
} Buffer;

This buffer is used for two slightly different purposes;

  • To hold mailbox filenames (hence the length of MAX_PATH).
  • To accumulate info for display.

All buffers used are zero-initialized when created. The output buffer is re-created at each instance of the loop.

Three functions and one convenience macro can act on a Buffer structure.

void buffer_append(Buffer *buf, const char *str, size_t len);
#define buffer_spacer(b) buffer_append(b, "| ", 2)
size_t buffer_remaining(Buffer *buf);
void buffer_puts(Buffer *buf);

What these functions do should be self-evident. One important detail is that buffer_puts flushes stdout after writing its contents to it.

Comparison

According to cloc, the Python version has 247 lines of code, while the C version has 444 lines of code. The stripped executable of the C version has a size of 12800 bytes.

On a FreeBSD 14.2 x86-64 machine, the virtual size of statusline-i3 is 13744 KiB, while the resident set size is 2296 KiB. The CPU usage is around 0.02% according to top(1).

For comparison, on the same machine an idle Python 3.11 process has a virtual size of 0 KiB and a resident set size of 14760 KiB. I did not record the CPU usage of the Python version of statusline-i3 other than noticing it was less than 1%.


For comments, please send me an e-mail.


Related articles


←  Writing C in 2024