Skip to content

Commit a2483ce

Browse files
rgushchinKernel Patches Daemon
authored andcommitted
bpf: selftests: psi struct ops test
Add a psi struct ops test. The test creates a cgroup with two child sub-cgroups, sets up memory.high for one of those and puts there a memory hungry process (initially frozen). Then it creates 2 psi triggers from within a init() bpf callback and attaches them to these cgroups. Then it deletes the first cgroup and runs the memory hungry task. The task is creating a high memory pressure, which triggers the psi event. The psi bpf handler declares a memcg oom in the corresponding cgroup. Finally the checks that both handle_cgroup_free() and handle_psi_event() handlers were executed, the correct process was killed and oom counters were updated. Signed-off-by: Roman Gushchin <[email protected]>
1 parent 4528373 commit a2483ce

File tree

2 files changed

+300
-0
lines changed

2 files changed

+300
-0
lines changed
Lines changed: 224 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,224 @@
1+
// SPDX-License-Identifier: GPL-2.0-only
2+
#include <test_progs.h>
3+
#include <bpf/btf.h>
4+
#include <bpf/bpf.h>
5+
6+
#include "cgroup_helpers.h"
7+
#include "test_psi.skel.h"
8+
9+
enum psi_res {
10+
PSI_IO,
11+
PSI_MEM,
12+
PSI_CPU,
13+
PSI_IRQ,
14+
NR_PSI_RESOURCES,
15+
};
16+
17+
struct cgroup_desc {
18+
const char *path;
19+
unsigned long long id;
20+
int pid;
21+
int fd;
22+
size_t target;
23+
size_t high;
24+
bool victim;
25+
};
26+
27+
#define MB (1024 * 1024)
28+
29+
static struct cgroup_desc cgroups[] = {
30+
{ .path = "/oom_test" },
31+
{ .path = "/oom_test/cg1" },
32+
{ .path = "/oom_test/cg2", .target = 500 * MB,
33+
.high = 40 * MB, .victim = true },
34+
};
35+
36+
static int spawn_task(struct cgroup_desc *desc)
37+
{
38+
char *ptr;
39+
int pid;
40+
41+
pid = fork();
42+
if (pid < 0)
43+
return pid;
44+
45+
if (pid > 0) {
46+
/* parent */
47+
desc->pid = pid;
48+
return 0;
49+
}
50+
51+
/* child */
52+
ptr = (char *)malloc(desc->target);
53+
if (!ptr)
54+
return -ENOMEM;
55+
56+
memset(ptr, 'a', desc->target);
57+
58+
while (1)
59+
sleep(1000);
60+
61+
return 0;
62+
}
63+
64+
static void setup_environment(void)
65+
{
66+
int i, err;
67+
68+
err = setup_cgroup_environment();
69+
if (!ASSERT_OK(err, "setup_cgroup_environment"))
70+
goto cleanup;
71+
72+
for (i = 0; i < ARRAY_SIZE(cgroups); i++) {
73+
cgroups[i].fd = create_and_get_cgroup(cgroups[i].path);
74+
if (!ASSERT_GE(cgroups[i].fd, 0, "create_and_get_cgroup"))
75+
goto cleanup;
76+
77+
cgroups[i].id = get_cgroup_id(cgroups[i].path);
78+
if (!ASSERT_GT(cgroups[i].id, 0, "get_cgroup_id"))
79+
goto cleanup;
80+
81+
/* Freeze the top-level cgroup and enable the memory controller */
82+
if (i == 0) {
83+
err = write_cgroup_file(cgroups[i].path, "cgroup.freeze", "1");
84+
if (!ASSERT_OK(err, "freeze cgroup"))
85+
goto cleanup;
86+
87+
err = write_cgroup_file(cgroups[i].path, "cgroup.subtree_control",
88+
"+memory");
89+
if (!ASSERT_OK(err, "enable memory controller"))
90+
goto cleanup;
91+
}
92+
93+
/* Set memory.high */
94+
if (cgroups[i].high) {
95+
char buf[256];
96+
97+
snprintf(buf, sizeof(buf), "%lu", cgroups[i].high);
98+
err = write_cgroup_file(cgroups[i].path, "memory.high", buf);
99+
if (!ASSERT_OK(err, "set memory.high"))
100+
goto cleanup;
101+
102+
snprintf(buf, sizeof(buf), "0");
103+
write_cgroup_file(cgroups[i].path, "memory.swap.max", buf);
104+
}
105+
106+
/* Spawn tasks creating memory pressure */
107+
if (cgroups[i].target) {
108+
char buf[256];
109+
110+
err = spawn_task(&cgroups[i]);
111+
if (!ASSERT_OK(err, "spawn task"))
112+
goto cleanup;
113+
114+
snprintf(buf, sizeof(buf), "%d", cgroups[i].pid);
115+
err = write_cgroup_file(cgroups[i].path, "cgroup.procs", buf);
116+
if (!ASSERT_OK(err, "put child into a cgroup"))
117+
goto cleanup;
118+
}
119+
}
120+
121+
return;
122+
123+
cleanup:
124+
cleanup_cgroup_environment();
125+
}
126+
127+
static int run_and_wait_for_oom(void)
128+
{
129+
int ret = -1;
130+
bool first = true;
131+
char buf[4096] = {};
132+
size_t size;
133+
134+
/* Unfreeze the top-level cgroup */
135+
ret = write_cgroup_file(cgroups[0].path, "cgroup.freeze", "0");
136+
if (!ASSERT_OK(ret, "unfreeze cgroup"))
137+
return -1;
138+
139+
for (;;) {
140+
int i, status;
141+
pid_t pid = wait(&status);
142+
143+
if (pid == -1) {
144+
if (errno == EINTR)
145+
continue;
146+
/* ECHILD */
147+
break;
148+
}
149+
150+
if (!first)
151+
continue;
152+
first = false;
153+
154+
/* Check which process was terminated first */
155+
for (i = 0; i < ARRAY_SIZE(cgroups); i++) {
156+
if (!ASSERT_OK(cgroups[i].victim !=
157+
(pid == cgroups[i].pid),
158+
"correct process was killed")) {
159+
ret = -1;
160+
break;
161+
}
162+
163+
if (!cgroups[i].victim)
164+
continue;
165+
166+
/* Check the memcg oom counter */
167+
size = read_cgroup_file(cgroups[i].path, "memory.events",
168+
buf, sizeof(buf));
169+
if (!ASSERT_OK(size <= 0, "read memory.events")) {
170+
ret = -1;
171+
break;
172+
}
173+
174+
if (!ASSERT_OK(strstr(buf, "oom_kill 1") == NULL,
175+
"oom_kill count check")) {
176+
ret = -1;
177+
break;
178+
}
179+
}
180+
181+
/* Kill all remaining tasks */
182+
for (i = 0; i < ARRAY_SIZE(cgroups); i++)
183+
if (cgroups[i].pid && cgroups[i].pid != pid)
184+
kill(cgroups[i].pid, SIGKILL);
185+
}
186+
187+
return ret;
188+
}
189+
190+
void test_psi(void)
191+
{
192+
struct test_psi *skel;
193+
u64 freed_cgroup_id;
194+
int err;
195+
196+
setup_environment();
197+
198+
skel = test_psi__open_and_load();
199+
err = libbpf_get_error(skel);
200+
if (CHECK_FAIL(err))
201+
goto cleanup;
202+
203+
skel->bss->deleted_cgroup_id = cgroups[1].id;
204+
skel->bss->high_pressure_cgroup_id = cgroups[2].id;
205+
206+
err = test_psi__attach(skel);
207+
if (CHECK_FAIL(err))
208+
goto cleanup;
209+
210+
/* Delete the first cgroup, it should trigger handle_cgroup_free() */
211+
remove_cgroup(cgroups[1].path);
212+
213+
/* Unfreeze all child tasks and create the memory pressure */
214+
err = run_and_wait_for_oom();
215+
CHECK_FAIL(err);
216+
217+
/* Check the result of the handle_cgroup_free() handler */
218+
freed_cgroup_id = skel->bss->deleted_cgroup_id;
219+
ASSERT_EQ(freed_cgroup_id, cgroups[1].id, "freed cgroup id");
220+
221+
cleanup:
222+
cleanup_cgroup_environment();
223+
test_psi__destroy(skel);
224+
}
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
// SPDX-License-Identifier: GPL-2.0-only
2+
#include "vmlinux.h"
3+
#include <bpf/bpf_helpers.h>
4+
#include <bpf/bpf_tracing.h>
5+
6+
char _license[] SEC("license") = "GPL";
7+
8+
struct mem_cgroup *bpf_get_mem_cgroup(struct cgroup_subsys_state *css) __ksym;
9+
void bpf_put_mem_cgroup(struct mem_cgroup *memcg) __ksym;
10+
int bpf_out_of_memory(struct mem_cgroup *memcg, int order, bool wait_on_oom_lock,
11+
const char *constraint_text__nullable) __ksym;
12+
int bpf_psi_create_trigger(struct bpf_psi *bpf_psi, u64 cgroup_id,
13+
u32 res, u32 threshold_us, u32 window_us) __ksym;
14+
15+
#define PSI_FULL 0x80000000
16+
17+
/* cgroup which will experience the high memory pressure */
18+
u64 high_pressure_cgroup_id;
19+
20+
/* cgroup which will be deleted */
21+
u64 deleted_cgroup_id;
22+
23+
/* cgroup which was actually freed */
24+
u64 freed_cgroup_id;
25+
26+
char constraint_name[] = "CONSTRAINT_BPF_PSI_MEM";
27+
28+
SEC("struct_ops.s/init")
29+
int BPF_PROG(psi_init, struct bpf_psi *bpf_psi)
30+
{
31+
int ret;
32+
33+
ret = bpf_psi_create_trigger(bpf_psi, high_pressure_cgroup_id,
34+
PSI_MEM | PSI_FULL, 100000, 1000000);
35+
if (ret)
36+
return ret;
37+
38+
return bpf_psi_create_trigger(bpf_psi, deleted_cgroup_id,
39+
PSI_IO, 100000, 1000000);
40+
}
41+
42+
SEC("struct_ops.s/handle_psi_event")
43+
void BPF_PROG(handle_psi_event, struct psi_trigger *t)
44+
{
45+
u64 cgroup_id = t->cgroup_id;
46+
struct mem_cgroup *memcg;
47+
struct cgroup *cgroup;
48+
49+
cgroup = bpf_cgroup_from_id(cgroup_id);
50+
if (!cgroup)
51+
return;
52+
53+
memcg = bpf_get_mem_cgroup(&cgroup->self);
54+
if (!memcg) {
55+
bpf_cgroup_release(cgroup);
56+
return;
57+
}
58+
59+
bpf_out_of_memory(memcg, 0, true, constraint_name);
60+
61+
bpf_put_mem_cgroup(memcg);
62+
bpf_cgroup_release(cgroup);
63+
}
64+
65+
SEC("struct_ops.s/handle_cgroup_free")
66+
void BPF_PROG(handle_cgroup_free, u64 cgroup_id)
67+
{
68+
freed_cgroup_id = cgroup_id;
69+
}
70+
71+
SEC(".struct_ops.link")
72+
struct bpf_psi_ops test_bpf_psi = {
73+
.init = (void *)psi_init,
74+
.handle_psi_event = (void *)handle_psi_event,
75+
.handle_cgroup_free = (void *)handle_cgroup_free,
76+
};

0 commit comments

Comments
 (0)