File Index
Files and directories
Sign in
FILE

memcg_event_listener.c

File preview with syntax highlighting.

linux-6.19.14/samples/cgroup/memcg_event_listener.c C
  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
 33struct 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
 42struct memcg_events {
 43	struct memcg_counters counters;
 44	char path[PATH_MAX];
 45	int inotify_fd;
 46	int inotify_wd;
 47};
 48
 49static 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
 60static 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
 98static 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
195exit:
196	free(line);
197	fclose(fp);
198
199	return ret;
200}
201
202static 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
225static 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
261static 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
303static 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
309int 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}
~/ Read-only file preview.