Skip to content

Commit 1665706

Browse files
committed
libbpf-tools: oomkill: support display cgroup
Using a simple test program (not shown) called OOM, we performed the following two tests: 1. Allocating unlimited memory 2. Adding the process to a cgroup named oom-memcg and limiting its memory usage to 200MB. When we do not print cgroup information, we can only see process information and cannot see the difference between memcg and non-memcg. $ sudo ./oomkill Tracing OOM kills... Ctrl-C to stop. 14:28:23 Triggered by PID 179201 ("oom"), OOM kill of PID 179201 ("oom"), 6114610 pages, loadavg: loadavg: 0.56 0.51 0.38 2/968 179204 14:28:42 Triggered by PID 179212 ("oom"), OOM kill of PID 179212 ("oom"), 51200 pages, loadavg: loadavg: 0.40 0.47 0.37 3/968 179212 The function implemented by this patch can clearly display cgroup information. $ sudo ./oomkill -c Tracing OOM kills... Ctrl-C to stop. 14:32:59 Triggered by PID 179879 ("oom"), CGROUP 8309 ("/sys/fs/cgroup/user.slice/user-1000.slice/[email protected]/session.slice/[email protected]"), OOM kill of PID 179879 ("oom"), 6114610 pages, loadavg: loadavg: 0.50 0.38 0.35 4/970 179879 14:33:14 Triggered by PID 179884 ("oom"), CGROUP 122547 ("/sys/fs/cgroup/oom-memcg"), MEMCG 122547 ("/sys/fs/cgroup/oom-memcg"), OOM kill of PID 179884 ("oom"), 51200 pages, loadavg: loadavg: 0.47 0.38 0.35 3/971 179884 Signed-off-by: Rong Tao <[email protected]>
1 parent 21aa746 commit 1665706

File tree

5 files changed

+79
-147
lines changed

5 files changed

+79
-147
lines changed

libbpf-tools/cgroup_helpers.c

Lines changed: 34 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -3,25 +3,31 @@
33
#include <assert.h>
44
#include <dirent.h>
55
#include <errno.h>
6+
#include <limits.h>
67
#include <stdbool.h>
78
#include <stdio.h>
89
#include <stdlib.h>
910
#include <sys/stat.h>
1011
#include <string.h>
12+
#include <unistd.h>
1113
#include "cgroup_helpers.h"
1214

1315

