1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
5 #include "alloc-util.h"
6 #include "cgroup-setup.h"
7 #include "cgroup-util.h"
10 #include "oomd-util.h"
11 #include "parse-util.h"
12 #include "path-util.h"
13 #include "string-util.h"
17 static int fork_and_sleep(unsigned sleep_min
) {
18 usec_t n
, timeout
, ts
;
24 timeout
= sleep_min
* USEC_PER_MINUTE
;
25 ts
= now(CLOCK_MONOTONIC
);
27 n
= now(CLOCK_MONOTONIC
);
28 if (ts
+ timeout
< n
) {
29 log_error("Child timed out waiting to be killed");
39 static void test_oomd_cgroup_kill(void) {
40 _cleanup_free_
char *cgroup_root
= NULL
, *cgroup
= NULL
;
45 return (void) log_tests_skipped("not root");
47 if (cg_all_unified() <= 0)
48 return (void) log_tests_skipped("cgroups are not running in unified mode");
50 assert_se(cg_pid_get_path(NULL
, 0, &cgroup_root
) >= 0);
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");
56 assert_se(cg_create(SYSTEMD_CGROUP_CONTROLLER
, cgroup
) >= 0);
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");
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
;
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);
72 r
= oomd_cgroup_kill(cgroup
, false /* recurse */, false /* dry run */);
74 log_debug_errno(r
, "Failed to kill processes under %s: %m", cgroup
);
78 /* Wait a bit since processes may take some time to be cleaned up. */
80 assert_se(cg_is_empty(SYSTEMD_CGROUP_CONTROLLER
, cgroup
) == true);
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);
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
;
97 return (void) log_tests_skipped("not root");
99 if (!is_pressure_supported())
100 return (void) log_tests_skipped("system does not support pressure");
102 if (cg_all_unified() <= 0)
103 return (void) log_tests_skipped("cgroups are not running in unified mode");
105 assert_se(cg_pid_get_path(NULL
, 0, &cgroup
) >= 0);
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
);
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);
117 assert_se(oomd_cgroup_context_acquire(cgroup
, &ctx
) == 0);
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 */
128 assert_se(ctx
->preference
== MANAGED_OOM_PREFERENCE_OMIT
);
130 assert_se(ctx
->preference
== MANAGED_OOM_PREFERENCE_NONE
);
131 ctx
= oomd_cgroup_context_free(ctx
);
133 /* also check when only avoid is set to true */
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);
138 assert_se(oomd_cgroup_context_acquire(cgroup
, &ctx
) == 0);
140 assert_se(ctx
->preference
== MANAGED_OOM_PREFERENCE_AVOID
);
141 ctx
= oomd_cgroup_context_free(ctx
);
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
);
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
);
160 assert_se(oomd_insert_cgroup_context(NULL
, h1
, cgroup
) == -EEXIST
);
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
);
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 */
179 /* Assert that avoid/omit are not set if the cgroup is not owned by root */
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
);
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",
194 OomdCGroupContext ctx_old
[2] = {
196 .mem_pressure_limit
= 5,
197 .mem_pressure_limit_hit_start
= 777,
198 .last_had_mem_reclaim
= 888,
201 .mem_pressure_limit
= 6,
202 .mem_pressure_limit_hit_start
= 888,
203 .last_had_mem_reclaim
= 888,
207 OomdCGroupContext ctx_new
[2] = {
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);
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);
222 oomd_update_cgroup_contexts_between_hashmaps(h_old
, h_new
);
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
);
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
);
239 static void test_oomd_system_context_acquire(void) {
240 _cleanup_(unlink_tempfilep
) char path
[] = "/oomdgetsysctxtestXXXXXX";
241 OomdSystemContext ctx
;
244 return (void) log_tests_skipped("not root");
246 assert_se(mkstemp(path
));
248 assert_se(oomd_system_context_acquire("/verylikelynonexistentpath", &ctx
) == -ENOENT
);
250 assert_se(oomd_system_context_acquire(path
, &ctx
) == -EINVAL
);
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
);
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
);
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
);
265 assert_se(write_string_file(path
, "MemTotal: 32495256 kB\n"
266 "MemFree: 9880512 kB\n"
267 "MemAvailable: 21777088 kB\n"
269 "Cached: 14344796 kB\n"
270 "Unevictable: 740004 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);
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
;
285 assert_se(store_loadavg_fixed_point(80, 0, &threshold
) == 0);
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
;
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
;
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);
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);
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);
327 static void test_oomd_swap_free_below(void) {
328 OomdSystemContext ctx
= (OomdSystemContext
) {
329 .swap_total
= 20971512 * 1024U,
330 .swap_used
= 20971440 * 1024U,
332 assert_se(oomd_swap_free_below(&ctx
, 2000) == true);
334 ctx
= (OomdSystemContext
) {
335 .swap_total
= 20971512 * 1024U,
336 .swap_used
= 3310136 * 1024U,
338 assert_se(oomd_swap_free_below(&ctx
, 2000) == false);
340 ctx
= (OomdSystemContext
) {
344 assert_se(oomd_swap_free_below(&ctx
, 2000) == false);
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",
358 OomdCGroupContext ctx
[7] = {
363 .current_memory_usage
= 10 },
368 .current_memory_usage
= 20 },
373 .current_memory_usage
= 40 },
378 .current_memory_usage
= 10 },
383 .current_memory_usage
= 10 },
387 .pgscan
= UINT64_MAX
,
388 .preference
= MANAGED_OOM_PREFERENCE_OMIT
},
392 .pgscan
= UINT64_MAX
,
393 .preference
= MANAGED_OOM_PREFERENCE_AVOID
},
396 assert_se(h
= hashmap_new(&string_hash_ops
));
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);
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
);
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
);
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
);
438 test_setup_logging(LOG_DEBUG
);
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();
446 /* The following tests operate on live cgroups */
448 r
= enter_cgroup_root(NULL
);
450 return log_tests_skipped_errno(r
, "failed to enter a test cgroup scope");
452 test_oomd_cgroup_kill();
453 test_oomd_cgroup_context_acquire_and_insert();