1 // SPDX-License-Identifier: GPL-2.0
3 * memcg_event_listener.c - Simple listener of memcg memory.events
5 * Copyright (c) 2023, SaluteDevices. All Rights Reserved.
7 * Author: Dmitry Rokosov <ddrokosov@salutedevices.com>
18 #include <sys/inotify.h>
21 #define MEMCG_EVENTS "memory.events"
23 /* Size of buffer to use when reading inotify events */
24 #define INOTIFY_BUFFER_SIZE 8192
26 #define INOTIFY_EVENT_NEXT(event, length) ({ \
27 (length) -= sizeof(*(event)) + (event)->len; \
31 #define INOTIFY_EVENT_OK(event, length) ((length) >= (ssize_t)sizeof(*(event)))
33 #define ARRAY_SIZE(arr) (sizeof(arr) / sizeof(arr[0]))
35 struct memcg_counters {
45 struct memcg_counters counters;
51 static void print_memcg_counters(const struct memcg_counters *counters)
53 printf("MEMCG events:\n");
54 printf("\tlow: %ld\n", counters->low);
55 printf("\thigh: %ld\n", counters->high);
56 printf("\tmax: %ld\n", counters->max);
57 printf("\toom: %ld\n", counters->oom);
58 printf("\toom_kill: %ld\n", counters->oom_kill);
59 printf("\toom_group_kill: %ld\n", counters->oom_group_kill);
62 static int get_memcg_counter(char *line, const char *name, long *counter)
64 size_t len = strlen(name);
68 if (memcmp(line, name, len)) {
69 warnx("Counter line %s has wrong name, %s is expected",
74 /* skip the whitespace delimiter */
78 tmp = strtol(&line[len], &endptr, 10);
79 if (((tmp == LONG_MAX || tmp == LONG_MIN) && errno == ERANGE) ||
81 warnx("Failed to parse: %s", &line[len]);
85 if (endptr == &line[len]) {
86 warnx("Not digits were found in line %s", &line[len]);
90 if (!(*endptr == '\0' || (*endptr == '\n' && *++endptr == '\0'))) {
91 warnx("Further characters after number: %s", endptr);
100 static int read_memcg_events(struct memcg_events *events, bool show_diff)
102 FILE *fp = fopen(events->path, "re");
105 bool any_new_events = false;
108 struct memcg_counters new_counters;
109 struct memcg_counters *counters = &events->counters;
117 .new = &new_counters.low,
118 .old = &counters->low,
122 .new = &new_counters.high,
123 .old = &counters->high,
127 .new = &new_counters.max,
128 .old = &counters->max,
132 .new = &new_counters.oom,
133 .old = &counters->oom,
137 .new = &new_counters.oom_kill,
138 .old = &counters->oom_kill,
141 .name = "oom_group_kill",
142 .new = &new_counters.oom_group_kill,
143 .old = &counters->oom_group_kill,
148 warn("Failed to open memcg events file %s", events->path);
152 /* Read new values for memcg counters */
153 for (i = 0; i < ARRAY_SIZE(map); ++i) {
157 nread = getline(&line, &len, fp);
160 warn("Failed to read line for counter %s",
169 ret = get_memcg_counter(line, map[i].name, map[i].new);
171 warnx("Failed to get counter value from line %s", line);
176 for (i = 0; i < ARRAY_SIZE(map); ++i) {
179 if (*map[i].new > *map[i].old) {
180 diff = *map[i].new - *map[i].old;
183 printf("*** %ld MEMCG %s event%s, "
184 "change counter %ld => %ld\n",
186 (diff == 1) ? "" : "s",
187 *map[i].old, *map[i].new);
190 any_new_events = true;
194 if (show_diff && !any_new_events)
195 printf("*** No new untracked memcg events available\n");
204 static void process_memcg_events(struct memcg_events *events,
205 struct inotify_event *event)
209 if (events->inotify_wd != event->wd) {
210 warnx("Unknown inotify event %d, should be %d", event->wd,
215 printf("Received event in %s:\n", events->path);
217 if (!(event->mask & IN_MODIFY)) {
218 warnx("No IN_MODIFY event, skip it");
222 ret = read_memcg_events(events, /* show_diff = */true);
224 warnx("Can't read memcg events");
227 static void monitor_events(struct memcg_events *events)
229 struct pollfd fds[1];
232 printf("Started monitoring memory events from '%s'...\n", events->path);
234 fds[0].fd = events->inotify_fd;
235 fds[0].events = POLLIN;
238 ret = poll(fds, ARRAY_SIZE(fds), -1);
239 if (ret < 0 && errno != EAGAIN)
240 err(EXIT_FAILURE, "Can't poll memcg events (%d)", ret);
242 if (fds[0].revents & POLLERR)
243 err(EXIT_FAILURE, "Got POLLERR during monitor events");
245 if (fds[0].revents & POLLIN) {
246 struct inotify_event *event;
247 char buffer[INOTIFY_BUFFER_SIZE];
250 length = read(fds[0].fd, buffer, INOTIFY_BUFFER_SIZE);
254 event = (struct inotify_event *)buffer;
255 while (INOTIFY_EVENT_OK(event, length)) {
256 process_memcg_events(events, event);
257 event = INOTIFY_EVENT_NEXT(event, length);
263 static int initialize_memcg_events(struct memcg_events *events,
268 memset(events, 0, sizeof(struct memcg_events));
270 ret = snprintf(events->path, PATH_MAX,
271 "/sys/fs/cgroup/%s/memory.events", cgroup);
272 if (ret >= PATH_MAX) {
273 warnx("Path to cgroup memory.events is too long");
275 } else if (ret < 0) {
276 warn("Can't generate cgroup event full name");
280 ret = read_memcg_events(events, /* show_diff = */false);
282 warnx("Failed to read initial memcg events state (%d)", ret);
286 events->inotify_fd = inotify_init();
287 if (events->inotify_fd < 0) {
288 warn("Failed to setup new inotify device");
292 events->inotify_wd = inotify_add_watch(events->inotify_fd,
293 events->path, IN_MODIFY);
294 if (events->inotify_wd < 0) {
295 warn("Couldn't add monitor in dir %s", events->path);
299 printf("Initialized MEMCG events with counters:\n");
300 print_memcg_counters(&events->counters);
305 static void cleanup_memcg_events(struct memcg_events *events)
307 inotify_rm_watch(events->inotify_fd, events->inotify_wd);
308 close(events->inotify_fd);
311 int main(int argc, const char **argv)
313 struct memcg_events events;
317 errx(EXIT_FAILURE, "Usage: %s <cgroup>", argv[0]);
319 ret = initialize_memcg_events(&events, argv[1]);
321 errx(EXIT_FAILURE, "Can't initialize memcg events (%zd)", ret);
323 monitor_events(&events);
325 cleanup_memcg_events(&events);
327 printf("Exiting memcg event listener...\n");