xref: /linux/samples/cgroup/memcg_event_listener.c (revision 8804d970fab45726b3c7cd7f240b31122aa94219)
1 // SPDX-License-Identifier: GPL-2.0
2 /*
3  * memcg_event_listener.c - Simple listener of memcg memory.events
4  *
5  * Copyright (c) 2023, SaluteDevices. All Rights Reserved.
6  *
7  * Author: Dmitry Rokosov <ddrokosov@salutedevices.com>
8  */
9 
10 #include <err.h>
11 #include <errno.h>
12 #include <limits.h>
13 #include <poll.h>
14 #include <stdbool.h>
15 #include <stdio.h>
16 #include <stdlib.h>
17 #include <string.h>
18 #include <sys/inotify.h>
19 #include <unistd.h>
20 
21 /* Size of buffer to use when reading inotify events */
22 #define INOTIFY_BUFFER_SIZE 8192
23 
24 #define INOTIFY_EVENT_NEXT(event, length) ({         \
25 	(length) -= sizeof(*(event)) + (event)->len; \
26 	(event)++;                                   \
27 })
28 
29 #define INOTIFY_EVENT_OK(event, length) ((length) >= (ssize_t)sizeof(*(event)))
30 
31 #define ARRAY_SIZE(arr) (sizeof(arr) / sizeof(arr[0]))
32 
33 struct memcg_counters {
34 	long low;
35 	long high;
36 	long max;
37 	long oom;
38 	long oom_kill;
39 	long oom_group_kill;
40 };
41 
42 struct memcg_events {
43 	struct memcg_counters counters;
44 	char path[PATH_MAX];
45 	int inotify_fd;
46 	int inotify_wd;
47 };
48 
print_memcg_counters(const struct memcg_counters * counters)49 static void print_memcg_counters(const struct memcg_counters *counters)
50 {
51 	printf("MEMCG events:\n");
52 	printf("\tlow: %ld\n", counters->low);
53 	printf("\thigh: %ld\n", counters->high);
54 	printf("\tmax: %ld\n", counters->max);
55 	printf("\toom: %ld\n", counters->oom);
56 	printf("\toom_kill: %ld\n", counters->oom_kill);
57 	printf("\toom_group_kill: %ld\n", counters->oom_group_kill);
58 }
59 
get_memcg_counter(char * line,const char * name,long * counter)60 static int get_memcg_counter(char *line, const char *name, long *counter)
61 {
62 	size_t len = strlen(name);
63 	char *endptr;
64 	long tmp;
65 
66 	if (memcmp(line, name, len)) {
67 		warnx("Counter line %s has wrong name, %s is expected",
68 		      line, name);
69 		return -EINVAL;
70 	}
71 
72 	/* skip the whitespace delimiter */
73 	len += 1;
74 
75 	errno = 0;
76 	tmp = strtol(&line[len], &endptr, 10);
77 	if (((tmp == LONG_MAX || tmp == LONG_MIN) && errno == ERANGE) ||
78 	    (errno && !tmp)) {
79 		warnx("Failed to parse: %s", &line[len]);
80 		return -ERANGE;
81 	}
82 
83 	if (endptr == &line[len]) {
84 		warnx("Not digits were found in line %s", &line[len]);
85 		return -EINVAL;
86 	}
87 
88 	if (!(*endptr == '\0' || (*endptr == '\n' && *++endptr == '\0'))) {
89 		warnx("Further characters after number: %s", endptr);
90 		return -EINVAL;
91 	}
92 
93 	*counter = tmp;
94 
95 	return 0;
96 }
97 
read_memcg_events(struct memcg_events * events,bool show_diff)98 static int read_memcg_events(struct memcg_events *events, bool show_diff)
99 {
100 	FILE *fp = fopen(events->path, "re");
101 	size_t i;
102 	int ret = 0;
103 	bool any_new_events = false;
104 	char *line = NULL;
105 	size_t len = 0;
106 	struct memcg_counters new_counters;
107 	struct memcg_counters *counters = &events->counters;
108 	struct {
109 		const char *name;
110 		long *new;
111 		long *old;
112 	} map[] = {
113 		{
114 			.name = "low",
115 			.new = &new_counters.low,
116 			.old = &counters->low,
117 		},
118 		{
119 			.name = "high",
120 			.new = &new_counters.high,
121 			.old = &counters->high,
122 		},
123 		{
124 			.name = "max",
125 			.new = &new_counters.max,
126 			.old = &counters->max,
127 		},
128 		{
129 			.name = "oom",
130 			.new = &new_counters.oom,
131 			.old = &counters->oom,
132 		},
133 		{
134 			.name = "oom_kill",
135 			.new = &new_counters.oom_kill,
136 			.old = &counters->oom_kill,
137 		},
138 		{
139 			.name = "oom_group_kill",
140 			.new = &new_counters.oom_group_kill,
141 			.old = &counters->oom_group_kill,
142 		},
143 	};
144 
145 	if (!fp) {
146 		warn("Failed to open memcg events file %s", events->path);
147 		return -EBADF;
148 	}
149 
150 	/* Read new values for memcg counters */
151 	for (i = 0; i < ARRAY_SIZE(map); ++i) {
152 		ssize_t nread;
153 
154 		errno = 0;
155 		nread = getline(&line, &len, fp);
156 		if (nread == -1) {
157 			if (errno) {
158 				warn("Failed to read line for counter %s",
159 				     map[i].name);
160 				ret = -EIO;
161 				goto exit;
162 			}
163 
164 			break;
165 		}
166 
167 		ret = get_memcg_counter(line, map[i].name, map[i].new);
168 		if (ret) {
169 			warnx("Failed to get counter value from line %s", line);
170 			goto exit;
171 		}
172 	}
173 
174 	for (i = 0; i < ARRAY_SIZE(map); ++i) {
175 		long diff;
176 
177 		if (*map[i].new > *map[i].old) {
178 			diff = *map[i].new - *map[i].old;
179 
180 			if (show_diff)
181 				printf("*** %ld MEMCG %s event%s, "
182 				       "change counter %ld => %ld\n",
183 				       diff, map[i].name,
184 				       (diff == 1) ? "" : "s",
185 				       *map[i].old, *map[i].new);
186 
187 			*map[i].old += diff;
188 			any_new_events = true;
189 		}
190 	}
191 
192 	if (show_diff && !any_new_events)
193 		printf("*** No new untracked memcg events available\n");
194 
195 exit:
196 	free(line);
197 	fclose(fp);
198 
199 	return ret;
200 }
201 
process_memcg_events(struct memcg_events * events,struct inotify_event * event)202 static void process_memcg_events(struct memcg_events *events,
203 				 struct inotify_event *event)
204 {
205 	int ret;
206 
207 	if (events->inotify_wd != event->wd) {
208 		warnx("Unknown inotify event %d, should be %d", event->wd,
209 		      events->inotify_wd);
210 		return;
211 	}
212 
213 	printf("Received event in %s:\n", events->path);
214 
215 	if (!(event->mask & IN_MODIFY)) {
216 		warnx("No IN_MODIFY event, skip it");
217 		return;
218 	}
219 
220 	ret = read_memcg_events(events, /* show_diff = */true);
221 	if (ret)
222 		warnx("Can't read memcg events");
223 }
224 
monitor_events(struct memcg_events * events)225 static void monitor_events(struct memcg_events *events)
226 {
227 	struct pollfd fds[1];
228 	int ret;
229 
230 	printf("Started monitoring memory events from '%s'...\n", events->path);
231 
232 	fds[0].fd = events->inotify_fd;
233 	fds[0].events = POLLIN;
234 
235 	for (;;) {
236 		ret = poll(fds, ARRAY_SIZE(fds), -1);
237 		if (ret < 0 && errno != EAGAIN)
238 			err(EXIT_FAILURE, "Can't poll memcg events (%d)", ret);
239 
240 		if (fds[0].revents & POLLERR)
241 			err(EXIT_FAILURE, "Got POLLERR during monitor events");
242 
243 		if (fds[0].revents & POLLIN) {
244 			struct inotify_event *event;
245 			char buffer[INOTIFY_BUFFER_SIZE];
246 			ssize_t length;
247 
248 			length = read(fds[0].fd, buffer, INOTIFY_BUFFER_SIZE);
249 			if (length <= 0)
250 				continue;
251 
252 			event = (struct inotify_event *)buffer;
253 			while (INOTIFY_EVENT_OK(event, length)) {
254 				process_memcg_events(events, event);
255 				event = INOTIFY_EVENT_NEXT(event, length);
256 			}
257 		}
258 	}
259 }
260 
initialize_memcg_events(struct memcg_events * events,const char * cgroup)261 static int initialize_memcg_events(struct memcg_events *events,
262 				   const char *cgroup)
263 {
264 	int ret;
265 
266 	memset(events, 0, sizeof(struct memcg_events));
267 
268 	ret = snprintf(events->path, PATH_MAX,
269 		       "/sys/fs/cgroup/%s/memory.events", cgroup);
270 	if (ret >= PATH_MAX) {
271 		warnx("Path to cgroup memory.events is too long");
272 		return -EMSGSIZE;
273 	} else if (ret < 0) {
274 		warn("Can't generate cgroup event full name");
275 		return ret;
276 	}
277 
278 	ret = read_memcg_events(events, /* show_diff = */false);
279 	if (ret) {
280 		warnx("Failed to read initial memcg events state (%d)", ret);
281 		return ret;
282 	}
283 
284 	events->inotify_fd = inotify_init();
285 	if (events->inotify_fd < 0) {
286 		warn("Failed to setup new inotify device");
287 		return -EMFILE;
288 	}
289 
290 	events->inotify_wd = inotify_add_watch(events->inotify_fd,
291 					       events->path, IN_MODIFY);
292 	if (events->inotify_wd < 0) {
293 		warn("Couldn't add monitor in dir %s", events->path);
294 		return -EIO;
295 	}
296 
297 	printf("Initialized MEMCG events with counters:\n");
298 	print_memcg_counters(&events->counters);
299 
300 	return 0;
301 }
302 
cleanup_memcg_events(struct memcg_events * events)303 static void cleanup_memcg_events(struct memcg_events *events)
304 {
305 	inotify_rm_watch(events->inotify_fd, events->inotify_wd);
306 	close(events->inotify_fd);
307 }
308 
main(int argc,const char ** argv)309 int main(int argc, const char **argv)
310 {
311 	struct memcg_events events;
312 	ssize_t ret;
313 
314 	if (argc != 2)
315 		errx(EXIT_FAILURE, "Usage: %s <cgroup>", argv[0]);
316 
317 	ret = initialize_memcg_events(&events, argv[1]);
318 	if (ret)
319 		errx(EXIT_FAILURE, "Can't initialize memcg events (%zd)", ret);
320 
321 	monitor_events(&events);
322 
323 	cleanup_memcg_events(&events);
324 
325 	printf("Exiting memcg event listener...\n");
326 
327 	return EXIT_SUCCESS;
328 }
329