File: writing_a_plugin.md

package info (click to toggle)
oomd 0.5.0-1.2
  • links: PTS, VCS
  • area: main
  • in suites: bookworm, forky, sid, trixie
  • size: 1,996 kB
  • sloc: cpp: 14,345; sh: 89; makefile: 7
file content (200 lines) | stat: -rw-r--r-- 7,974 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
# Writing a plugin

Plugins are at the core of oomd. Everything that implements business logic
must be done in a plugin. If you haven't already, you should read
[configuration.md](configuration.md). That doc explains the high level goals
of plugins.

If you're writing a kill plugin, you can get most common kill plugin
functionality by inheriting from BaseKillPlugin. Read this doc, then read
[writing_a_kill_plugin.md](writing_a_kill_plugin.md).

## Interface

The `BasePlugin` interface is found in `engine/BasePlugin.h`. `BasePlugin`
is a pure virtual class that defines what is expected of each plugin. This
document assumes you have already read through `engine/BasePlugin.h`. If
you have not, please do.

Ignoring the comments and less relevant bits, every plugin must implement
the following two methods:

      virtual int init(
          const PluginArgs& args,
          const PluginConstructionContext& context) = 0;

      /* where PluginArgs is an alias of
         std::unordered_map<std::string, std::string> */

      virtual PluginRet run(OomdContext& context) = 0;

and optionally implement:

      virtual void prerun(OomdContext& context){}

### init(..)

The `init(..)` method is called by the config compiler. The config compiler
transforms (typically) JSON configuration into actual data structures oomd
will work with. As part of the compilation process, oomd will run `init(..)`
on every instantiated plugin.

`MonitoredResources& resources` is a typedef'd std::unordered_set of strings.
Plugins should insert any cgroups they want accounting on into `resources`.
Accounting information will be passed back to the plugin at each event loop
tick in `run(..)` via `OomdContext& context`. Kill plugins will typically
place any cgroups they are instructed to possibly kill into `resources`.

`const PluginArgs& args` is a map of arguments that are provided to the plugin.
Each plugin, as defined by the config schema, is allowed to have a JSON object
containing string->string key/value pairs representing the configuration.

`const PluginConstructionContext& context` holds other init()-time context.
`context->cgroupFs()` is the cgroup fs that oomd will monitor, as set from the
--cgroup-fs command line flag, defaulting to /sys/fs/cgroup.

If plugin initialization is success, the plugin must return zero. A non-zero
return value will fail the config compilation process (and usually exit the
process). Plugins are encouraged to print a useful error message before
returning non-zero.

### run(..)

`run(..)` is called by the core oomd runtime each event loop tick. The
duration between each tick can be configured via `--interval,-i` on the
command line. `run(..)` is the work horse function of every plugin. This
is where most, if not all, of the work is expected to be done. You can do
pretty much whatever you want in the plugin. Make syscalls, inspect the file
system, mess with other plugins by modifying `OomdContext`, name it. (Not to
imply doing all of those things are a productive idea).

`OomdContext& context` is an object that contains state about the system.
`OomdContext&` typically contains accounting information on every cgroup
that the core oomd runtime has been instructed to monitor. This means that
cgroups other plugins placed into `resources` in `init(..)` will also be
available.

### prerun(..)

`prerun(..)` is called by the core oomd runtime each event loop tick, before
`run(..)` has been called on any plugin. It is guaranteed to be invoked as long
as the plugin is enabled, even if it is an action plugin and not triggered.
Therefore, it is designed to execute stateful logic, such as calculating sliding
window metrics, storing time when a threshold is exceeded, etc.

If the plugin may rely on temporal cgroup counters such as average usage and io
cost rate (see `CgroupContext.h`) in `run(..)`, it must implement `prerun(..)`
to retrieve temporal counters for all of its cgroups to keep them from getting
stale. See `KillIOCost` for example.

## Plugin registration

You might have wondered, how does the config compiler know which plugin name
maps to which C++ class? This section goes into the details of plugin
registration.

oomd employs a static plugin registration system. In other words, oomd plugins
will insert themselves into a map of plugin name -> plugin factory method.
The details of static registration are out of the scope of this document, but
plenty of sources exist online that explain the details. In short, static
registration ensures that the map of X -> Y will be fully populated before
the program reaches `int main()`.

Plugins are required to register themselves to the plugin registry via the
`REGISTER_PLUGIN` macro defined in `oomd/PluginRegistry.h`. If you do not
register your plugin and try to use it in a config, the compilation process
will fail and oomd will not start up.

Note that `REGISTER_PLUGIN` must be a static factory method that returns
a pointer to an instance of your plugin allocated on the heap. There is
not currently support to register custom deleter functions. This means
you may not use custom allocators (by overloading `new`/`delete` or
otherwise) to create instances of your plugin.

## Logging

Plugins are encouraged to use the oomd logging facilities.

`OLOG` is an ostream style macro that prints logs asynchronously. This is
useful as systems under intense memory pressure are not usually able to write
to filesystems or output things to stdout/sterr. It's usually not a good idea
to log inline on a production host, as writes can block indefinitely, thus
limiting the utility of oomd.

`OLOG` is also smart enough to log inline (ie not async) when run in unit
tests. Logging async in unit tests can mess with gtest output parsing.

## Anatomy of ContinuePlugin

There is a functioning (but useless) example plugin included in oomd's set of
core plugins: ContinuePlugin. You can find the code at
`plugins/ContinuePlugin.h` and `plugins/ContinuePlugin.cpp.`

### ContinuePlugin.h

    #pragma once

    #include "oomd/engine/BasePlugin.h"

    namespace Oomd {

Plugins do not need to exist in the `Oomd` namespace but it'll save you some
typing.

    class ContinuePlugin : public Engine::BasePlugin {

All plugins must derive from `BasePlugin`, as discussed in the previous
section.

     public:
      int init(
          Engine::MonitoredResources& /* unused */,
          const Engine::PluginArgs& /* unused */,
          const PluginConstructionContext& /* unused */) override {
        return 0;
      }

The `init(..)` method is implemented inline here. Note that we do not examine
our arguments or register any resource requirements. We return 0 to signify
success.

     Engine::PluginRet run(OomdContext& /* unused */) override {
        return Engine::PluginRet::CONTINUE;
      }

Our plugin does nothing besides return CONTINUE.

      static ContinuePlugin* create() {
        return new ContinuePlugin();
      }

    } // namespace Oomd

This is our static factory method. Note it has to be static, as the config
compiler uses the static plugin registry, and all factory functions in the
plugin registry must be static.

      ~ContinuePlugin() = default;

We can use the default destructor.

### ContinuePlugin.cpp

    #include "oomd/plugins/ContinuePlugin.h"

    #include "oomd/PluginRegistry.h"

    namespace Oomd {

    REGISTER_PLUGIN(continue, ContinuePlugin::create);

    } // namespace Oomd

The only thing our cpp file does is register our plugin into the plugin
registry. Carefully note that we did not register our plugin in the header
file. Doing that might work for now, but will cause (somewhat cryptic) errors
if someone decides to include your plugin header and subclass some things.

What happens in this bad case is that there will be multiple places where
your plugin will be registered. The `REGISTER_PLUGIN` macro is designed such
that collision will occur if you try to register > 1 plugin with the same name.