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.
|