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 OomdCGroupContext
*c1
, *c2
;
96 return (void) log_tests_skipped("not root");
98 if (!is_pressure_supported())
99 return (void) log_tests_skipped("system does not support pressure");
101 if (cg_all_unified() <= 0)
102 return (void) log_tests_skipped("cgroups are not running in unified mode");
104 assert_se(cg_pid_get_path(NULL
, 0, &cgroup
) >= 0);
106 /* If we don't have permissions to set xattrs we're likely in a userns or missing capabilities
107 * so skip the xattr portions of the test. */
108 r
= cg_set_xattr(SYSTEMD_CGROUP_CONTROLLER
, cgroup
, "user.oomd_test", "1", 1, 0);
109 test_xattrs
= !ERRNO_IS_PRIVILEGE(r
) && !ERRNO_IS_NOT_SUPPORTED(r
);
112 assert_se(cg_set_xattr(SYSTEMD_CGROUP_CONTROLLER
, cgroup
, "user.oomd_omit", "1", 1, 0) >= 0);
113 assert_se(cg_set_xattr(SYSTEMD_CGROUP_CONTROLLER
, cgroup
, "user.oomd_avoid", "1", 1, 0) >= 0);
116 assert_se(oomd_cgroup_context_acquire(cgroup
, &ctx
) == 0);
118 assert_se(streq(ctx
->path
, cgroup
));
119 assert_se(ctx
->current_memory_usage
> 0);
120 assert_se(ctx
->memory_min
== 0);
121 assert_se(ctx
->memory_low
== 0);
122 assert_se(ctx
->swap_usage
== 0);
123 assert_se(ctx
->last_pgscan
== 0);
124 assert_se(ctx
->pgscan
== 0);
125 /* omit takes precedence over avoid when both are set to true */
127 assert_se(ctx
->preference
== MANAGED_OOM_PREFERENCE_OMIT
);
129 assert_se(ctx
->preference
== MANAGED_OOM_PREFERENCE_NONE
);
130 ctx
= oomd_cgroup_context_free(ctx
);
132 /* also check when only avoid is set to true */
134 assert_se(cg_set_xattr(SYSTEMD_CGROUP_CONTROLLER
, cgroup
, "user.oomd_omit", "0", 1, 0) >= 0);
135 assert_se(cg_set_xattr(SYSTEMD_CGROUP_CONTROLLER
, cgroup
, "user.oomd_avoid", "1", 1, 0) >= 0);
137 assert_se(oomd_cgroup_context_acquire(cgroup
, &ctx
) == 0);
139 assert_se(ctx
->preference
== MANAGED_OOM_PREFERENCE_AVOID
);
140 ctx
= oomd_cgroup_context_free(ctx
);
142 /* Test the root cgroup */
143 assert_se(oomd_cgroup_context_acquire("", &ctx
) == 0);
144 assert_se(streq(ctx
->path
, "/"));
145 assert_se(ctx
->current_memory_usage
> 0);
146 assert_se(ctx
->preference
== MANAGED_OOM_PREFERENCE_NONE
);
148 /* Test hashmap inserts */
149 assert_se(h1
= hashmap_new(&oomd_cgroup_ctx_hash_ops
));
150 assert_se(oomd_insert_cgroup_context(NULL
, h1
, cgroup
) == 0);
151 c1
= hashmap_get(h1
, cgroup
);
154 /* make sure certain values from h1 get updated in h2 */
156 c1
->mem_pressure_limit
= 6789;
157 c1
->last_hit_mem_pressure_limit
= 42;
158 assert_se(h2
= hashmap_new(&oomd_cgroup_ctx_hash_ops
));
159 assert_se(oomd_insert_cgroup_context(h1
, h2
, cgroup
) == 0);
160 c1
= hashmap_get(h1
, cgroup
);
161 c2
= hashmap_get(h2
, cgroup
);
165 assert_se(c2
->last_pgscan
== 5555);
166 assert_se(c2
->mem_pressure_limit
== 6789);
167 assert_se(c2
->last_hit_mem_pressure_limit
== 42);
169 /* Assert that avoid/omit are not set if the cgroup is not owned by root */
171 ctx
= oomd_cgroup_context_free(ctx
);
172 assert_se(cg_set_access(SYSTEMD_CGROUP_CONTROLLER
, cgroup
, 65534, 0) >= 0);
173 assert_se(oomd_cgroup_context_acquire(cgroup
, &ctx
) == 0);
174 assert_se(ctx
->preference
== MANAGED_OOM_PREFERENCE_NONE
);
178 static void test_oomd_system_context_acquire(void) {
179 _cleanup_(unlink_tempfilep
) char path
[] = "/oomdgetsysctxtestXXXXXX";
180 OomdSystemContext ctx
;
183 return (void) log_tests_skipped("not root");
185 assert_se(mkstemp(path
));
187 assert_se(oomd_system_context_acquire("/verylikelynonexistentpath", &ctx
) == -ENOENT
);
189 assert_se(oomd_system_context_acquire(path
, &ctx
) == 0);
190 assert_se(ctx
.swap_total
== 0);
191 assert_se(ctx
.swap_used
== 0);
193 assert_se(write_string_file(path
, "some\nwords\nacross\nmultiple\nlines", WRITE_STRING_FILE_CREATE
) == 0);
194 assert_se(oomd_system_context_acquire(path
, &ctx
) == 0);
195 assert_se(ctx
.swap_total
== 0);
196 assert_se(ctx
.swap_used
== 0);
198 assert_se(write_string_file(path
, "Filename Type Size Used Priority", WRITE_STRING_FILE_CREATE
) == 0);
199 assert_se(oomd_system_context_acquire(path
, &ctx
) == 0);
200 assert_se(ctx
.swap_total
== 0);
201 assert_se(ctx
.swap_used
== 0);
203 assert_se(write_string_file(path
, "Filename Type Size Used Priority\n"
204 "/swapvol/swapfile file 18971644 0 -3\n"
205 "/dev/vda2 partition 1999868 993780 -2", WRITE_STRING_FILE_CREATE
) == 0);
206 assert_se(oomd_system_context_acquire(path
, &ctx
) == 0);
207 assert_se(ctx
.swap_total
== 21474828288);
208 assert_se(ctx
.swap_used
== 1017630720);
211 static void test_oomd_pressure_above(void) {
212 _cleanup_hashmap_free_ Hashmap
*h1
= NULL
, *h2
= NULL
;
213 _cleanup_set_free_ Set
*t1
= NULL
, *t2
= NULL
, *t3
= NULL
;
214 OomdCGroupContext ctx
[2], *c
;
217 assert_se(store_loadavg_fixed_point(80, 0, &threshold
) == 0);
220 assert_se(store_loadavg_fixed_point(99, 99, &(ctx
[0].memory_pressure
.avg10
)) == 0);
221 assert_se(store_loadavg_fixed_point(99, 99, &(ctx
[0].memory_pressure
.avg60
)) == 0);
222 assert_se(store_loadavg_fixed_point(99, 99, &(ctx
[0].memory_pressure
.avg300
)) == 0);
223 ctx
[0].mem_pressure_limit
= threshold
;
226 assert_se(store_loadavg_fixed_point(1, 11, &(ctx
[1].memory_pressure
.avg10
)) == 0);
227 assert_se(store_loadavg_fixed_point(1, 11, &(ctx
[1].memory_pressure
.avg60
)) == 0);
228 assert_se(store_loadavg_fixed_point(1, 11, &(ctx
[1].memory_pressure
.avg300
)) == 0);
229 ctx
[1].mem_pressure_limit
= threshold
;
232 /* High memory pressure */
233 assert_se(h1
= hashmap_new(&string_hash_ops
));
234 assert_se(hashmap_put(h1
, "/herp.slice", &ctx
[0]) >= 0);
235 assert_se(oomd_pressure_above(h1
, 0 /* duration */, &t1
) == 1);
236 assert_se(set_contains(t1
, &ctx
[0]) == true);
237 assert_se(c
= hashmap_get(h1
, "/herp.slice"));
238 assert_se(c
->last_hit_mem_pressure_limit
> 0);
240 /* Low memory pressure */
241 assert_se(h2
= hashmap_new(&string_hash_ops
));
242 assert_se(hashmap_put(h2
, "/derp.slice", &ctx
[1]) >= 0);
243 assert_se(oomd_pressure_above(h2
, 0 /* duration */, &t2
) == 0);
244 assert_se(t2
== NULL
);
245 assert_se(c
= hashmap_get(h2
, "/derp.slice"));
246 assert_se(c
->last_hit_mem_pressure_limit
== 0);
248 /* High memory pressure w/ multiple cgroups */
249 assert_se(hashmap_put(h1
, "/derp.slice", &ctx
[1]) >= 0);
250 assert_se(oomd_pressure_above(h1
, 0 /* duration */, &t3
) == 1);
251 assert_se(set_contains(t3
, &ctx
[0]) == true);
252 assert_se(set_size(t3
) == 1);
253 assert_se(c
= hashmap_get(h1
, "/herp.slice"));
254 assert_se(c
->last_hit_mem_pressure_limit
> 0);
255 assert_se(c
= hashmap_get(h1
, "/derp.slice"));
256 assert_se(c
->last_hit_mem_pressure_limit
== 0);
259 static void test_oomd_memory_reclaim(void) {
260 _cleanup_hashmap_free_ Hashmap
*h1
= NULL
;
261 char **paths
= STRV_MAKE("/0.slice",
267 OomdCGroupContext ctx
[5] = {
278 .last_pgscan
= UINT64_MAX
,
282 .pgscan
= UINT64_MAX
},
285 assert_se(h1
= hashmap_new(&string_hash_ops
));
286 assert_se(hashmap_put(h1
, paths
[0], &ctx
[0]) >= 0);
287 assert_se(hashmap_put(h1
, paths
[1], &ctx
[1]) >= 0);
288 assert_se(oomd_memory_reclaim(h1
) == false);
290 assert_se(hashmap_put(h1
, paths
[2], &ctx
[2]) >= 0);
291 assert_se(oomd_memory_reclaim(h1
) == false);
293 assert_se(hashmap_put(h1
, paths
[4], &ctx
[4]) >= 0);
294 assert_se(oomd_memory_reclaim(h1
) == true);
296 assert_se(hashmap_put(h1
, paths
[3], &ctx
[3]) >= 0);
297 assert_se(oomd_memory_reclaim(h1
) == false);
300 static void test_oomd_swap_free_below(void) {
301 OomdSystemContext ctx
= (OomdSystemContext
) {
302 .swap_total
= 20971512 * 1024U,
303 .swap_used
= 20971440 * 1024U,
305 assert_se(oomd_swap_free_below(&ctx
, 20) == true);
307 ctx
= (OomdSystemContext
) {
308 .swap_total
= 20971512 * 1024U,
309 .swap_used
= 3310136 * 1024U,
311 assert_se(oomd_swap_free_below(&ctx
, 20) == false);
313 ctx
= (OomdSystemContext
) {
317 assert_se(oomd_swap_free_below(&ctx
, 20) == false);
320 static void test_oomd_sort_cgroups(void) {
321 _cleanup_hashmap_free_ Hashmap
*h
= NULL
;
322 _cleanup_free_ OomdCGroupContext
**sorted_cgroups
;
323 char **paths
= STRV_MAKE("/herp.slice",
324 "/herp.slice/derp.scope",
325 "/herp.slice/derp.scope/sheep.service",
330 OomdCGroupContext ctx
[6] = {
334 .current_memory_usage
= 10 },
338 .current_memory_usage
= 20 },
342 .current_memory_usage
= 40 },
346 .current_memory_usage
= 10 },
350 .preference
= MANAGED_OOM_PREFERENCE_OMIT
},
354 .preference
= MANAGED_OOM_PREFERENCE_AVOID
},
357 assert_se(h
= hashmap_new(&string_hash_ops
));
359 assert_se(hashmap_put(h
, "/herp.slice", &ctx
[0]) >= 0);
360 assert_se(hashmap_put(h
, "/herp.slice/derp.scope", &ctx
[1]) >= 0);
361 assert_se(hashmap_put(h
, "/herp.slice/derp.scope/sheep.service", &ctx
[2]) >= 0);
362 assert_se(hashmap_put(h
, "/zupa.slice", &ctx
[3]) >= 0);
363 assert_se(hashmap_put(h
, "/omitted.slice", &ctx
[4]) >= 0);
364 assert_se(hashmap_put(h
, "/avoid.slice", &ctx
[5]) >= 0);
366 assert_se(oomd_sort_cgroup_contexts(h
, compare_swap_usage
, NULL
, &sorted_cgroups
) == 5);
367 assert_se(sorted_cgroups
[0] == &ctx
[1]);
368 assert_se(sorted_cgroups
[1] == &ctx
[2]);
369 assert_se(sorted_cgroups
[2] == &ctx
[0]);
370 assert_se(sorted_cgroups
[3] == &ctx
[3]);
371 assert_se(sorted_cgroups
[4] == &ctx
[5]);
372 sorted_cgroups
= mfree(sorted_cgroups
);
374 assert_se(oomd_sort_cgroup_contexts(h
, compare_pgscan_and_memory_usage
, NULL
, &sorted_cgroups
) == 5);
375 assert_se(sorted_cgroups
[0] == &ctx
[3]);
376 assert_se(sorted_cgroups
[1] == &ctx
[0]);
377 assert_se(sorted_cgroups
[2] == &ctx
[2]);
378 assert_se(sorted_cgroups
[3] == &ctx
[1]);
379 assert_se(sorted_cgroups
[4] == &ctx
[5]);
380 sorted_cgroups
= mfree(sorted_cgroups
);
382 assert_se(oomd_sort_cgroup_contexts(h
, compare_pgscan_and_memory_usage
, "/herp.slice/derp.scope", &sorted_cgroups
) == 2);
383 assert_se(sorted_cgroups
[0] == &ctx
[2]);
384 assert_se(sorted_cgroups
[1] == &ctx
[1]);
385 assert_se(sorted_cgroups
[2] == 0);
386 assert_se(sorted_cgroups
[3] == 0);
387 assert_se(sorted_cgroups
[4] == 0);
388 assert_se(sorted_cgroups
[5] == 0);
389 sorted_cgroups
= mfree(sorted_cgroups
);
395 test_setup_logging(LOG_DEBUG
);
397 test_oomd_system_context_acquire();
398 test_oomd_pressure_above();
399 test_oomd_memory_reclaim();
400 test_oomd_swap_free_below();
401 test_oomd_sort_cgroups();
403 /* The following tests operate on live cgroups */
405 r
= enter_cgroup_root(NULL
);
407 return log_tests_skipped_errno(r
, "failed to enter a test cgroup scope");
409 test_oomd_cgroup_kill();
410 test_oomd_cgroup_context_acquire_and_insert();