3

I have a GUI where I display the ActiveState of several systemd services.

At 10Hz, I use the sd-bus api to query each service like so:

sd_bus* bus;
sd_bus_error err = SD_BUS_ERROR_NULL;
char* msg = 0;

sd_bus_default_system(&bus);

sd_bus_get_property_string(bus,
    "org.freedesktop.systemd1",
    "/org/freedesktop/systemd1/unit/foo_2eservice",
    "org.freedesktop.systemd1.Unit",
    "ActiveState",
    &err,
    &msg);

My problem is that when I run this code, /sbin/init or /lib/systemd/systemd-logind consumes about 50% CPU. Profiling the code shows a massive amount of time consumed by sd_bus_get_property_string. I need to reduce the number of times I call this function.

The introspection of the d-bus interface is interesting:

busctl introspect \
    org.freedesktop.systemd1 \
    /org/freedesktop/systemd1/unit/foo_2eservice \
    org.freedesktop.system1.Unit

NAME                             TYPE      SIGNATURE RESULT/VALUE   FLAGS
.Kill                            method    si        -     -
.Ref                             method    -         -     -
.Reload                          method    s         o     -
.ReloadOrRestart                 method    s         o     -
.ReloadOrTryRestart              method    s         o     -
.ResetFailed                     method    -         -     -
.Restart                         method    s         o     -
.SetProperties                   method    ba(sv)    -     -
.Start                           method    s         o     -
.Stop                            method    s         o     -
.TryRestart                      method    s         o     -
.Unref                           method    -         -     -
.ActiveEnterTimestamp            property  t         0     emits-change
.ActiveEnterTimestampMonotonic   property  t         0     emits-change
.ActiveExitTimestamp             property  t         0     emits-change
.ActiveExitTimestampMonotonic    property  t         0     emits-change
.ActiveState                     property  s         "inactive"   emits-change
...

This tells me that the ActiveState property emits a change.

How can I get a file-descriptor, or tap into the event-loop to receive that change?


The D-Bus specification suggests that systemd will emit a org.freedesktop.DBus.Properties.PropertiesChanged signal when properties change. I think I need to figure out how to subscribe to that signal.

2 Answers 2

3

The answer is to use sd_bus_match_signal(3) to set up a filter for events.

You listen for events by doing one of:

  1. sd_bus_wait(3) to block until events happen and sd_bus_process(3) to handle them.
  2. Connect sd-bus to the sd-event loop with sd_bus_attach_event(3) (you might need to set up the sd-event loop too).
  3. Use sd_bus_get_fd(3), sd_bus_get_events(3) and sd_bus_get_timeout(3) to connect sd-bus to your own event loop.

Here's a short C example of how to do this:

/* gcc main.c -lsystemd */

#include <systemd/sd-bus.h>
#include <stdio.h>
#include <stdlib.h>

static inline const char *strna(const char *s) {
        return s ?: "n/a";
}

int message_callback(sd_bus_message* m, void* userdata, sd_bus_error* ret_error) {
        printf("callback: path=%s interface=%s member=%s\n", 
                strna(sd_bus_message_get_path(m)), 
                strna(sd_bus_message_get_interface(m)), 
                strna(sd_bus_message_get_member(m))
        );
        return 0;
}

int main() {
        sd_bus* bus = NULL;
        sd_bus_error err = SD_BUS_ERROR_NULL;
        char* msg = NULL;
        void* userdata = NULL;

        sd_bus_default_system(&bus);

        sd_bus_match_signal(
                bus,                                             /* bus */
                NULL,                                            /* slot */
                NULL,                                            /* sender */
                "/org/freedesktop/systemd1/unit/foo_2eservice",  /* path */
                "org.freedesktop.DBus.Properties",               /* interface */
                "PropertiesChanged",                             /* member */
                NULL /*message_callback*/ ,                      /* callback */
                userdata
        );

        while( 1 ) { 
                sd_bus_wait(bus, UINT64_MAX);
                while ( sd_bus_process(bus, NULL) ) {  }

                sd_bus_get_property_string(
                        bus,                                             /* bus */
                        "org.freedesktop.systemd1",                      /* destination */
                        "/org/freedesktop/systemd1/unit/foo_2eservice", /* path */
                        "org.freedesktop.systemd1.Unit",                 /* interface */
                        "ActiveState",                                   /* member */
                        &err, 
                        &msg);

                printf("New state: %s\n", msg);
                free(msg);

        }

        sd_bus_error_free(&err);
        sd_bus_message_unref(ret);
        sd_bus_unref(bus);
        return 0;
}

I've commented out the callback mechanisms.

Note that you get a message for a change in any of the unit's properties. Therefore if you do something like systemctl stop, you can expect several messages.

3

I am not allowed to add comments, therefore I extend the answer of @Stewart. To enable signals one has to subscribe to them:

Subscribe() enables most bus signals to be sent out. Clients which are interested in signals need to call this method. Signals are only sent out if at least one client invoked this method.

If you are wondering why sd_bus_wait never returns, than you are missing something like

sd_bus_call_method(bus,
                   "org.freedesktop.systemd1",
                   "/org/freedesktop/systemd1",
                   "org.freedesktop.systemd1.Manager",
                   "Subscribe",
                   &error,
                   NULL,
                   NULL);

You must log in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.