Systemd Scope – How to Create a Systemd Scope for an Existing Process from Command Line

cgroupsd-buslinuxsystemd

I have a machine running the cgroups v2 (unified) hierarchy, so systemd is responsible for managing all cgroups and delegation to the systemd user instance works. I'd like to perform resource control on a group of processes, so I need them together in a unit — presumably a systemd scope.

Normally, systemd-run would do this — but unfortunately these processes are already running and I don't want to restart them.

How can I create a systemd scope out of already existing processes? The Control Group Interface documentation tells me it's possible, but I haven't been able to find a way from the command line. Neither systemctl nor systemd-run seem able to do this.

Is there a way from the command line? I am running systemd v241 if it matters.

Best Answer

There are various command-line tools to make dbus calls; systemd comes with one called busctl. So you can call StartTransientUnit from the command line.

The command

The syntax is positively annoying, but it looks like this (for one process id, 14460):

busctl call --user org.freedesktop.systemd1 /org/freedesktop/systemd1 \
       org.freedesktop.systemd1.Manager StartTransientUnit 'ssa(sv)a(sa(sv))' \
       'SCOPE-NAME.scope' fail 1 PIDs au 1 14460 0

Explanation

That is positively opaque (and took some tries to get right, and ultimately using dbus-monitor to see how systemd-run did it — only on the system manager though, systemd-run --user seems not to go through dbus). So an explanation, parameter by parameter:

busctl call --user                  # use user session dbus, not system   
  org.freedesktop.systemd1          # dbus service name
  /org/freedesktop/systemd1         # dbus object in that service
  org.freedesktop.systemd1.Manager  # interface name in that service 
  StartTransientUnit                # method we're going to call
  'ssa(sv)a(sa(sv))'                # signature of method, see below
  'SCOPE-NAME.scope'                # first argument, name of scope
  fail                              # second argument, how to handle conflicts (see below)
  1                                 # start of third argument, number of systemd properties for unit 
  PIDs                              # name of first property
  au                                # data type of first property, (a)rray [aka list] of (u)nsigned integers
  1                                 # count of array — that is, number of pids 
  14460                             # first pid
  0                                 # fourth argument: array size=0 (unused parameter)

Adding to the command

More properties

To add another systemd property to the unit, you'd increase the number of properties and add it on. Note that each property is at least three additional command-line arguments: the key, the value-type, and the value. As an example, adding a Slice property would go from:

… fail 1 PIDs au 1 14460 0

to

… fail 2 PIDs au 1 14460 Slice s whatever.slice 0
       ^                 ^^^^^ ^ ^^^^^^^^^^^^^^
     count                key type value

Type "s" is string. The list of them can be found in the D-Bus specification’s “Type system" chapter

You can of course change the count to 3 and add a third property. Etc.

More pids

Similar to more properties, but this time it's the count buried inside the "PIDs" property value. An example should make it clearer:

… fail 1 PIDs au 1 14460 0

becomes

… fail 1 PIDs au 2 14460 14461 0
                 ^       ^^^^^
         internal count  second pid

if you add PID 14461 as well as 14460.

You can add a third, fourth, etc. PID in the same way.

Combining them

You can of course combine additional properties with additional pids. Just remember that the list of pids is a property value, so it needs to stay together. You can't mix pid arguments with other properties. The right way is to change:

… fail 1 PIDs au 1 14460 0

to:

… fail 2 PIDs au 2 14460 14461 Slice s whatever.slice 0

(the order doesn't matter, you could put the Slice block before the PIDs block).

Where does the signature come from?

The signature is obtained either from the systemd dbus API documentation or, probably more easily, by using dbus introspection:

$ busctl introspect org.freedesktop.systemd1 /org/freedesktop/systemd1 \
         org.freedesktop.systemd1.Manager | grep1 StartTransientUnit
NAME                  TYPE     SIGNATURE         RESULT/VALUE   FLAGS
.StartTransientUnit   method   ssa(sv)a(sa(sv))  o              -

(for grep1, see https://unix.stackexchange.com/a/279518)

There are a lot of methods and dbus-properties listed, over 180 here. So don't omit the grep.

What does “fail” handling conflicts mean? What else is there?

According to the systemd documentation (look under "CreateUnit"), the useful values are fail and replace. fail means your scope will fail to start if there is some conflict. replace means systemd will get rid of the conflicting unit. Note that this seems to only be for units that are currently starting or scheduled to (it does say "queued") — replace won't, for example, replace an already-running scope with the same name.

Related Question