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
;
98 return (void) log_tests_skipped("not root");
100 if (!is_pressure_supported())
101 return (void) log_tests_skipped("system does not support pressure");
103 if (cg_all_unified() <= 0)
104 return (void) log_tests_skipped("cgroups are not running in unified mode");
106 assert_se(cg_mask_supported(&mask
) >= 0);
108 if (!FLAGS_SET(mask
, CGROUP_MASK_MEMORY
))
109 return (void) log_tests_skipped("cgroup memory controller is not available");
111 assert_se(cg_pid_get_path(NULL
, 0, &cgroup
) >= 0);
113 /* If we don't have permissions to set xattrs we're likely in a userns or missing capabilities
114 * so skip the xattr portions of the test. */
115 r
= cg_set_xattr(SYSTEMD_CGROUP_CONTROLLER
, cgroup
, "user.oomd_test", "1", 1, 0);
116 test_xattrs
= !ERRNO_IS_PRIVILEGE(r
) && !ERRNO_IS_NOT_SUPPORTED(r
);
119 assert_se(cg_set_xattr(SYSTEMD_CGROUP_CONTROLLER
, cgroup
, "user.oomd_omit", "1", 1, 0) >= 0);
120 assert_se(cg_set_xattr(SYSTEMD_CGROUP_CONTROLLER
, cgroup
, "user.oomd_avoid", "1", 1, 0) >= 0);
123 assert_se(oomd_cgroup_context_acquire(cgroup
, &ctx
) == 0);
125 assert_se(streq(ctx
->path
, cgroup
));
126 assert_se(ctx
->current_memory_usage
> 0);
127 assert_se(ctx
->memory_min
== 0);
128 assert_se(ctx
->memory_low
== 0);
129 assert_se(ctx
->swap_usage
== 0);
130 assert_se(ctx
->last_pgscan
== 0);
131 assert_se(ctx
->pgscan
== 0);
132 /* omit takes precedence over avoid when both are set to true */
134 assert_se(ctx
->preference
== MANAGED_OOM_PREFERENCE_OMIT
);
136 assert_se(ctx
->preference
== MANAGED_OOM_PREFERENCE_NONE
);
137 ctx
= oomd_cgroup_context_free(ctx
);
139 /* also check when only avoid is set to true */
141 assert_se(cg_set_xattr(SYSTEMD_CGROUP_CONTROLLER
, cgroup
, "user.oomd_omit", "0", 1, 0) >= 0);
142 assert_se(cg_set_xattr(SYSTEMD_CGROUP_CONTROLLER
, cgroup
, "user.oomd_avoid", "1", 1, 0) >= 0);
144 assert_se(oomd_cgroup_context_acquire(cgroup
, &ctx
) == 0);
146 assert_se(ctx
->preference
== MANAGED_OOM_PREFERENCE_AVOID
);
147 ctx
= oomd_cgroup_context_free(ctx
);
149 /* Test the root cgroup */
150 /* Root cgroup is live and not made on demand like the cgroup the test runs in. It can have varying
151 * xattrs set already so let's read in the booleans first to get the final preference value. */
152 root_xattrs
= cg_get_xattr_bool(SYSTEMD_CGROUP_CONTROLLER
, "", "user.oomd_omit");
153 root_pref
= root_xattrs
> 0 ? MANAGED_OOM_PREFERENCE_OMIT
: MANAGED_OOM_PREFERENCE_NONE
;
154 root_xattrs
= cg_get_xattr_bool(SYSTEMD_CGROUP_CONTROLLER
, "", "user.oomd_avoid");
155 root_pref
= root_xattrs
> 0 ? MANAGED_OOM_PREFERENCE_AVOID
: MANAGED_OOM_PREFERENCE_NONE
;
156 assert_se(oomd_cgroup_context_acquire("", &ctx
) == 0);
157 assert_se(streq(ctx
->path
, "/"));
158 assert_se(ctx
->current_memory_usage
> 0);
159 assert_se(ctx
->preference
== root_pref
);
161 /* Test hashmap inserts */
162 assert_se(h1
= hashmap_new(&oomd_cgroup_ctx_hash_ops
));
163 assert_se(oomd_insert_cgroup_context(NULL
, h1
, cgroup
) == 0);
164 c1
= hashmap_get(h1
, cgroup
);
166 assert_se(oomd_insert_cgroup_context(NULL
, h1
, cgroup
) == -EEXIST
);
168 /* make sure certain values from h1 get updated in h2 */
169 c1
->pgscan
= UINT64_MAX
;
170 c1
->mem_pressure_limit
= 6789;
171 c1
->mem_pressure_limit_hit_start
= 42;
172 c1
->last_had_mem_reclaim
= 888;
173 assert_se(h2
= hashmap_new(&oomd_cgroup_ctx_hash_ops
));
174 assert_se(oomd_insert_cgroup_context(h1
, h2
, cgroup
) == 0);
175 c1
= hashmap_get(h1
, cgroup
);
176 c2
= hashmap_get(h2
, cgroup
);
180 assert_se(c2
->last_pgscan
== UINT64_MAX
);
181 assert_se(c2
->mem_pressure_limit
== 6789);
182 assert_se(c2
->mem_pressure_limit_hit_start
== 42);
183 assert_se(c2
->last_had_mem_reclaim
== 888); /* assumes the live pgscan is less than UINT64_MAX */
185 /* Assert that avoid/omit are not set if the cgroup is not owned by root */
187 ctx
= oomd_cgroup_context_free(ctx
);
188 assert_se(cg_set_access(SYSTEMD_CGROUP_CONTROLLER
, cgroup
, 65534, 0) >= 0);
189 assert_se(oomd_cgroup_context_acquire(cgroup
, &ctx
) == 0);
190 assert_se(ctx
->preference
== MANAGED_OOM_PREFERENCE_NONE
);
194 static void test_oomd_update_cgroup_contexts_between_hashmaps(void) {
195 _cleanup_hashmap_free_ Hashmap
*h_old
= NULL
, *h_new
= NULL
;
196 OomdCGroupContext
*c_old
, *c_new
;
197 char **paths
= STRV_MAKE("/0.slice",
200 OomdCGroupContext ctx_old
[2] = {
202 .mem_pressure_limit
= 5,
203 .mem_pressure_limit_hit_start
= 777,
204 .last_had_mem_reclaim
= 888,
207 .mem_pressure_limit
= 6,
208 .mem_pressure_limit_hit_start
= 888,
209 .last_had_mem_reclaim
= 888,
213 OomdCGroupContext ctx_new
[2] = {
220 assert_se(h_old
= hashmap_new(&string_hash_ops
));
221 assert_se(hashmap_put(h_old
, paths
[0], &ctx_old
[0]) >= 0);
222 assert_se(hashmap_put(h_old
, paths
[1], &ctx_old
[1]) >= 0);
224 assert_se(h_new
= hashmap_new(&string_hash_ops
));
225 assert_se(hashmap_put(h_new
, paths
[0], &ctx_new
[0]) >= 0);
226 assert_se(hashmap_put(h_new
, paths
[1], &ctx_new
[1]) >= 0);
228 oomd_update_cgroup_contexts_between_hashmaps(h_old
, h_new
);
230 assert_se(c_old
= hashmap_get(h_old
, "/0.slice"));
231 assert_se(c_new
= hashmap_get(h_new
, "/0.slice"));
232 assert_se(c_old
->pgscan
== c_new
->last_pgscan
);
233 assert_se(c_old
->mem_pressure_limit
== c_new
->mem_pressure_limit
);
234 assert_se(c_old
->mem_pressure_limit_hit_start
== c_new
->mem_pressure_limit_hit_start
);
235 assert_se(c_old
->last_had_mem_reclaim
== c_new
->last_had_mem_reclaim
);
237 assert_se(c_old
= hashmap_get(h_old
, "/1.slice"));
238 assert_se(c_new
= hashmap_get(h_new
, "/1.slice"));
239 assert_se(c_old
->pgscan
== c_new
->last_pgscan
);
240 assert_se(c_old
->mem_pressure_limit
== c_new
->mem_pressure_limit
);
241 assert_se(c_old
->mem_pressure_limit_hit_start
== c_new
->mem_pressure_limit_hit_start
);
242 assert_se(c_new
->last_had_mem_reclaim
> c_old
->last_had_mem_reclaim
);
245 static void test_oomd_system_context_acquire(void) {
246 _cleanup_(unlink_tempfilep
) char path
[] = "/oomdgetsysctxtestXXXXXX";
247 OomdSystemContext ctx
;
250 return (void) log_tests_skipped("not root");
252 assert_se(mkstemp(path
));
254 assert_se(oomd_system_context_acquire("/verylikelynonexistentpath", &ctx
) == -ENOENT
);
256 assert_se(oomd_system_context_acquire(path
, &ctx
) == -EINVAL
);
258 assert_se(write_string_file(path
, "some\nwords\nacross\nmultiple\nlines", WRITE_STRING_FILE_CREATE
) == 0);
259 assert_se(oomd_system_context_acquire(path
, &ctx
) == -EINVAL
);
261 assert_se(write_string_file(path
, "MemTotal: 32495256 kB trailing\n"
262 "MemFree: 9880512 kB data\n"
263 "SwapTotal: 8388604 kB is\n"
264 "SwapFree: 7604 kB bad\n", WRITE_STRING_FILE_CREATE
) == 0);
265 assert_se(oomd_system_context_acquire(path
, &ctx
) == -EINVAL
);
267 assert_se(write_string_file(path
, "MemTotal: 32495256 kB\n"
268 "MemFree: 9880512 kB\n"
269 "MemAvailable: 21777088 kB\n"
271 "Cached: 14344796 kB\n"
272 "Unevictable: 740004 kB\n"
274 "SwapTotal: 8388604 kB\n"
275 "SwapFree: 7604 kB\n", WRITE_STRING_FILE_CREATE
) == 0);
276 assert_se(oomd_system_context_acquire(path
, &ctx
) == 0);
277 assert_se(ctx
.mem_total
== 33275142144);
278 assert_se(ctx
.mem_used
== 23157497856);
279 assert_se(ctx
.swap_total
== 8589930496);
280 assert_se(ctx
.swap_used
== 8582144000);
283 static void test_oomd_pressure_above(void) {
284 _cleanup_hashmap_free_ Hashmap
*h1
= NULL
, *h2
= NULL
;
285 _cleanup_set_free_ Set
*t1
= NULL
, *t2
= NULL
, *t3
= NULL
;
286 OomdCGroupContext ctx
[2], *c
;
289 assert_se(store_loadavg_fixed_point(80, 0, &threshold
) == 0);
292 assert_se(store_loadavg_fixed_point(99, 99, &(ctx
[0].memory_pressure
.avg10
)) == 0);
293 assert_se(store_loadavg_fixed_point(99, 99, &(ctx
[0].memory_pressure
.avg60
)) == 0);
294 assert_se(store_loadavg_fixed_point(99, 99, &(ctx
[0].memory_pressure
.avg300
)) == 0);
295 ctx
[0].mem_pressure_limit
= threshold
;
298 assert_se(store_loadavg_fixed_point(1, 11, &(ctx
[1].memory_pressure
.avg10
)) == 0);
299 assert_se(store_loadavg_fixed_point(1, 11, &(ctx
[1].memory_pressure
.avg60
)) == 0);
300 assert_se(store_loadavg_fixed_point(1, 11, &(ctx
[1].memory_pressure
.avg300
)) == 0);
301 ctx
[1].mem_pressure_limit
= threshold
;
303 /* High memory pressure */
304 assert_se(h1
= hashmap_new(&string_hash_ops
));
305 assert_se(hashmap_put(h1
, "/herp.slice", &ctx
[0]) >= 0);
306 assert_se(oomd_pressure_above(h1
, 0 /* duration */, &t1
) == 1);
307 assert_se(set_contains(t1
, &ctx
[0]));
308 assert_se(c
= hashmap_get(h1
, "/herp.slice"));
309 assert_se(c
->mem_pressure_limit_hit_start
> 0);
311 /* Low memory pressure */
312 assert_se(h2
= hashmap_new(&string_hash_ops
));
313 assert_se(hashmap_put(h2
, "/derp.slice", &ctx
[1]) >= 0);
314 assert_se(oomd_pressure_above(h2
, 0 /* duration */, &t2
) == 0);
316 assert_se(c
= hashmap_get(h2
, "/derp.slice"));
317 assert_se(c
->mem_pressure_limit_hit_start
== 0);
319 /* High memory pressure w/ multiple cgroups */
320 assert_se(hashmap_put(h1
, "/derp.slice", &ctx
[1]) >= 0);
321 assert_se(oomd_pressure_above(h1
, 0 /* duration */, &t3
) == 1);
322 assert_se(set_contains(t3
, &ctx
[0]));
323 assert_se(set_size(t3
) == 1);
324 assert_se(c
= hashmap_get(h1
, "/herp.slice"));
325 assert_se(c
->mem_pressure_limit_hit_start
> 0);
326 assert_se(c
= hashmap_get(h1
, "/derp.slice"));
327 assert_se(c
->mem_pressure_limit_hit_start
== 0);
330 static void test_oomd_mem_and_swap_free_below(void) {
331 OomdSystemContext ctx
= (OomdSystemContext
) {
332 .mem_total
= 20971512 * 1024U,
333 .mem_used
= 3310136 * 1024U,
334 .swap_total
= 20971512 * 1024U,
335 .swap_used
= 20971440 * 1024U,
337 assert_se(oomd_mem_free_below(&ctx
, 2000) == false);
338 assert_se(oomd_swap_free_below(&ctx
, 2000) == true);
340 ctx
= (OomdSystemContext
) {
341 .mem_total
= 20971512 * 1024U,
342 .mem_used
= 20971440 * 1024U,
343 .swap_total
= 20971512 * 1024U,
344 .swap_used
= 3310136 * 1024U,
346 assert_se(oomd_mem_free_below(&ctx
, 2000) == true);
347 assert_se(oomd_swap_free_below(&ctx
, 2000) == false);
349 ctx
= (OomdSystemContext
) {
355 assert_se(oomd_mem_free_below(&ctx
, 2000) == false);
356 assert_se(oomd_swap_free_below(&ctx
, 2000) == false);
359 static void test_oomd_sort_cgroups(void) {
360 _cleanup_hashmap_free_ Hashmap
*h
= NULL
;
361 _cleanup_free_ OomdCGroupContext
**sorted_cgroups
;
362 char **paths
= STRV_MAKE("/herp.slice",
363 "/herp.slice/derp.scope",
364 "/herp.slice/derp.scope/sheep.service",
370 OomdCGroupContext ctx
[7] = {
375 .current_memory_usage
= 10 },
380 .current_memory_usage
= 20 },
385 .current_memory_usage
= 40 },
390 .current_memory_usage
= 10 },
395 .current_memory_usage
= 10 },
399 .pgscan
= UINT64_MAX
,
400 .preference
= MANAGED_OOM_PREFERENCE_OMIT
},
404 .pgscan
= UINT64_MAX
,
405 .preference
= MANAGED_OOM_PREFERENCE_AVOID
},
408 assert_se(h
= hashmap_new(&string_hash_ops
));
410 assert_se(hashmap_put(h
, "/herp.slice", &ctx
[0]) >= 0);
411 assert_se(hashmap_put(h
, "/herp.slice/derp.scope", &ctx
[1]) >= 0);
412 assert_se(hashmap_put(h
, "/herp.slice/derp.scope/sheep.service", &ctx
[2]) >= 0);
413 assert_se(hashmap_put(h
, "/zupa.slice", &ctx
[3]) >= 0);
414 assert_se(hashmap_put(h
, "/boop.slice", &ctx
[4]) >= 0);
415 assert_se(hashmap_put(h
, "/omitted.slice", &ctx
[5]) >= 0);
416 assert_se(hashmap_put(h
, "/avoid.slice", &ctx
[6]) >= 0);
418 assert_se(oomd_sort_cgroup_contexts(h
, compare_swap_usage
, NULL
, &sorted_cgroups
) == 6);
419 assert_se(sorted_cgroups
[0] == &ctx
[1]);
420 assert_se(sorted_cgroups
[1] == &ctx
[2]);
421 assert_se(sorted_cgroups
[2] == &ctx
[0]);
422 assert_se(sorted_cgroups
[3] == &ctx
[4]);
423 assert_se(sorted_cgroups
[4] == &ctx
[3]);
424 assert_se(sorted_cgroups
[5] == &ctx
[6]);
425 sorted_cgroups
= mfree(sorted_cgroups
);
427 assert_se(oomd_sort_cgroup_contexts(h
, compare_pgscan_rate_and_memory_usage
, NULL
, &sorted_cgroups
) == 6);
428 assert_se(sorted_cgroups
[0] == &ctx
[0]);
429 assert_se(sorted_cgroups
[1] == &ctx
[2]);
430 assert_se(sorted_cgroups
[2] == &ctx
[3]);
431 assert_se(sorted_cgroups
[3] == &ctx
[1]);
432 assert_se(sorted_cgroups
[4] == &ctx
[4]);
433 assert_se(sorted_cgroups
[5] == &ctx
[6]);
434 sorted_cgroups
= mfree(sorted_cgroups
);
436 assert_se(oomd_sort_cgroup_contexts(h
, compare_pgscan_rate_and_memory_usage
, "/herp.slice/derp.scope", &sorted_cgroups
) == 2);
437 assert_se(sorted_cgroups
[0] == &ctx
[2]);
438 assert_se(sorted_cgroups
[1] == &ctx
[1]);
439 assert_se(sorted_cgroups
[2] == 0);
440 assert_se(sorted_cgroups
[3] == 0);
441 assert_se(sorted_cgroups
[4] == 0);
442 assert_se(sorted_cgroups
[5] == 0);
443 assert_se(sorted_cgroups
[6] == 0);
444 sorted_cgroups
= mfree(sorted_cgroups
);
450 test_setup_logging(LOG_DEBUG
);
452 test_oomd_update_cgroup_contexts_between_hashmaps();
453 test_oomd_system_context_acquire();
454 test_oomd_pressure_above();
455 test_oomd_mem_and_swap_free_below();
456 test_oomd_sort_cgroups();
458 /* The following tests operate on live cgroups */
460 r
= enter_cgroup_root(NULL
);
462 return log_tests_skipped_errno(r
, "failed to enter a test cgroup scope");
464 test_oomd_cgroup_kill();
465 test_oomd_cgroup_context_acquire_and_insert();