14-
int cgroup_get_roots(char ***roots)
16+
/**
17+
* Get cgroup mountpoints.
18+
*
19+
* @roots need to be free with cgroup_free_roots(), access with roots[idx].
20+
*
21+
* On success, the number of root path returned, need pass to cgroup_free_roots()
22+
* nentries parameter. On error, -errno returned.
23+
*/
24+
static int cgroup_get_roots(char ***roots)
1525
{
16-
char line[2048];
17-
char fsname[128];
18-
char mountpoint[PATH_MAX];
19-
char fstype[64];
20-
char mntoptions[256];
21-
int dump_frequency;
22-
int fsck_order;
26+
char line[2048], fsname[128], mntpoint[PATH_MAX], fstype[64], mntopt[256];
27+
int dump_frequency, fsck_order, n;
2328
FILE *fp;
24-
int n = 0;
29+
30+
n = 0;
2531

2632
fp = fopen("/proc/mounts", "r");
2733
if (!fp)
@@ -30,8 +36,8 @@ int cgroup_get_roots(char ***roots)
3036
*roots = NULL;
3137

3238
while (fgets(line, sizeof(line), fp)) {
33-
if (sscanf(line, "%s %s %s %s %d %d\n", fsname, mountpoint,
34-
fstype, mntoptions, &dump_frequency,
39+
if (sscanf(line, "%s %s %s %s %d %d\n", fsname, mntpoint,
40+
fstype, mntopt, &dump_frequency,
3541
&fsck_order) != 6)
3642
continue;
3743

@@ -41,19 +47,24 @@ int cgroup_get_roots(char ***roots)
4147

4248
n++;
4349
*roots = (char **)realloc(*roots, n * sizeof(char *));
44-
(*roots)[n - 1] = strdup(mountpoint);
50+
(*roots)[n - 1] = strdup(mntpoint);
4551
}
4652

4753
fclose(fp);
4854

4955
return n;
5056
}
5157

52-
int cgroup_free_roots(char **roots, int nentries)
58+
/**
59+
* Used to release roots allocated by cgroup_get_roots().
60+
*
61+
* On success, zero returned. On error, -errno returned.
62+
*/
63+
static int cgroup_free_roots(char **roots, int nentries)
5364
{
5465
int i;
5566

56-
if (!roots || nentries <= 0)
67+
if (!roots)
5768
return -EINVAL;
5869

5970
for (i = 0; i < nentries; i++)
@@ -72,17 +83,11 @@ long cgroup_cgroupid_of_path(const char *cgroup_path)
7283
return err ? -errno : st.st_ino;
7384
}
7485

75-
long cgroup_cgroupid_of_mnt_path(const char *mntpoint, const char *cgroup_path)
76-
{
77-
char path[PATH_MAX];
78-
snprintf(path, sizeof(path) - 1, "%s/%s", mntpoint, cgroup_path);
79-
return cgroup_cgroupid_of_path(path);
80-
}
81-
8286
typedef int (*match_fn)(const char *path, void *arg);
8387

8488
/**
85-
* Recursively search for a matching cgroup in a root directory.
89+
* Recursively traverse all directories under the known cgroup root for
90+
* matching.
8691
*
8792
* When @match returns true, the match succeeds and the function returns
8893
* without further searching.
@@ -100,7 +105,9 @@ static int find_cgroup_from_root_recur(const char *root, match_fn match,
100105
struct stat st;
101106
size_t path_len;
102107

103-
if (!root || !match)
108+
assert(match && "match_fn is NULL");
109+
110+
if (!root)
104111
return -EINVAL;
105112

106113
err = lstat(root, &st);
@@ -110,7 +117,8 @@ static int find_cgroup_from_root_recur(const char *root, match_fn match,
110117
return -ENOTDIR;
111118

112119
path = malloc(PATH_MAX);
113-
assert(path && "Malloc failed");
120+
if (!path)
121+
return -errno;
114122

115123
snprintf(path, PATH_MAX - 1, "%s/", root);
116124

@@ -204,9 +212,9 @@ static int match_cgroupid(const char *path, void *arg)
204212
return 0;
205213
}
206214

207-
int cgroup_cgroup_path(long cgroupid, char *buf, size_t buf_len)
215+
int get_cgroupid_path(long cgroupid, char *buf, size_t buf_len)
208216
{
209-
char **roots;
217+
char **roots = 0;
210218
int nroots, i, err;
211219
struct match_cgroupid_arg arg = {};
212220
bool found = false;
@@ -231,61 +239,3 @@ int cgroup_cgroup_path(long cgroupid, char *buf, size_t buf_len)
231239

232240
return found ? 0 : -ENOENT;
233241
}
234-
235-
int cgroup_proc_for_each_cgroup_entry(pid_t pid, cgroup_proc_entry_fn callback,
236-
void *arg)
237-
{
238-
char line[512];
239-
char proc[64];
240-
FILE *f;
241-
int lines = 0;
242-
struct cgroup_proc_entry cgrp;
243-
244-
if (!callback)
245-
return -EINVAL;
246-
247-
snprintf(proc, sizeof(proc) - 1, "/proc/%d/cgroup", pid);
248-
249-
f = fopen(proc, "r");
250-
if (!f)
251-
return -errno;
252-
253-
/**
254-
* Parse each line, format:
255-
*
256-
* <hierarchy_id>:<subsystem_list>:<cgroup_path>
257-
*
258-
* For cgroupv2, subsystem_list is empty.
259-
*/
260-
while (fgets(line, sizeof(line), f)) {
261-
char *s_hid = line;
262-
char *s_subsys = strchr(line, ':');
263-
char *s_path = strrchr(line, ':');
264-
265-
s_subsys[0] = '\0';
266-
s_subsys++;
267-
s_path[0] = '\0';
268-
s_path++;
269-
270-
memset(&cgrp, 0, sizeof(cgrp));
271-
272-
cgrp.hierarchy_id = atoi(s_hid);
273-
274-
if (strlen(s_subsys) == 0) {
275-
cgrp.cgroup_type = 2; /* cgroupv2 */
276-
} else {
277-
cgrp.cgroup_type = 1; /* cgroupv1 */
278-
snprintf(cgrp.subsystem_list, sizeof(cgrp.subsystem_list),
279-
s_subsys);
280-
}
281-
282-
/* Use strlen() to strip '\n' */
283-
snprintf(cgrp.cgroup_path, strlen(s_path), s_path);
284-
285-
lines++;
286-
callback(&cgrp, arg);
287-
}
288-
289-
fclose(f);
290-
return lines;
291-
}

libbpf-tools/cgroup_helpers.h

Lines changed: 1 addition & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -1,74 +1,19 @@
11
// SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause)
22
/* Copyright (c) 2025 Rong Tao */
33
#pragma once
4-
#include <limits.h>
5-
#include <unistd.h>
64
#include <sys/types.h>
75

8-
#define CGROUP_DEFAULT_MNTPOINT "/sys/fs/cgroup/"
9-
10-
/**
11-
* Get cgroup mountpoints.
12-
*
13-
* @roots need to be free with cgroup_free_roots(), access with roots[idx].
14-
*
15-
* On success, the number of root path returned, need pass to cgroup_free_roots()
16-
* nentries parameter. On error, -errno returned.
17-
*/
18-
int cgroup_get_roots(char ***roots);
19-
20-
/**
21-
* Used to release roots allocated by cgroup_get_roots().
22-
*
23-
* On success, zero returned. On error, -errno returned.
24-
*/
25-
int cgroup_free_roots(char **roots, int nentries);
266

277
/**
288
* Get cgroup id from cgroup path.
299
*
3010
* On success, the cgroupid returned. On error, -errno returned.
3111
*/
3212
long cgroup_cgroupid_of_path(const char *cgroup_path);
33-
long cgroup_cgroupid_of_mnt_path(const char *mntpoint, const char *cgroup_path);
3413

3514
/**
3615
* Get cgroup path from cgroupid.
3716
*
3817
* On success, zero returned. On error, -errno returned.
3918
*/
40-
int cgroup_cgroup_path(long cgroupid, char *buf, size_t buf_len);
41-
42-
/**
43-
* This structure is used to describe a line of info in /proc/<pid>/cgroup.
44-
* For example, the following two entries show cgroupv1 and cgroupv2
45-
* respectively.
46-
*
47-
* 1:name=systemd:/user.slice/user-1000.slice/session-1.scope
48-
* 0::/user.slice/user-1000.slice/session-1.scope
49-
*/
50-
struct cgroup_proc_entry {
51-
/**
52-
* 1: cgroup v1
53-
* 2: cgroup v2
54-
*/
55-
int cgroup_type;
56-
/**
57-
* The following variable stores a line of information from a
58-
* /proc/<pid>/cgroup file.
59-
*/
60-
int hierarchy_id;
61-
char subsystem_list[256];
62-
char cgroup_path[PATH_MAX];
63-
};
64-
65-
typedef void (*cgroup_proc_entry_fn)(const struct cgroup_proc_entry *cgrp, void *arg);
66-
67-
/**
68-
* This function traverses all cgroup information of a process, and each cgroup
69-
* entry will call the callback function.
70-
*
71-
* On success, the number of entry returned. On error, -errno returned.
72-
*/
73-
int cgroup_proc_for_each_cgroup_entry(pid_t pid, cgroup_proc_entry_fn callback,
74-
void *arg);
19+
int get_cgroupid_path(long cgroupid, char *buf, size_t buf_len);

libbpf-tools/oomkill.bpf.c

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,16 @@
88
#include "compat.bpf.h"
99
#include "oomkill.h"
1010

11+
/* linux:include/linux/memcontrol.h */
12+
struct mem_cgroup {
13+
struct cgroup_subsys_state css;
14+
};
15+
1116
SEC("kprobe/oom_kill_process")
1217
int BPF_KPROBE(oom_kill_process, struct oom_control *oc, const char *message)
1318
{
1419
struct data_t *data;
20+
struct mem_cgroup *memcg;
1521

1622
data = reserve_buf(sizeof(*data));
1723
if (!data)
@@ -20,6 +26,16 @@ int BPF_KPROBE(oom_kill_process, struct oom_control *oc, const char *message)
2026
data->fpid = bpf_get_current_pid_tgid() >> 32;
2127
data->tpid = BPF_CORE_READ(oc, chosen, tgid);
2228
data->pages = BPF_CORE_READ(oc, totalpages);
29+
data->cgroupid = bpf_get_current_cgroup_id();
30+
31+
/* Get the memory cgroup id */
32+
memcg = BPF_CORE_READ(oc, memcg);
33+
if (memcg) {
34+
struct cgroup *cgrp = BPF_CORE_READ(memcg, css.cgroup);
35+
data->mem_cgroupid = BPF_CORE_READ(cgrp, kn, id);
36+
} else
37+
data->mem_cgroupid = 0;
38+
2339
bpf_get_current_comm(&data->fcomm, sizeof(data->fcomm));
2440
bpf_probe_read_kernel(&data->tcomm, sizeof(data->tcomm), BPF_CORE_READ(oc, chosen, comm));
2541
submit_buf(ctx, data, sizeof(*data));

libbpf-tools/oomkill.c

Lines changed: 26 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
// SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause)
22
// Copyright (c) 2022 Jingxiang Zeng
33
// Copyright (c) 2022 Krisztian Fekete
4+
// Copyright (c) 2025 Rong Tao
45
//
56
// Based on oomkill(8) from BCC by Brendan Gregg.
67
// 13-Jan-2022 Jingxiang Zeng Created this.
78
// 17-Oct-2022 Krisztian Fekete Edited this.
9+
// 03-Aug-2025 Rong Tao Support display cgroup.
810
#include <argp.h>
911
#include <errno.h>
1012
#include <signal.h>
@@ -20,13 +22,15 @@
2022
#include "compat.h"
2123
#include "oomkill.h"
2224
#include "btf_helpers.h"
25+
#include "cgroup_helpers.h"
2326
#include "trace_helpers.h"
2427

2528
static volatile sig_atomic_t exiting = 0;
2629

2730
static bool verbose = false;
31+
static bool display_cgroup = false;
2832

29-
const char *argp_program_version = "oomkill 0.1";
33+
const char *argp_program_version = "oomkill 0.2";
3034
const char *argp_program_bug_address =
3135
"https://github.com/iovisor/bcc/tree/master/libbpf-tools";
3236
const char argp_program_doc[] =
@@ -35,17 +39,22 @@ const char argp_program_doc[] =
3539
"USAGE: oomkill [-h]\n"
3640
"\n"
3741
"EXAMPLES:\n"
38-
" oomkill # trace OOM kills\n";
42+
" oomkill # trace OOM kills\n"
43+
" oomkill --cgroup # trace OOM kills with cgroup display\n";
3944

4045
static const struct argp_option opts[] = {
4146
{ "verbose", 'v', NULL, 0, "Verbose debug output", 0 },
47+
{ "cgroup", 'c', NULL, 0, "Display cgroup information", 0 },
4248
{ NULL, 'h', NULL, OPTION_HIDDEN, "Show the full help", 0 },
4349
{},
4450
};
4551

4652
static error_t parse_arg(int key, char *arg, struct argp_state *state)
4753
{
4854
switch (key) {
55+
case 'c':
56+
display_cgroup = true;
57+
break;
4958
case 'v':
5059
verbose = true;
5160
break;
@@ -60,18 +69,28 @@ static error_t parse_arg(int key, char *arg, struct argp_state *state)
6069

6170
static int handle_event(void *ctx, void *data, size_t len)
6271
{
63-
char loadavg[256];
72+
char loadavg[256], cgroup_path[PATH_MAX];
6473
char ts[32];
6574
struct data_t *e = data;
6675

6776
str_timestamp("%H:%M:%S", ts, sizeof(ts));
77+
printf("%s Triggered by PID %d (\"%s\"),", ts, e->tpid, e->tcomm);
78+
if (display_cgroup) {
79+
get_cgroupid_path(e->cgroupid, cgroup_path, sizeof(cgroup_path));
80+
printf(" CGROUP %lld (\"%s\"),", e->cgroupid, cgroup_path);
81+
if (e->mem_cgroupid) {
82+
if (e->mem_cgroupid != e->cgroupid)
83+
get_cgroupid_path(e->mem_cgroupid, cgroup_path,
84+
sizeof(cgroup_path));
85+
printf(" MEMCG %lld (\"%s\"),", e->mem_cgroupid, cgroup_path);
86+
}
87+
}
88+
printf(" OOM kill of PID %d (\"%s\"), %lld pages", e->tpid, e->tcomm, e->pages);
6889

6990
if (str_loadavg(loadavg, sizeof(loadavg)) > 0)
70-
printf("%s Triggered by PID %d (\"%s\"), OOM kill of PID %d (\"%s\"), %lld pages, loadavg: %s",
71-
ts, e->fpid, e->fcomm, e->tpid, e->tcomm, e->pages, loadavg);
91+
printf(", loadavg: %s\n", loadavg);
7292
else
73-
printf("%s Triggered by PID %d (\"%s\"), OOM kill of PID %d (\"%s\"), %lld pages\n",
74-
ts, e->fpid, e->fcomm, e->tpid, e->tcomm, e->pages);
93+
printf("\n");
7594

7695
return 0;
7796
}

0 commit comments

Comments
 (0)