]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/oom/test-oomd-util.c
Merge pull request #22791 from keszybz/bootctl-invert-order
[thirdparty/systemd.git] / src / oom / test-oomd-util.c
1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
2
3 #include <unistd.h>
4
5 #include "alloc-util.h"
6 #include "cgroup-setup.h"
7 #include "cgroup-util.h"
8 #include "fd-util.h"
9 #include "fileio.h"
10 #include "fs-util.h"
11 #include "oomd-util.h"
12 #include "parse-util.h"
13 #include "path-util.h"
14 #include "string-util.h"
15 #include "strv.h"
16 #include "tests.h"
17 #include "tmpfile-util.h"
18
19 static int fork_and_sleep(unsigned sleep_min) {
20 usec_t n, timeout, ts;
21
22 pid_t pid = fork();
23 assert_se(pid >= 0);
24
25 if (pid == 0) {
26 timeout = sleep_min * USEC_PER_MINUTE;
27 ts = now(CLOCK_MONOTONIC);
28 for (;;) {
29 n = now(CLOCK_MONOTONIC);
30 if (ts + timeout < n) {
31 log_error("Child timed out waiting to be killed");
32 abort();
33 }
34 sleep(1);
35 }
36 }
37
38 return pid;
39 }
40
41 static void test_oomd_cgroup_kill(void) {
42 _cleanup_free_ char *cgroup_root = NULL, *cgroup = NULL;
43 int pid[2];
44 int r;
45
46 if (geteuid() != 0)
47 return (void) log_tests_skipped("not root");
48
49 if (cg_all_unified() <= 0)
50 return (void) log_tests_skipped("cgroups are not running in unified mode");
51
52 assert_se(cg_pid_get_path(NULL, 0, &cgroup_root) >= 0);
53
54 /* Create another cgroup below this one for the pids we forked off. We need this to be managed
55 * by the test so that pid1 doesn't delete it before we can read the xattrs. */
56 cgroup = path_join(cgroup_root, "oomdkilltest");
57 assert_se(cgroup);
58 assert_se(cg_create(SYSTEMD_CGROUP_CONTROLLER, cgroup) >= 0);
59
60 /* If we don't have permissions to set xattrs we're likely in a userns or missing capabilities */
61 r = cg_set_xattr(SYSTEMD_CGROUP_CONTROLLER, cgroup, "user.oomd_test", "test", 4, 0);
62 if (ERRNO_IS_PRIVILEGE(r) || ERRNO_IS_NOT_SUPPORTED(r))
63 return (void) log_tests_skipped("Cannot set user xattrs");
64
65 /* Do this twice to also check the increment behavior on the xattrs */
66 for (int i = 0; i < 2; i++) {
67 _cleanup_free_ char *v = NULL;
68
69 for (int j = 0; j < 2; j++) {
70 pid[j] = fork_and_sleep(5);
71 assert_se(cg_attach(SYSTEMD_CGROUP_CONTROLLER, cgroup, pid[j]) >= 0);
72 }
73
74 r = oomd_cgroup_kill(cgroup, false /* recurse */, false /* dry run */);
75 if (r <= 0) {
76 log_debug_errno(r, "Failed to kill processes under %s: %m", cgroup);
77 abort();
78 }
79
80 assert_se(cg_get_xattr_malloc(SYSTEMD_CGROUP_CONTROLLER, cgroup, "user.oomd_ooms", &v) >= 0);
81 assert_se(streq(v, i == 0 ? "1" : "2"));
82 v = mfree(v);
83
84 /* Wait a bit since processes may take some time to be cleaned up. */
85 sleep(2);
86 assert_se(cg_is_empty(SYSTEMD_CGROUP_CONTROLLER, cgroup) == true);
87
88 assert_se(cg_get_xattr_malloc(SYSTEMD_CGROUP_CONTROLLER, cgroup, "user.oomd_kill", &v) >= 0);
89 assert_se(streq(v, i == 0 ? "2" : "4"));
90 }
91 }
92
93 static void test_oomd_cgroup_context_acquire_and_insert(void) {
94 _cleanup_hashmap_free_ Hashmap *h1 = NULL, *h2 = NULL;
95 _cleanup_(oomd_cgroup_context_freep) OomdCGroupContext *ctx = NULL;
96 _cleanup_free_ char *cgroup = NULL;
97 ManagedOOMPreference root_pref;
98 OomdCGroupContext *c1, *c2;
99 CGroupMask mask;
100 bool test_xattrs;
101 int root_xattrs, r;
102
103 if (geteuid() != 0)
104 return (void) log_tests_skipped("not root");
105
106 if (!is_pressure_supported())
107 return (void) log_tests_skipped("system does not support pressure");
108
109 if (cg_all_unified() <= 0)
110 return (void) log_tests_skipped("cgroups are not running in unified mode");
111
112 assert_se(cg_mask_supported(&mask) >= 0);
113
114 if (!FLAGS_SET(mask, CGROUP_MASK_MEMORY))
115 return (void) log_tests_skipped("cgroup memory controller is not available");
116
117 assert_se(cg_pid_get_path(NULL, 0, &cgroup) >= 0);
118
119 /* If we don't have permissions to set xattrs we're likely in a userns or missing capabilities
120 * so skip the xattr portions of the test. */
121 r = cg_set_xattr(SYSTEMD_CGROUP_CONTROLLER, cgroup, "user.oomd_test", "1", 1, 0);
122 test_xattrs = !ERRNO_IS_PRIVILEGE(r) && !ERRNO_IS_NOT_SUPPORTED(r);
123
124 if (test_xattrs) {
125 assert_se(cg_set_xattr(SYSTEMD_CGROUP_CONTROLLER, cgroup, "user.oomd_omit", "1", 1, 0) >= 0);
126 assert_se(cg_set_xattr(SYSTEMD_CGROUP_CONTROLLER, cgroup, "user.oomd_avoid", "1", 1, 0) >= 0);
127 }
128
129 assert_se(oomd_cgroup_context_acquire(cgroup, &ctx) == 0);
130
131 assert_se(streq(ctx->path, cgroup));
132 assert_se(ctx->current_memory_usage > 0);
133 assert_se(ctx->memory_min == 0);
134 assert_se(ctx->memory_low == 0);
135 assert_se(ctx->swap_usage == 0);
136 assert_se(ctx->last_pgscan == 0);
137 assert_se(ctx->pgscan == 0);
138 /* omit takes precedence over avoid when both are set to true */
139 if (test_xattrs)
140 assert_se(ctx->preference == MANAGED_OOM_PREFERENCE_OMIT);
141 else
142 assert_se(ctx->preference == MANAGED_OOM_PREFERENCE_NONE);
143 ctx = oomd_cgroup_context_free(ctx);
144
145 /* also check when only avoid is set to true */
146 if (test_xattrs) {
147 assert_se(cg_set_xattr(SYSTEMD_CGROUP_CONTROLLER, cgroup, "user.oomd_omit", "0", 1, 0) >= 0);
148 assert_se(cg_set_xattr(SYSTEMD_CGROUP_CONTROLLER, cgroup, "user.oomd_avoid", "1", 1, 0) >= 0);
149 }
150 assert_se(oomd_cgroup_context_acquire(cgroup, &ctx) == 0);
151 if (test_xattrs)
152 assert_se(ctx->preference == MANAGED_OOM_PREFERENCE_AVOID);
153 ctx = oomd_cgroup_context_free(ctx);
154
155 /* Test the root cgroup */
156 /* Root cgroup is live and not made on demand like the cgroup the test runs in. It can have varying
157 * xattrs set already so let's read in the booleans first to get the final preference value. */
158 root_xattrs = cg_get_xattr_bool(SYSTEMD_CGROUP_CONTROLLER, "", "user.oomd_omit");
159 root_pref = root_xattrs > 0 ? MANAGED_OOM_PREFERENCE_OMIT : MANAGED_OOM_PREFERENCE_NONE;
160 root_xattrs = cg_get_xattr_bool(SYSTEMD_CGROUP_CONTROLLER, "", "user.oomd_avoid");
161 root_pref = root_xattrs > 0 ? MANAGED_OOM_PREFERENCE_AVOID : MANAGED_OOM_PREFERENCE_NONE;
162 assert_se(oomd_cgroup_context_acquire("", &ctx) == 0);
163 assert_se(streq(ctx->path, "/"));
164 assert_se(ctx->current_memory_usage > 0);
165 assert_se(ctx->preference == root_pref);
166
167 /* Test hashmap inserts */
168 assert_se(h1 = hashmap_new(&oomd_cgroup_ctx_hash_ops));
169 assert_se(oomd_insert_cgroup_context(NULL, h1, cgroup) == 0);
170 c1 = hashmap_get(h1, cgroup);
171 assert_se(c1);
172 assert_se(oomd_insert_cgroup_context(NULL, h1, cgroup) == -EEXIST);
173
174 /* make sure certain values from h1 get updated in h2 */
175 c1->pgscan = UINT64_MAX;
176 c1->mem_pressure_limit = 6789;
177 c1->mem_pressure_limit_hit_start = 42;
178 c1->last_had_mem_reclaim = 888;
179 assert_se(h2 = hashmap_new(&oomd_cgroup_ctx_hash_ops));
180 assert_se(oomd_insert_cgroup_context(h1, h2, cgroup) == 0);
181 c1 = hashmap_get(h1, cgroup);
182 c2 = hashmap_get(h2, cgroup);
183 assert_se(c1);
184 assert_se(c2);
185 assert_se(c1 != c2);
186 assert_se(c2->last_pgscan == UINT64_MAX);
187 assert_se(c2->mem_pressure_limit == 6789);
188 assert_se(c2->mem_pressure_limit_hit_start == 42);
189 assert_se(c2->last_had_mem_reclaim == 888); /* assumes the live pgscan is less than UINT64_MAX */
190
191 /* Assert that avoid/omit are not set if the cgroup is not owned by root */
192 if (test_xattrs) {
193 ctx = oomd_cgroup_context_free(ctx);
194 assert_se(cg_set_access(SYSTEMD_CGROUP_CONTROLLER, cgroup, 65534, 0) >= 0);
195 assert_se(oomd_cgroup_context_acquire(cgroup, &ctx) == 0);
196 assert_se(ctx->preference == MANAGED_OOM_PREFERENCE_NONE);
197 }
198 }
199
200 static void test_oomd_update_cgroup_contexts_between_hashmaps(void) {
201 _cleanup_hashmap_free_ Hashmap *h_old = NULL, *h_new = NULL;
202 OomdCGroupContext *c_old, *c_new;
203 char **paths = STRV_MAKE("/0.slice",
204 "/1.slice");
205
206 OomdCGroupContext ctx_old[2] = {
207 { .path = paths[0],
208 .mem_pressure_limit = 5,
209 .mem_pressure_limit_hit_start = 777,
210 .last_had_mem_reclaim = 888,
211 .pgscan = 57 },
212 { .path = paths[1],
213 .mem_pressure_limit = 6,
214 .mem_pressure_limit_hit_start = 888,
215 .last_had_mem_reclaim = 888,
216 .pgscan = 42 },
217 };
218
219 OomdCGroupContext ctx_new[2] = {
220 { .path = paths[0],
221 .pgscan = 57 },
222 { .path = paths[1],
223 .pgscan = 101 },
224 };
225
226 assert_se(h_old = hashmap_new(&string_hash_ops));
227 assert_se(hashmap_put(h_old, paths[0], &ctx_old[0]) >= 0);
228 assert_se(hashmap_put(h_old, paths[1], &ctx_old[1]) >= 0);
229
230 assert_se(h_new = hashmap_new(&string_hash_ops));
231 assert_se(hashmap_put(h_new, paths[0], &ctx_new[0]) >= 0);
232 assert_se(hashmap_put(h_new, paths[1], &ctx_new[1]) >= 0);
233
234 oomd_update_cgroup_contexts_between_hashmaps(h_old, h_new);
235
236 assert_se(c_old = hashmap_get(h_old, "/0.slice"));
237 assert_se(c_new = hashmap_get(h_new, "/0.slice"));
238 assert_se(c_old->pgscan == c_new->last_pgscan);
239 assert_se(c_old->mem_pressure_limit == c_new->mem_pressure_limit);
240 assert_se(c_old->mem_pressure_limit_hit_start == c_new->mem_pressure_limit_hit_start);
241 assert_se(c_old->last_had_mem_reclaim == c_new->last_had_mem_reclaim);
242
243 assert_se(c_old = hashmap_get(h_old, "/1.slice"));
244 assert_se(c_new = hashmap_get(h_new, "/1.slice"));
245 assert_se(c_old->pgscan == c_new->last_pgscan);
246 assert_se(c_old->mem_pressure_limit == c_new->mem_pressure_limit);
247 assert_se(c_old->mem_pressure_limit_hit_start == c_new->mem_pressure_limit_hit_start);
248 assert_se(c_new->last_had_mem_reclaim > c_old->last_had_mem_reclaim);
249 }
250
251 static void test_oomd_system_context_acquire(void) {
252 _cleanup_(unlink_tempfilep) char path[] = "/oomdgetsysctxtestXXXXXX";
253 _cleanup_close_ int fd = -1;
254 OomdSystemContext ctx;
255
256 if (geteuid() != 0)
257 return (void) log_tests_skipped("not root");
258
259 assert_se((fd = mkostemp_safe(path)) >= 0);
260
261 assert_se(oomd_system_context_acquire("/verylikelynonexistentpath", &ctx) == -ENOENT);
262
263 assert_se(oomd_system_context_acquire(path, &ctx) == -EINVAL);
264
265 assert_se(write_string_file(path, "some\nwords\nacross\nmultiple\nlines", WRITE_STRING_FILE_CREATE) == 0);
266 assert_se(oomd_system_context_acquire(path, &ctx) == -EINVAL);
267
268 assert_se(write_string_file(path, "MemTotal: 32495256 kB trailing\n"
269 "MemFree: 9880512 kB data\n"
270 "SwapTotal: 8388604 kB is\n"
271 "SwapFree: 7604 kB bad\n", WRITE_STRING_FILE_CREATE) == 0);
272 assert_se(oomd_system_context_acquire(path, &ctx) == -EINVAL);
273
274 assert_se(write_string_file(path, "MemTotal: 32495256 kB\n"
275 "MemFree: 9880512 kB\n"
276 "MemAvailable: 21777088 kB\n"
277 "Buffers: 5968 kB\n"
278 "Cached: 14344796 kB\n"
279 "Unevictable: 740004 kB\n"
280 "Mlocked: 4484 kB\n"
281 "SwapTotal: 8388604 kB\n"
282 "SwapFree: 7604 kB\n", WRITE_STRING_FILE_CREATE) == 0);
283 assert_se(oomd_system_context_acquire(path, &ctx) == 0);
284 assert_se(ctx.mem_total == 33275142144);
285 assert_se(ctx.mem_used == 23157497856);
286 assert_se(ctx.swap_total == 8589930496);
287 assert_se(ctx.swap_used == 8582144000);
288 }
289
290 static void test_oomd_pressure_above(void) {
291 _cleanup_hashmap_free_ Hashmap *h1 = NULL, *h2 = NULL;
292 _cleanup_set_free_ Set *t1 = NULL, *t2 = NULL, *t3 = NULL;
293 OomdCGroupContext ctx[2] = {}, *c;
294 loadavg_t threshold;
295
296 assert_se(store_loadavg_fixed_point(80, 0, &threshold) == 0);
297
298 /* /herp.slice */
299 assert_se(store_loadavg_fixed_point(99, 99, &(ctx[0].memory_pressure.avg10)) == 0);
300 assert_se(store_loadavg_fixed_point(99, 99, &(ctx[0].memory_pressure.avg60)) == 0);
301 assert_se(store_loadavg_fixed_point(99, 99, &(ctx[0].memory_pressure.avg300)) == 0);
302 ctx[0].mem_pressure_limit = threshold;
303
304 /* /derp.slice */
305 assert_se(store_loadavg_fixed_point(1, 11, &(ctx[1].memory_pressure.avg10)) == 0);
306 assert_se(store_loadavg_fixed_point(1, 11, &(ctx[1].memory_pressure.avg60)) == 0);
307 assert_se(store_loadavg_fixed_point(1, 11, &(ctx[1].memory_pressure.avg300)) == 0);
308 ctx[1].mem_pressure_limit = threshold;
309
310 /* High memory pressure */
311 assert_se(h1 = hashmap_new(&string_hash_ops));
312 assert_se(hashmap_put(h1, "/herp.slice", &ctx[0]) >= 0);
313 assert_se(oomd_pressure_above(h1, 0 /* duration */, &t1) == 1);
314 assert_se(set_contains(t1, &ctx[0]));
315 assert_se(c = hashmap_get(h1, "/herp.slice"));
316 assert_se(c->mem_pressure_limit_hit_start > 0);
317
318 /* Low memory pressure */
319 assert_se(h2 = hashmap_new(&string_hash_ops));
320 assert_se(hashmap_put(h2, "/derp.slice", &ctx[1]) >= 0);
321 assert_se(oomd_pressure_above(h2, 0 /* duration */, &t2) == 0);
322 assert_se(!t2);
323 assert_se(c = hashmap_get(h2, "/derp.slice"));
324 assert_se(c->mem_pressure_limit_hit_start == 0);
325
326 /* High memory pressure w/ multiple cgroups */
327 assert_se(hashmap_put(h1, "/derp.slice", &ctx[1]) >= 0);
328 assert_se(oomd_pressure_above(h1, 0 /* duration */, &t3) == 1);
329 assert_se(set_contains(t3, &ctx[0]));
330 assert_se(set_size(t3) == 1);
331 assert_se(c = hashmap_get(h1, "/herp.slice"));
332 assert_se(c->mem_pressure_limit_hit_start > 0);
333 assert_se(c = hashmap_get(h1, "/derp.slice"));
334 assert_se(c->mem_pressure_limit_hit_start == 0);
335 }
336
337 static void test_oomd_mem_and_swap_free_below(void) {
338 OomdSystemContext ctx = (OomdSystemContext) {
339 .mem_total = 20971512 * 1024U,
340 .mem_used = 3310136 * 1024U,
341 .swap_total = 20971512 * 1024U,
342 .swap_used = 20971440 * 1024U,
343 };
344 assert_se(oomd_mem_free_below(&ctx, 2000) == false);
345 assert_se(oomd_swap_free_below(&ctx, 2000) == true);
346
347 ctx = (OomdSystemContext) {
348 .mem_total = 20971512 * 1024U,
349 .mem_used = 20971440 * 1024U,
350 .swap_total = 20971512 * 1024U,
351 .swap_used = 3310136 * 1024U,
352 };
353 assert_se(oomd_mem_free_below(&ctx, 2000) == true);
354 assert_se(oomd_swap_free_below(&ctx, 2000) == false);
355
356 ctx = (OomdSystemContext) {
357 .mem_total = 0,
358 .mem_used = 0,
359 .swap_total = 0,
360 .swap_used = 0,
361 };
362 assert_se(oomd_mem_free_below(&ctx, 2000) == false);
363 assert_se(oomd_swap_free_below(&ctx, 2000) == false);
364 }
365
366 static void test_oomd_sort_cgroups(void) {
367 _cleanup_hashmap_free_ Hashmap *h = NULL;
368 _cleanup_free_ OomdCGroupContext **sorted_cgroups;
369 char **paths = STRV_MAKE("/herp.slice",
370 "/herp.slice/derp.scope",
371 "/herp.slice/derp.scope/sheep.service",
372 "/zupa.slice",
373 "/boop.slice",
374 "/omitted.slice",
375 "/avoid.slice");
376
377 OomdCGroupContext ctx[7] = {
378 { .path = paths[0],
379 .swap_usage = 20,
380 .last_pgscan = 0,
381 .pgscan = 33,
382 .current_memory_usage = 10 },
383 { .path = paths[1],
384 .swap_usage = 60,
385 .last_pgscan = 33,
386 .pgscan = 1,
387 .current_memory_usage = 20 },
388 { .path = paths[2],
389 .swap_usage = 40,
390 .last_pgscan = 1,
391 .pgscan = 33,
392 .current_memory_usage = 40 },
393 { .path = paths[3],
394 .swap_usage = 10,
395 .last_pgscan = 33,
396 .pgscan = 2,
397 .current_memory_usage = 10 },
398 { .path = paths[4],
399 .swap_usage = 11,
400 .last_pgscan = 33,
401 .pgscan = 33,
402 .current_memory_usage = 10 },
403 { .path = paths[5],
404 .swap_usage = 90,
405 .last_pgscan = 0,
406 .pgscan = UINT64_MAX,
407 .preference = MANAGED_OOM_PREFERENCE_OMIT },
408 { .path = paths[6],
409 .swap_usage = 99,
410 .last_pgscan = 0,
411 .pgscan = UINT64_MAX,
412 .preference = MANAGED_OOM_PREFERENCE_AVOID },
413 };
414
415 assert_se(h = hashmap_new(&string_hash_ops));
416
417 assert_se(hashmap_put(h, "/herp.slice", &ctx[0]) >= 0);
418 assert_se(hashmap_put(h, "/herp.slice/derp.scope", &ctx[1]) >= 0);
419 assert_se(hashmap_put(h, "/herp.slice/derp.scope/sheep.service", &ctx[2]) >= 0);
420 assert_se(hashmap_put(h, "/zupa.slice", &ctx[3]) >= 0);
421 assert_se(hashmap_put(h, "/boop.slice", &ctx[4]) >= 0);
422 assert_se(hashmap_put(h, "/omitted.slice", &ctx[5]) >= 0);
423 assert_se(hashmap_put(h, "/avoid.slice", &ctx[6]) >= 0);
424
425 assert_se(oomd_sort_cgroup_contexts(h, compare_swap_usage, NULL, &sorted_cgroups) == 6);
426 assert_se(sorted_cgroups[0] == &ctx[1]);
427 assert_se(sorted_cgroups[1] == &ctx[2]);
428 assert_se(sorted_cgroups[2] == &ctx[0]);
429 assert_se(sorted_cgroups[3] == &ctx[4]);
430 assert_se(sorted_cgroups[4] == &ctx[3]);
431 assert_se(sorted_cgroups[5] == &ctx[6]);
432 sorted_cgroups = mfree(sorted_cgroups);
433
434 assert_se(oomd_sort_cgroup_contexts(h, compare_pgscan_rate_and_memory_usage, NULL, &sorted_cgroups) == 6);
435 assert_se(sorted_cgroups[0] == &ctx[0]);
436 assert_se(sorted_cgroups[1] == &ctx[2]);
437 assert_se(sorted_cgroups[2] == &ctx[3]);
438 assert_se(sorted_cgroups[3] == &ctx[1]);
439 assert_se(sorted_cgroups[4] == &ctx[4]);
440 assert_se(sorted_cgroups[5] == &ctx[6]);
441 sorted_cgroups = mfree(sorted_cgroups);
442
443 assert_se(oomd_sort_cgroup_contexts(h, compare_pgscan_rate_and_memory_usage, "/herp.slice/derp.scope", &sorted_cgroups) == 2);
444 assert_se(sorted_cgroups[0] == &ctx[2]);
445 assert_se(sorted_cgroups[1] == &ctx[1]);
446 assert_se(sorted_cgroups[2] == 0);
447 assert_se(sorted_cgroups[3] == 0);
448 assert_se(sorted_cgroups[4] == 0);
449 assert_se(sorted_cgroups[5] == 0);
450 assert_se(sorted_cgroups[6] == 0);
451 sorted_cgroups = mfree(sorted_cgroups);
452 }
453
454 int main(void) {
455 int r;
456
457 test_setup_logging(LOG_DEBUG);
458
459 test_oomd_update_cgroup_contexts_between_hashmaps();
460 test_oomd_system_context_acquire();
461 test_oomd_pressure_above();
462 test_oomd_mem_and_swap_free_below();
463 test_oomd_sort_cgroups();
464
465 /* The following tests operate on live cgroups */
466
467 r = enter_cgroup_root(NULL);
468 if (r < 0)
469 return log_tests_skipped_errno(r, "failed to enter a test cgroup scope");
470
471 test_oomd_cgroup_kill();
472 test_oomd_cgroup_context_acquire_and_insert();
473
474 return 0;
475 }