File: writing_a_kill_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 (196 lines) | stat: -rw-r--r-- 7,519 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
# Writing a kill plugin

Kill plugins are regular plugins: they inherit from `BaseKillPlugin` which
inherits from `BasePlugin`. Read [writing_a_plugin.md](writing_a_plugin.md)
first; everything in that doc applies to kill plugins as well.

# BaseKillPlugin default behavior

Kill plugins are responsible for the policy picking which cgroup to kill out of
a set of options. The mechanism of killing, with support for all the
standard kill plugin behavior, is implemented by `BaseKillPlugin`.

`BaseKillPlugin` subclasses by default have support for the `cgroup`,
`recursive`, `dry`, `debug`, and `post_action_delay` params as described in
[core_plugins.md](core_plugins.md).

Additionally, plugins that follow the `BaseKillPlugin` template respect
oomd.prefer and oomd.avoid, though technically that’s not part of
`BaseKillPlugin`.

# Interface

The `BaseKillPlugin` interface is found in `plugins/BaseKillPlugin.h`.
`BaseKillPlugin` 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.

There are two methods you must override:

      virtual std::vector<OomdContext::ConstCgroupContextRef> rankForKilling(
          OomdContext& ctx,
          std::vector<OomdContext::ConstCgroupContextRef>& cgroups,
          std::function<void(const CgroupContext&)>& olog_kill_fn_out) = 0;

      virtual void ologKillTarget(
          OomdContext& ctx,
          const CgroupContext& target,
          const std::vector<OomdContext::ConstCgroupContextRef>& peers) = 0;

and two you may want to override:

      int init(
          const Engine::PluginArgs& args,
          const PluginConstructionContext& context);

      virtual void prerun(OomdContext& context) {};

Note that these are different from the 3 `BasePlugin` methods `run`, `init`, and
`prerun`. `run` has been implemented for you in `BaseKillPlugin`, and will call
out to the subclass’ `rankForKilling` and `ologKillTarget` implementations.
`init` has a default implementation which you may wish to override, but unlike
`BasePlugin`s, are not required to. `prerun` is the same.

# Anatomy of KillIOCost

When creating a new kill plugin, it’s easiest to copy the files of an existing
kill plugin and follow their format. KillIOCost is a simple, useful plugin that
uses most of the APIs we’re interested in. It’s spread across 3 files, plus
entries in the TARGETS and meson.build files.

### KillIOCost.h

      #include "oomd/plugins/BaseKillPlugin.h"

      namespace Oomd {

      template <typename Base = BaseKillPlugin>
      class KillIOCost : public Base {

KillIOCost inherits from a templated base class to facilitate unit testing. The
base class is always `BaseKillPlugin`, except in CorePluginsTest.cpp where we
pass in `BaseKillPluginMock` to mocks out the killing. It's safe to assume
`Base = BaseKillPlugin`.

      public:
        void prerun(OomdContext& ctx) override;

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

        ~KillIOCost() override = default;

      protected:
        std::vector<OomdContext::ConstCgroupContextRef> rankForKilling(
            OomdContext& ctx,
            std::vector<OomdContext::ConstCgroupContextRef>& cgroups,
            std::function<void(const CgroupContext&)>& olog_kill_fn_out) override;

KillIOCost implements `prerun` and `rankForKilling`. Other plugins may want to
override `init` as well.

      };

      } // namespace Oomd

      #include "oomd/plugins/KillIOCost-inl.h"

Because KillIOCost has a templated base class, its method implementations can't
be in a `.cpp` file.

### KillIOCost.cpp

      #include "oomd/plugins/KillIOCost.h"

      #include "oomd/PluginRegistry.h"

      namespace Oomd {
      REGISTER_PLUGIN(kill_by_io_cost, KillIOCost<>::create);
      } // namespace Oomd

The `.cpp` file just registers the `kill_by_io_cost` plugin. List the `.cpp`
file in TARGETS and meson.build or the plugin will not be registered.


### KillIOCost-inl.h

      namespace Oomd {

      template <typename Base>
      int KillIOCost<Base>::init(
          const Engine::PluginArgs& args,
          const PluginConstructionContext& context) {
        // additional arg parsing and initialization here
        return Base::init(args, context);
      }

`BaseKillPlugin` implements  `init(...)`, parsing the  `cgroup`, `recursive`,
`post_action_delay`, `dry`, and `debug` plugin args by default. To eg. support
additional plugin-specific arguments, override init(...) and include a call to
`Base::init` as above. In the real code `KillIOCost` does not override
`init(...)` because `BaseKillPlugin::init(...)` is sufficient.

      template <typename Base>
      void KillIOCost<Base>::prerun(OomdContext& ctx) {
        // Make sure temporal counters be available when run() is invoked
        Base::prerunOnCgroups(
            ctx, [](const auto& cgroup_ctx) { cgroup_ctx.io_cost_rate(); });
      }

`Base::prerunOnCgroups(...)` supports `"recursive"` by default.

      template <typename Base>
      std::vector<OomdContext::ConstCgroupContextRef>
      KillIOCost<Base>::rankForKilling(
          OomdContext& ctx,
          const std::vector<OomdContext::ConstCgroupContextRef>& cgroups) {

`BaseKillPlugin::run` calls `rankForKilling` repeatedly as it walks down the
cgroup tree. `BaseKillPlugin::run` handles getting the CgroupContexts from the
plugin's `"cgroup"` arg, recursing (or not) if the plugin's `"recursive"` arg is
set, respecting memory.oom.group, and actually killing the appropriate pids.
`KillIOCost::rankForKilling(...)` is responsible for picking which cgroup to
kill from among the plugin's `"cgroup"` argument, or among a set of siblings if
we're recursing. See comment in `BaseKillPlugin.h` for in-depth details.

We return a sorted vector, instead of the single best cgroup, because if killing
the best-choice fails, we'll try to kill the next-best, and so on.

        return OomdContext::sortDescWithKillPrefs(
            cgroups, [](const CgroupContext& cgroup_ctx) {
              return cgroup_ctx.io_cost_rate().value_or(0);
            });

`sortDescWithKillPrefs` adds default support for `oomd_prefer` and `oomd_avoid`.

      }

      template <typename Base>
      void KillIOCost<Base>::ologKillTarget(
          OomdContext& ctx,
          const CgroupContext& target,
          const std::vector<OomdContext::ConstCgroupContextRef>& /* unused */) {
        OLOG << "Picked \"" << target.cgroup().relativePath() << "\" ("
            << target.current_usage().value_or(0) / 1024 / 1024
            << "MB) based on io cost generation at "
            << target.io_cost_rate().value_or(0);
      }

`ologKillTarget` is called every time a cgroup is chosen from the cgroups
returned from `rankForKilling`. KillIOCost uses it to log io_cost_rate() to
help readers of the logs understand why this cgroup was chosen.

`ologKillTarget` will be called at least once per `rankForKilling`, on the first
element returned by `rankForKilling`. If killing that cgroup fails,
`ologKillTarget` will be called on subsequent elems returned by
`rankForKilling`, in order, as we try to kill them. If `"recursive"` is set,
`ologKillTarget` will be called on every cgroup in the path down to the victim
leaf cgroup.

The 3rd argument of `ologKillTarget` is the set of cgroups `target` was selected
from. See KillMemoryGrowth for an example where this is useful to log.

      }

      } // namespace Oomd