]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/oom/test-oomd-util.c
oomd: switch system context parsing to use /proc/meminfo
[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 "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
17 static 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);
26 for (;;) {
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
39 static void test_oomd_cgroup_kill(void) {
40 _cleanup_free_ char *cgroup_root = NULL, *cgroup = NULL;
41 int pid[2];
42 int r;
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);
56 assert_se(cg_create(SYSTEMD_CGROUP_CONTROLLER, cgroup) >= 0);
57
58 /* If we don't have permissions to set xattrs we're likely in a userns or missing capabilities */
59 r = cg_set_xattr(SYSTEMD_CGROUP_CONTROLLER, cgroup, "user.oomd_test", "test", 4, 0);
60 if (ERRNO_IS_PRIVILEGE(r) || ERRNO_IS_NOT_SUPPORTED(r))
61 return (void) log_tests_skipped("Cannot set user xattrs");
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;
66
67 for (int j = 0; j < 2; j++) {
68 pid[j] = fork_and_sleep(5);
69 assert_se(cg_attach(SYSTEMD_CGROUP_CONTROLLER, cgroup, pid[j]) >= 0);
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.oomd_kill", &v) >= 0);
83 assert_se(memcmp(v, i == 0 ? "2" : "4", 2) == 0);
84 }
85 }
86
87 static 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 ManagedOOMPreference root_pref;
92 OomdCGroupContext *c1, *c2;
93 bool test_xattrs;
94 int root_xattrs, r;
95
96 if (geteuid() != 0)
97 return (void) log_tests_skipped("not root");
98
99 if (!is_pressure_supported())
100 return (void) log_tests_skipped("system does not support pressure");
101
102 if (cg_all_unified() <= 0)
103 return (void) log_tests_skipped("cgroups are not running in unified mode");
104
105 assert_se(cg_pid_get_path(NULL, 0, &cgroup) >= 0);
106
107 /* If we don't have permissions to set xattrs we're likely in a userns or missing capabilities
108 * so skip the xattr portions of the test. */
109 r = cg_set_xattr(SYSTEMD_CGROUP_CONTROLLER, cgroup, "user.oomd_test", "1", 1, 0);
110 test_xattrs = !ERRNO_IS_PRIVILEGE(r) && !ERRNO_IS_NOT_SUPPORTED(r);
111
112 if (test_xattrs) {
113 assert_se(cg_set_xattr(SYSTEMD_CGROUP_CONTROLLER, cgroup, "user.oomd_omit", "1", 1, 0) >= 0);
114 assert_se(cg_set_xattr(SYSTEMD_CGROUP_CONTROLLER, cgroup, "user.oomd_avoid", "1", 1, 0) >= 0);
115 }
116
117 assert_se(oomd_cgroup_context_acquire(cgroup, &ctx) == 0);
118
119 assert_se(streq(ctx->path, cgroup));
120 assert_se(ctx->current_memory_usage > 0);
121 assert_se(ctx->memory_min == 0);
122 assert_se(ctx->memory_low == 0);
123 assert_se(ctx->swap_usage == 0);
124 assert_se(ctx->last_pgscan == 0);
125 assert_se(ctx->pgscan == 0);
126 /* omit takes precedence over avoid when both are set to true */
127 if (test_xattrs)
128 assert_se(ctx->preference == MANAGED_OOM_PREFERENCE_OMIT);
129 else
130 assert_se(ctx->preference == MANAGED_OOM_PREFERENCE_NONE);
131 ctx = oomd_cgroup_context_free(ctx);
132
133 /* also check when only avoid is set to true */
134 if (test_xattrs) {
135 assert_se(cg_set_xattr(SYSTEMD_CGROUP_CONTROLLER, cgroup, "user.oomd_omit", "0", 1, 0) >= 0);
136 assert_se(cg_set_xattr(SYSTEMD_CGROUP_CONTROLLER, cgroup, "user.oomd_avoid", "1", 1, 0) >= 0);
137 }
138 assert_se(oomd_cgroup_context_acquire(cgroup, &ctx) == 0);
139 if (test_xattrs)
140 assert_se(ctx->preference == MANAGED_OOM_PREFERENCE_AVOID);
141 ctx = oomd_cgroup_context_free(ctx);
142
143 /* Test the root cgroup */
144 /* Root cgroup is live and not made on demand like the cgroup the test runs in. It can have varying
145 * xattrs set already so let's read in the booleans first to get the final preference value. */
146 root_xattrs = cg_get_xattr_bool(SYSTEMD_CGROUP_CONTROLLER, "", "user.oomd_omit");
147 root_pref = root_xattrs > 0 ? MANAGED_OOM_PREFERENCE_OMIT : MANAGED_OOM_PREFERENCE_NONE;
148 root_xattrs = cg_get_xattr_bool(SYSTEMD_CGROUP_CONTROLLER, "", "user.oomd_avoid");
149 root_pref = root_xattrs > 0 ? MANAGED_OOM_PREFERENCE_AVOID : MANAGED_OOM_PREFERENCE_NONE;
150 assert_se(oomd_cgroup_context_acquire("", &ctx) == 0);
151 assert_se(streq(ctx->path, "/"));
152 assert_se(ctx->current_memory_usage > 0);
153 assert_se(ctx->preference == root_pref);
154
155 /* Test hashmap inserts */
156 assert_se(h1 = hashmap_new(&oomd_cgroup_ctx_hash_ops));
157 assert_se(oomd_insert_cgroup_context(NULL, h1, cgroup) == 0);
158 c1 = hashmap_get(h1, cgroup);
159 assert_se(c1);
160 assert_se(oomd_insert_cgroup_context(NULL, h1, cgroup) == -EEXIST);
161
162 /* make sure certain values from h1 get updated in h2 */
163 c1->pgscan = UINT64_MAX;
164 c1->mem_pressure_limit = 6789;
165 c1->mem_pressure_limit_hit_start = 42;
166 c1->last_had_mem_reclaim = 888;
167 assert_se(h2 = hashmap_new(&oomd_cgroup_ctx_hash_ops));
168 assert_se(oomd_insert_cgroup_context(h1, h2, cgroup) == 0);
169 c1 = hashmap_get(h1, cgroup);
170 c2 = hashmap_get(h2, cgroup);
171 assert_se(c1);
172 assert_se(c2);
173 assert_se(c1 != c2);
174 assert_se(c2->last_pgscan == UINT64_MAX);
175 assert_se(c2->mem_pressure_limit == 6789);
176 assert_se(c2->mem_pressure_limit_hit_start == 42);
177 assert_se(c2->last_had_mem_reclaim == 888); /* assumes the live pgscan is less than UINT64_MAX */
178
179 /* Assert that avoid/omit are not set if the cgroup is not owned by root */
180 if (test_xattrs) {
181 ctx = oomd_cgroup_context_free(ctx);
182 assert_se(cg_set_access(SYSTEMD_CGROUP_CONTROLLER, cgroup, 65534, 0) >= 0);
183 assert_se(oomd_cgroup_context_acquire(cgroup, &ctx) == 0);
184 assert_se(ctx->preference == MANAGED_OOM_PREFERENCE_NONE);
185 }
186 }
187
188 static void test_oomd_update_cgroup_contexts_between_hashmaps(void) {
189 _cleanup_hashmap_free_ Hashmap *h_old = NULL, *h_new = NULL;
190 OomdCGroupContext *c_old, *c_new;
191 char **paths = STRV_MAKE("/0.slice",
192 "/1.slice");
193
194 OomdCGroupContext ctx_old[2] = {
195 { .path = paths[0],
196 .mem_pressure_limit = 5,
197 .mem_pressure_limit_hit_start = 777,
198 .last_had_mem_reclaim = 888,
199 .pgscan = 57 },
200 { .path = paths[1],
201 .mem_pressure_limit = 6,
202 .mem_pressure_limit_hit_start = 888,
203 .last_had_mem_reclaim = 888,
204 .pgscan = 42 },
205 };
206
207 OomdCGroupContext ctx_new[2] = {
208 { .path = paths[0],
209 .pgscan = 57 },
210 { .path = paths[1],
211 .pgscan = 101 },
212 };
213
214 assert_se(h_old = hashmap_new(&string_hash_ops));
215 assert_se(hashmap_put(h_old, paths[0], &ctx_old[0]) >= 0);
216 assert_se(hashmap_put(h_old, paths[1], &ctx_old[1]) >= 0);
217
218 assert_se(h_new = hashmap_new(&string_hash_ops));
219 assert_se(hashmap_put(h_new, paths[0], &ctx_new[0]) >= 0);
220 assert_se(hashmap_put(h_new, paths[1], &ctx_new[1]) >= 0);
221
222 oomd_update_cgroup_contexts_between_hashmaps(h_old, h_new);
223
224 assert_se(c_old = hashmap_get(h_old, "/0.slice"));
225 assert_se(c_new = hashmap_get(h_new, "/0.slice"));
226 assert_se(c_old->pgscan == c_new->last_pgscan);
227 assert_se(c_old->mem_pressure_limit == c_new->mem_pressure_limit);
228 assert_se(c_old->mem_pressure_limit_hit_start == c_new->mem_pressure_limit_hit_start);
229 assert_se(c_old->last_had_mem_reclaim == c_new->last_had_mem_reclaim);
230
231 assert_se(c_old = hashmap_get(h_old, "/1.slice"));
232 assert_se(c_new = hashmap_get(h_new, "/1.slice"));
233 assert_se(c_old->pgscan == c_new->last_pgscan);
234 assert_se(c_old->mem_pressure_limit == c_new->mem_pressure_limit);
235 assert_se(c_old->mem_pressure_limit_hit_start == c_new->mem_pressure_limit_hit_start);
236 assert_se(c_new->last_had_mem_reclaim > c_old->last_had_mem_reclaim);
237 }
238
239 static void test_oomd_system_context_acquire(void) {
240 _cleanup_(unlink_tempfilep) char path[] = "/oomdgetsysctxtestXXXXXX";
241 OomdSystemContext ctx;
242
243 if (geteuid() != 0)
244 return (void) log_tests_skipped("not root");
245
246 assert_se(mkstemp(path));
247
248 assert_se(oomd_system_context_acquire("/verylikelynonexistentpath", &ctx) == -ENOENT);
249
250 assert_se(oomd_system_context_acquire(path, &ctx) == -EINVAL);
251
252 assert_se(write_string_file(path, "some\nwords\nacross\nmultiple\nlines", WRITE_STRING_FILE_CREATE) == 0);
253 assert_se(oomd_system_context_acquire(path, &ctx) == -EINVAL);
254
255 assert_se(write_string_file(path, "MemTotal: 32495256 kB trailing\n"
256 "MemFree: 9880512 kB data\n"
257 "SwapTotal: 8388604 kB is\n"
258 "SwapFree: 7604 kB bad\n", WRITE_STRING_FILE_CREATE) == 0);
259 assert_se(oomd_system_context_acquire(path, &ctx) == -EINVAL);
260
261 assert_se(oomd_system_context_acquire("/proc/meminfo", &ctx) == 0);
262 assert_se(ctx.swap_total > 0);
263 assert_se(ctx.swap_used <= ctx.swap_total);
264
265 assert_se(write_string_file(path, "MemTotal: 32495256 kB\n"
266 "MemFree: 9880512 kB\n"
267 "MemAvailable: 21777088 kB\n"
268 "Buffers: 5968 kB\n"
269 "Cached: 14344796 kB\n"
270 "Unevictable: 740004 kB\n"
271 "Mlocked: 4484 kB\n"
272 "SwapTotal: 8388604 kB\n"
273 "SwapFree: 7604 kB\n", WRITE_STRING_FILE_CREATE) == 0);
274 assert_se(oomd_system_context_acquire(path, &ctx) == 0);
275 assert_se(ctx.swap_total == 8589930496);
276 assert_se(ctx.swap_used == 8582144000);
277 }
278
279 static void test_oomd_pressure_above(void) {
280 _cleanup_hashmap_free_ Hashmap *h1 = NULL, *h2 = NULL;
281 _cleanup_set_free_ Set *t1 = NULL, *t2 = NULL, *t3 = NULL;
282 OomdCGroupContext ctx[2], *c;
283 loadavg_t threshold;
284
285 assert_se(store_loadavg_fixed_point(80, 0, &threshold) == 0);
286
287 /* /herp.slice */
288 assert_se(store_loadavg_fixed_point(99, 99, &(ctx[0].memory_pressure.avg10)) == 0);
289 assert_se(store_loadavg_fixed_point(99, 99, &(ctx[0].memory_pressure.avg60)) == 0);
290 assert_se(store_loadavg_fixed_point(99, 99, &(ctx[0].memory_pressure.avg300)) == 0);
291 ctx[0].mem_pressure_limit = threshold;
292
293 /* /derp.slice */
294 assert_se(store_loadavg_fixed_point(1, 11, &(ctx[1].memory_pressure.avg10)) == 0);
295 assert_se(store_loadavg_fixed_point(1, 11, &(ctx[1].memory_pressure.avg60)) == 0);
296 assert_se(store_loadavg_fixed_point(1, 11, &(ctx[1].memory_pressure.avg300)) == 0);
297 ctx[1].mem_pressure_limit = threshold;
298
299
300 /* High memory pressure */
301 assert_se(h1 = hashmap_new(&string_hash_ops));
302 assert_se(hashmap_put(h1, "/herp.slice", &ctx[0]) >= 0);
303 assert_se(oomd_pressure_above(h1, 0 /* duration */, &t1) == 1);
304 assert_se(set_contains(t1, &ctx[0]) == true);
305 assert_se(c = hashmap_get(h1, "/herp.slice"));
306 assert_se(c->mem_pressure_limit_hit_start > 0);
307
308 /* Low memory pressure */
309 assert_se(h2 = hashmap_new(&string_hash_ops));
310 assert_se(hashmap_put(h2, "/derp.slice", &ctx[1]) >= 0);
311 assert_se(oomd_pressure_above(h2, 0 /* duration */, &t2) == 0);
312 assert_se(t2 == NULL);
313 assert_se(c = hashmap_get(h2, "/derp.slice"));
314 assert_se(c->mem_pressure_limit_hit_start == 0);
315
316 /* High memory pressure w/ multiple cgroups */
317 assert_se(hashmap_put(h1, "/derp.slice", &ctx[1]) >= 0);
318 assert_se(oomd_pressure_above(h1, 0 /* duration */, &t3) == 1);
319 assert_se(set_contains(t3, &ctx[0]) == true);
320 assert_se(set_size(t3) == 1);
321 assert_se(c = hashmap_get(h1, "/herp.slice"));
322 assert_se(c->mem_pressure_limit_hit_start > 0);
323 assert_se(c = hashmap_get(h1, "/derp.slice"));
324 assert_se(c->mem_pressure_limit_hit_start == 0);
325 }
326
327 static void test_oomd_swap_free_below(void) {
328 OomdSystemContext ctx = (OomdSystemContext) {
329 .swap_total = 20971512 * 1024U,
330 .swap_used = 20971440 * 1024U,
331 };
332 assert_se(oomd_swap_free_below(&ctx, 2000) == true);
333
334 ctx = (OomdSystemContext) {
335 .swap_total = 20971512 * 1024U,
336 .swap_used = 3310136 * 1024U,
337 };
338 assert_se(oomd_swap_free_below(&ctx, 2000) == false);
339
340 ctx = (OomdSystemContext) {
341 .swap_total = 0,
342 .swap_used = 0,
343 };
344 assert_se(oomd_swap_free_below(&ctx, 2000) == false);
345 }
346
347 static void test_oomd_sort_cgroups(void) {
348 _cleanup_hashmap_free_ Hashmap *h = NULL;
349 _cleanup_free_ OomdCGroupContext **sorted_cgroups;
350 char **paths = STRV_MAKE("/herp.slice",
351 "/herp.slice/derp.scope",
352 "/herp.slice/derp.scope/sheep.service",
353 "/zupa.slice",
354 "/boop.slice",
355 "/omitted.slice",
356 "/avoid.slice");
357
358 OomdCGroupContext ctx[7] = {
359 { .path = paths[0],
360 .swap_usage = 20,
361 .last_pgscan = 0,
362 .pgscan = 33,
363 .current_memory_usage = 10 },
364 { .path = paths[1],
365 .swap_usage = 60,
366 .last_pgscan = 33,
367 .pgscan = 1,
368 .current_memory_usage = 20 },
369 { .path = paths[2],
370 .swap_usage = 40,
371 .last_pgscan = 1,
372 .pgscan = 33,
373 .current_memory_usage = 40 },
374 { .path = paths[3],
375 .swap_usage = 10,
376 .last_pgscan = 33,
377 .pgscan = 2,
378 .current_memory_usage = 10 },
379 { .path = paths[4],
380 .swap_usage = 11,
381 .last_pgscan = 33,
382 .pgscan = 33,
383 .current_memory_usage = 10 },
384 { .path = paths[5],
385 .swap_usage = 90,
386 .last_pgscan = 0,
387 .pgscan = UINT64_MAX,
388 .preference = MANAGED_OOM_PREFERENCE_OMIT },
389 { .path = paths[6],
390 .swap_usage = 99,
391 .last_pgscan = 0,
392 .pgscan = UINT64_MAX,
393 .preference = MANAGED_OOM_PREFERENCE_AVOID },
394 };
395
396 assert_se(h = hashmap_new(&string_hash_ops));
397
398 assert_se(hashmap_put(h, "/herp.slice", &ctx[0]) >= 0);
399 assert_se(hashmap_put(h, "/herp.slice/derp.scope", &ctx[1]) >= 0);
400 assert_se(hashmap_put(h, "/herp.slice/derp.scope/sheep.service", &ctx[2]) >= 0);
401 assert_se(hashmap_put(h, "/zupa.slice", &ctx[3]) >= 0);
402 assert_se(hashmap_put(h, "/boop.slice", &ctx[4]) >= 0);
403 assert_se(hashmap_put(h, "/omitted.slice", &ctx[5]) >= 0);
404 assert_se(hashmap_put(h, "/avoid.slice", &ctx[6]) >= 0);
405
406 assert_se(oomd_sort_cgroup_contexts(h, compare_swap_usage, NULL, &sorted_cgroups) == 6);
407 assert_se(sorted_cgroups[0] == &ctx[1]);
408 assert_se(sorted_cgroups[1] == &ctx[2]);
409 assert_se(sorted_cgroups[2] == &ctx[0]);
410 assert_se(sorted_cgroups[3] == &ctx[4]);
411 assert_se(sorted_cgroups[4] == &ctx[3]);
412 assert_se(sorted_cgroups[5] == &ctx[6]);
413 sorted_cgroups = mfree(sorted_cgroups);
414
415 assert_se(oomd_sort_cgroup_contexts(h, compare_pgscan_rate_and_memory_usage, NULL, &sorted_cgroups) == 6);
416 assert_se(sorted_cgroups[0] == &ctx[0]);
417 assert_se(sorted_cgroups[1] == &ctx[2]);
418 assert_se(sorted_cgroups[2] == &ctx[3]);
419 assert_se(sorted_cgroups[3] == &ctx[1]);
420 assert_se(sorted_cgroups[4] == &ctx[4]);
421 assert_se(sorted_cgroups[5] == &ctx[6]);
422 sorted_cgroups = mfree(sorted_cgroups);
423
424 assert_se(oomd_sort_cgroup_contexts(h, compare_pgscan_rate_and_memory_usage, "/herp.slice/derp.scope", &sorted_cgroups) == 2);
425 assert_se(sorted_cgroups[0] == &ctx[2]);
426 assert_se(sorted_cgroups[1] == &ctx[1]);
427 assert_se(sorted_cgroups[2] == 0);
428 assert_se(sorted_cgroups[3] == 0);
429 assert_se(sorted_cgroups[4] == 0);
430 assert_se(sorted_cgroups[5] == 0);
431 assert_se(sorted_cgroups[6] == 0);
432 sorted_cgroups = mfree(sorted_cgroups);
433 }
434
435 int main(void) {
436 int r;
437
438 test_setup_logging(LOG_DEBUG);
439
440 test_oomd_update_cgroup_contexts_between_hashmaps();
441 test_oomd_system_context_acquire();
442 test_oomd_pressure_above();
443 test_oomd_swap_free_below();
444 test_oomd_sort_cgroups();
445
446 /* The following tests operate on live cgroups */
447
448 r = enter_cgroup_root(NULL);
449 if (r < 0)
450 return log_tests_skipped_errno(r, "failed to enter a test cgroup scope");
451
452 test_oomd_cgroup_kill();
453 test_oomd_cgroup_context_acquire_and_insert();
454
455 return 0;
456 }