]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/oom/test-oomd-util.c
Merge pull request #17732 from yuwata/core-use-synthetic_errno
[thirdparty/systemd.git] / src / oom / test-oomd-util.c
CommitLineData
db9ecf05 1/* SPDX-License-Identifier: LGPL-2.1-or-later */
61ff7397
AZ
2
3#include <unistd.h>
4
5#include "alloc-util.h"
6#include "cgroup-setup.h"
7#include "cgroup-util.h"
8#include "fileio.h"
9#include "fs-util.h"
10#include "oomd-util.h"
11#include "parse-util.h"
12#include "path-util.h"
13#include "string-util.h"
14#include "strv.h"
15#include "tests.h"
16
17static int fork_and_sleep(unsigned sleep_min) {
18 usec_t n, timeout, ts;
19
20 pid_t pid = fork();
21 assert_se(pid >= 0);
22
23 if (pid == 0) {
24 timeout = sleep_min * USEC_PER_MINUTE;
25 ts = now(CLOCK_MONOTONIC);
2940b128 26 for (;;) {
61ff7397
AZ
27 n = now(CLOCK_MONOTONIC);
28 if (ts + timeout < n) {
29 log_error("Child timed out waiting to be killed");
30 abort();
31 }
32 sleep(1);
33 }
34 }
35
36 return pid;
37}
38
39static void test_oomd_cgroup_kill(void) {
40 _cleanup_free_ char *cgroup_root = NULL, *cgroup = NULL;
41 int pid[2];
3e9b4f91 42 int r;
61ff7397
AZ
43
44 if (geteuid() != 0)
45 return (void) log_tests_skipped("not root");
46
47 if (cg_all_unified() <= 0)
48 return (void) log_tests_skipped("cgroups are not running in unified mode");
49
50 assert_se(cg_pid_get_path(NULL, 0, &cgroup_root) >= 0);
51
52 /* Create another cgroup below this one for the pids we forked off. We need this to be managed
53 * by the test so that pid1 doesn't delete it before we can read the xattrs. */
54 cgroup = path_join(cgroup_root, "oomdkilltest");
55 assert(cgroup);
3e9b4f91 56 assert_se(cg_create(SYSTEMD_CGROUP_CONTROLLER, cgroup) >= 0);
61ff7397
AZ
57
58 /* If we don't have permissions to set xattrs we're likely in a userns or missing capabilities */
3e9b4f91 59 r = cg_set_xattr(SYSTEMD_CGROUP_CONTROLLER, cgroup, "user.oomd_test", "test", 4, 0);
018b6f45 60 if (ERRNO_IS_PRIVILEGE(r) || ERRNO_IS_NOT_SUPPORTED(r))
3e9b4f91 61 return (void) log_tests_skipped("Cannot set user xattrs");
61ff7397
AZ
62
63 /* Do this twice to also check the increment behavior on the xattrs */
64 for (int i = 0; i < 2; i++) {
65 _cleanup_free_ char *v = NULL;
61ff7397
AZ
66
67 for (int j = 0; j < 2; j++) {
68 pid[j] = fork_and_sleep(5);
3e9b4f91 69 assert_se(cg_attach(SYSTEMD_CGROUP_CONTROLLER, cgroup, pid[j]) >= 0);
61ff7397
AZ
70 }
71
72 r = oomd_cgroup_kill(cgroup, false /* recurse */, false /* dry run */);
73 if (r <= 0) {
74 log_debug_errno(r, "Failed to kill processes under %s: %m", cgroup);
75 abort();
76 }
77
78 /* Wait a bit since processes may take some time to be cleaned up. */
79 sleep(2);
80 assert_se(cg_is_empty(SYSTEMD_CGROUP_CONTROLLER, cgroup) == true);
81
82 assert_se(cg_get_xattr_malloc(SYSTEMD_CGROUP_CONTROLLER, cgroup, "user.systemd_oomd_kill", &v) >= 0);
83 assert_se(memcmp(v, i == 0 ? "2" : "4", 2) == 0);
84 }
85}
86
87static void test_oomd_cgroup_context_acquire_and_insert(void) {
88 _cleanup_hashmap_free_ Hashmap *h1 = NULL, *h2 = NULL;
89 _cleanup_(oomd_cgroup_context_freep) OomdCGroupContext *ctx = NULL;
90 _cleanup_free_ char *cgroup = NULL;
91 OomdCGroupContext *c1, *c2;
92
93 if (geteuid() != 0)
94 return (void) log_tests_skipped("not root");
95
96 if (!is_pressure_supported())
97 return (void) log_tests_skipped("system does not support pressure");
98
99 if (cg_all_unified() <= 0)
100 return (void) log_tests_skipped("cgroups are not running in unified mode");
101
102 assert_se(cg_pid_get_path(NULL, 0, &cgroup) >= 0);
103
104 assert_se(oomd_cgroup_context_acquire(cgroup, &ctx) == 0);
105
106 assert_se(streq(ctx->path, cgroup));
61ff7397
AZ
107 assert_se(ctx->current_memory_usage > 0);
108 assert_se(ctx->memory_min == 0);
109 assert_se(ctx->memory_low == 0);
110 assert_se(ctx->swap_usage == 0);
111 assert_se(ctx->last_pgscan == 0);
112 assert_se(ctx->pgscan == 0);
113 ctx = oomd_cgroup_context_free(ctx);
114
115 /* Test the root cgroup */
116 assert_se(oomd_cgroup_context_acquire("", &ctx) == 0);
117 assert_se(streq(ctx->path, "/"));
118 assert_se(ctx->current_memory_usage > 0);
119
120 /* Test hashmap inserts */
121 assert_se(h1 = hashmap_new(&oomd_cgroup_ctx_hash_ops));
122 assert_se(oomd_insert_cgroup_context(NULL, h1, cgroup) == 0);
123 c1 = hashmap_get(h1, cgroup);
124 assert_se(c1);
125
126 /* make sure certain values from h1 get updated in h2 */
127 c1->pgscan = 5555;
128 c1->mem_pressure_limit = 6789;
129 c1->last_hit_mem_pressure_limit = 42;
130 assert_se(h2 = hashmap_new(&oomd_cgroup_ctx_hash_ops));
131 assert_se(oomd_insert_cgroup_context(h1, h2, cgroup) == 0);
132 c1 = hashmap_get(h1, cgroup);
133 c2 = hashmap_get(h2, cgroup);
134 assert_se(c1);
135 assert_se(c2);
136 assert_se(c1 != c2);
137 assert_se(c2->last_pgscan == 5555);
138 assert_se(c2->mem_pressure_limit == 6789);
139 assert_se(c2->last_hit_mem_pressure_limit == 42);
140}
141
142static void test_oomd_system_context_acquire(void) {
143 _cleanup_(unlink_tempfilep) char path[] = "/oomdgetsysctxtestXXXXXX";
144 OomdSystemContext ctx;
145
146 if (geteuid() != 0)
147 return (void) log_tests_skipped("not root");
148
149 assert_se(mkstemp(path));
150
151 assert_se(oomd_system_context_acquire("/verylikelynonexistentpath", &ctx) == -ENOENT);
152
153 assert_se(oomd_system_context_acquire(path, &ctx) == 0);
154 assert_se(ctx.swap_total == 0);
155 assert_se(ctx.swap_used == 0);
156
157 assert_se(write_string_file(path, "some\nwords\nacross\nmultiple\nlines", WRITE_STRING_FILE_CREATE) == 0);
158 assert_se(oomd_system_context_acquire(path, &ctx) == 0);
159 assert_se(ctx.swap_total == 0);
160 assert_se(ctx.swap_used == 0);
161
162 assert_se(write_string_file(path, "Filename Type Size Used Priority\n"
163 "/swapvol/swapfile file 18971644 0 -3\n"
164 "/dev/vda2 partition 1999868 993780 -2", WRITE_STRING_FILE_CREATE) == 0);
165 assert_se(oomd_system_context_acquire(path, &ctx) == 0);
166 assert_se(ctx.swap_total == 21474828288);
167 assert_se(ctx.swap_used == 1017630720);
168}
169
170static void test_oomd_pressure_above(void) {
171 _cleanup_hashmap_free_ Hashmap *h1 = NULL, *h2 = NULL;
172 _cleanup_set_free_ Set *t1 = NULL, *t2 = NULL, *t3 = NULL;
173 OomdCGroupContext ctx[2], *c;
174 loadavg_t threshold;
175
176 assert_se(store_loadavg_fixed_point(80, 0, &threshold) == 0);
177
178 /* /herp.slice */
179 assert_se(store_loadavg_fixed_point(99, 99, &(ctx[0].memory_pressure.avg10)) == 0);
180 assert_se(store_loadavg_fixed_point(99, 99, &(ctx[0].memory_pressure.avg60)) == 0);
181 assert_se(store_loadavg_fixed_point(99, 99, &(ctx[0].memory_pressure.avg300)) == 0);
182 ctx[0].mem_pressure_limit = threshold;
183
184 /* /derp.slice */
185 assert_se(store_loadavg_fixed_point(1, 11, &(ctx[1].memory_pressure.avg10)) == 0);
186 assert_se(store_loadavg_fixed_point(1, 11, &(ctx[1].memory_pressure.avg60)) == 0);
187 assert_se(store_loadavg_fixed_point(1, 11, &(ctx[1].memory_pressure.avg300)) == 0);
188 ctx[1].mem_pressure_limit = threshold;
189
190
191 /* High memory pressure */
192 assert_se(h1 = hashmap_new(&string_hash_ops));
193 assert_se(hashmap_put(h1, "/herp.slice", &ctx[0]) >= 0);
194 assert_se(oomd_pressure_above(h1, 0 /* duration */, &t1) == 1);
195 assert_se(set_contains(t1, &ctx[0]) == true);
196 assert_se(c = hashmap_get(h1, "/herp.slice"));
197 assert_se(c->last_hit_mem_pressure_limit > 0);
198
199 /* Low memory pressure */
200 assert_se(h2 = hashmap_new(&string_hash_ops));
201 assert_se(hashmap_put(h2, "/derp.slice", &ctx[1]) >= 0);
202 assert_se(oomd_pressure_above(h2, 0 /* duration */, &t2) == 0);
203 assert_se(t2 == NULL);
204 assert_se(c = hashmap_get(h2, "/derp.slice"));
205 assert_se(c->last_hit_mem_pressure_limit == 0);
206
207 /* High memory pressure w/ multiple cgroups */
208 assert_se(hashmap_put(h1, "/derp.slice", &ctx[1]) >= 0);
209 assert_se(oomd_pressure_above(h1, 0 /* duration */, &t3) == 1);
210 assert_se(set_contains(t3, &ctx[0]) == true);
211 assert_se(set_size(t3) == 1);
212 assert_se(c = hashmap_get(h1, "/herp.slice"));
213 assert_se(c->last_hit_mem_pressure_limit > 0);
214 assert_se(c = hashmap_get(h1, "/derp.slice"));
215 assert_se(c->last_hit_mem_pressure_limit == 0);
216}
217
218static void test_oomd_memory_reclaim(void) {
219 _cleanup_hashmap_free_ Hashmap *h1 = NULL;
220 char **paths = STRV_MAKE("/0.slice",
221 "/1.slice",
222 "/2.slice",
223 "/3.slice",
224 "/4.slice");
225
226 OomdCGroupContext ctx[5] = {
227 { .path = paths[0],
228 .last_pgscan = 100,
229 .pgscan = 100 },
230 { .path = paths[1],
231 .last_pgscan = 100,
232 .pgscan = 100 },
233 { .path = paths[2],
234 .last_pgscan = 77,
235 .pgscan = 33 },
236 { .path = paths[3],
237 .last_pgscan = UINT64_MAX,
238 .pgscan = 100 },
239 { .path = paths[4],
240 .last_pgscan = 100,
241 .pgscan = UINT64_MAX },
242 };
243
244 assert_se(h1 = hashmap_new(&string_hash_ops));
245 assert_se(hashmap_put(h1, paths[0], &ctx[0]) >= 0);
246 assert_se(hashmap_put(h1, paths[1], &ctx[1]) >= 0);
247 assert_se(oomd_memory_reclaim(h1) == false);
248
249 assert_se(hashmap_put(h1, paths[2], &ctx[2]) >= 0);
250 assert_se(oomd_memory_reclaim(h1) == false);
251
252 assert_se(hashmap_put(h1, paths[4], &ctx[4]) >= 0);
253 assert_se(oomd_memory_reclaim(h1) == true);
254
255 assert_se(hashmap_put(h1, paths[3], &ctx[3]) >= 0);
256 assert_se(oomd_memory_reclaim(h1) == false);
257}
258
259static void test_oomd_swap_free_below(void) {
260 OomdSystemContext ctx = (OomdSystemContext) {
261 .swap_total = 20971512 * 1024U,
262 .swap_used = 20971440 * 1024U,
263 };
264 assert_se(oomd_swap_free_below(&ctx, 20) == true);
265
266 ctx = (OomdSystemContext) {
267 .swap_total = 20971512 * 1024U,
268 .swap_used = 3310136 * 1024U,
269 };
270 assert_se(oomd_swap_free_below(&ctx, 20) == false);
271}
272
273static void test_oomd_sort_cgroups(void) {
274 _cleanup_hashmap_free_ Hashmap *h = NULL;
275 _cleanup_free_ OomdCGroupContext **sorted_cgroups;
276 char **paths = STRV_MAKE("/herp.slice",
277 "/herp.slice/derp.scope",
278 "/herp.slice/derp.scope/sheep.service",
279 "/zupa.slice");
280
281 OomdCGroupContext ctx[4] = {
282 { .path = paths[0],
283 .swap_usage = 20,
284 .pgscan = 60 },
285 { .path = paths[1],
286 .swap_usage = 60,
287 .pgscan = 40 },
288 { .path = paths[2],
289 .swap_usage = 40,
290 .pgscan = 20 },
291 { .path = paths[3],
292 .swap_usage = 10,
293 .pgscan = 80 },
294 };
295
296 assert_se(h = hashmap_new(&string_hash_ops));
297
298 assert_se(hashmap_put(h, "/herp.slice", &ctx[0]) >= 0);
299 assert_se(hashmap_put(h, "/herp.slice/derp.scope", &ctx[1]) >= 0);
300 assert_se(hashmap_put(h, "/herp.slice/derp.scope/sheep.service", &ctx[2]) >= 0);
301 assert_se(hashmap_put(h, "/zupa.slice", &ctx[3]) >= 0);
302
303 assert_se(oomd_sort_cgroup_contexts(h, compare_swap_usage, NULL, &sorted_cgroups) == 4);
304 assert_se(sorted_cgroups[0] == &ctx[1]);
305 assert_se(sorted_cgroups[1] == &ctx[2]);
306 assert_se(sorted_cgroups[2] == &ctx[0]);
307 assert_se(sorted_cgroups[3] == &ctx[3]);
308 sorted_cgroups = mfree(sorted_cgroups);
309
310 assert_se(oomd_sort_cgroup_contexts(h, compare_pgscan, NULL, &sorted_cgroups) == 4);
311 assert_se(sorted_cgroups[0] == &ctx[3]);
312 assert_se(sorted_cgroups[1] == &ctx[0]);
313 assert_se(sorted_cgroups[2] == &ctx[1]);
314 assert_se(sorted_cgroups[3] == &ctx[2]);
315 sorted_cgroups = mfree(sorted_cgroups);
316
317 assert_se(oomd_sort_cgroup_contexts(h, compare_pgscan, "/herp.slice/derp.scope", &sorted_cgroups) == 2);
318 assert_se(sorted_cgroups[0] == &ctx[1]);
319 assert_se(sorted_cgroups[1] == &ctx[2]);
320 assert_se(sorted_cgroups[2] == 0);
321 assert_se(sorted_cgroups[3] == 0);
322 sorted_cgroups = mfree(sorted_cgroups);
323}
324
325int main(void) {
326 int r;
327
328 test_setup_logging(LOG_DEBUG);
329
330 test_oomd_system_context_acquire();
331 test_oomd_pressure_above();
332 test_oomd_memory_reclaim();
333 test_oomd_swap_free_below();
334 test_oomd_sort_cgroups();
335
336 /* The following tests operate on live cgroups */
337
338 r = enter_cgroup_root(NULL);
339 if (r < 0)
340 return log_tests_skipped_errno(r, "failed to enter a test cgroup scope");
341
342 test_oomd_cgroup_kill();
343 test_oomd_cgroup_context_acquire_and_insert();
344
345 return 0;
346}