1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
5 #include "alloc-util.h"
6 #include "cgroup-setup.h"
7 #include "cgroup-util.h"
11 #include "oomd-util.h"
12 #include "parse-util.h"
13 #include "path-util.h"
14 #include "string-util.h"
17 #include "tmpfile-util.h"
19 static int fork_and_sleep(unsigned sleep_min
) {
20 usec_t n
, timeout
, ts
;
26 timeout
= sleep_min
* USEC_PER_MINUTE
;
27 ts
= now(CLOCK_MONOTONIC
);
29 n
= now(CLOCK_MONOTONIC
);
30 if (ts
+ timeout
< n
) {
31 log_error("Child timed out waiting to be killed");
41 static void test_oomd_cgroup_kill(void) {
42 _cleanup_free_
char *cgroup_root
= NULL
, *cgroup
= NULL
;
47 return (void) log_tests_skipped("not root");
49 if (cg_all_unified() <= 0)
50 return (void) log_tests_skipped("cgroups are not running in unified mode");
52 assert_se(cg_pid_get_path(NULL
, 0, &cgroup_root
) >= 0);
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");
58 assert_se(cg_create(SYSTEMD_CGROUP_CONTROLLER
, cgroup
) >= 0);
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");
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
;
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);
74 r
= oomd_cgroup_kill(cgroup
, false /* recurse */, false /* dry run */);
76 log_debug_errno(r
, "Failed to kill processes under %s: %m", cgroup
);
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"));
84 /* Wait a bit since processes may take some time to be cleaned up. */
86 assert_se(cg_is_empty(SYSTEMD_CGROUP_CONTROLLER
, cgroup
) == true);
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"));
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
;
104 return (void) log_tests_skipped("not root");
106 if (!is_pressure_supported())
107 return (void) log_tests_skipped("system does not support pressure");
109 if (cg_all_unified() <= 0)
110 return (void) log_tests_skipped("cgroups are not running in unified mode");
112 assert_se(cg_mask_supported(&mask
) >= 0);
114 if (!FLAGS_SET(mask
, CGROUP_MASK_MEMORY
))
115 return (void) log_tests_skipped("cgroup memory controller is not available");
117 assert_se(cg_pid_get_path(NULL
, 0, &cgroup
) >= 0);
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
);
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);
129 assert_se(oomd_cgroup_context_acquire(cgroup
, &ctx
) == 0);
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 */
140 assert_se(ctx
->preference
== MANAGED_OOM_PREFERENCE_OMIT
);
142 assert_se(ctx
->preference
== MANAGED_OOM_PREFERENCE_NONE
);
143 ctx
= oomd_cgroup_context_free(ctx
);
145 /* also check when only avoid is set to true */
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);
150 assert_se(oomd_cgroup_context_acquire(cgroup
, &ctx
) == 0);
152 assert_se(ctx
->preference
== MANAGED_OOM_PREFERENCE_AVOID
);
153 ctx
= oomd_cgroup_context_free(ctx
);
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
);
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
);
172 assert_se(oomd_insert_cgroup_context(NULL
, h1
, cgroup
) == -EEXIST
);
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
);
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 */
191 /* Assert that avoid/omit are not set if the cgroup is not owned by root */
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
);
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",
206 OomdCGroupContext ctx_old
[2] = {
208 .mem_pressure_limit
= 5,
209 .mem_pressure_limit_hit_start
= 777,
210 .last_had_mem_reclaim
= 888,
213 .mem_pressure_limit
= 6,
214 .mem_pressure_limit_hit_start
= 888,
215 .last_had_mem_reclaim
= 888,
219 OomdCGroupContext ctx_new
[2] = {
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);
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);
234 oomd_update_cgroup_contexts_between_hashmaps(h_old
, h_new
);
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
);
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
);
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
;
257 return (void) log_tests_skipped("not root");
259 assert_se((fd
= mkostemp_safe(path
)) >= 0);
261 assert_se(oomd_system_context_acquire("/verylikelynonexistentpath", &ctx
) == -ENOENT
);
263 assert_se(oomd_system_context_acquire(path
, &ctx
) == -EINVAL
);
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
);
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
);
274 assert_se(write_string_file(path
, "MemTotal: 32495256 kB\n"
275 "MemFree: 9880512 kB\n"
276 "MemAvailable: 21777088 kB\n"
278 "Cached: 14344796 kB\n"
279 "Unevictable: 740004 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);
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
;
296 assert_se(store_loadavg_fixed_point(80, 0, &threshold
) == 0);
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
;
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
;
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);
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);
323 assert_se(c
= hashmap_get(h2
, "/derp.slice"));
324 assert_se(c
->mem_pressure_limit_hit_start
== 0);
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);
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,
344 assert_se(oomd_mem_free_below(&ctx
, 2000) == false);
345 assert_se(oomd_swap_free_below(&ctx
, 2000) == true);
347 ctx
= (OomdSystemContext
) {
348 .mem_total
= 20971512 * 1024U,
349 .mem_used
= 20971440 * 1024U,
350 .swap_total
= 20971512 * 1024U,
351 .swap_used
= 3310136 * 1024U,
353 assert_se(oomd_mem_free_below(&ctx
, 2000) == true);
354 assert_se(oomd_swap_free_below(&ctx
, 2000) == false);
356 ctx
= (OomdSystemContext
) {
362 assert_se(oomd_mem_free_below(&ctx
, 2000) == false);
363 assert_se(oomd_swap_free_below(&ctx
, 2000) == false);
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",
377 OomdCGroupContext ctx
[7] = {
382 .current_memory_usage
= 10 },
387 .current_memory_usage
= 20 },
392 .current_memory_usage
= 40 },
397 .current_memory_usage
= 10 },
402 .current_memory_usage
= 10 },
406 .pgscan
= UINT64_MAX
,
407 .preference
= MANAGED_OOM_PREFERENCE_OMIT
},
411 .pgscan
= UINT64_MAX
,
412 .preference
= MANAGED_OOM_PREFERENCE_AVOID
},
415 assert_se(h
= hashmap_new(&string_hash_ops
));
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);
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
);
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
);
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
);
457 test_setup_logging(LOG_DEBUG
);
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();
465 /* The following tests operate on live cgroups */
467 r
= enter_cgroup_root(NULL
);
469 return log_tests_skipped_errno(r
, "failed to enter a test cgroup scope");
471 test_oomd_cgroup_kill();
472 test_oomd_cgroup_context_acquire_and_insert();