Skip to content

Latest commit

 

History

History
200 lines (143 loc) · 7.79 KB

writing_a_plugin.md

File metadata and controls

200 lines (143 loc) · 7.79 KB

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

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.