// SPDX-License-Identifier: GPL-2.0 /* * memcg_event_listener.c - Simple listener of memcg memory.events * * Copyright (c) 2023, SaluteDevices. All Rights Reserved. * * Author: Dmitry Rokosov */ #include #include #include #include #include #include #include #include #include #include #define MEMCG_EVENTS "memory.events" /* Size of buffer to use when reading inotify events */ #define INOTIFY_BUFFER_SIZE 8192 #define INOTIFY_EVENT_NEXT(event, length) ({ \ (length) -= sizeof(*(event)) + (event)->len; \ (event)++; \ }) #define INOTIFY_EVENT_OK(event, length) ((length) >= (ssize_t)sizeof(*(event))) #define ARRAY_SIZE(arr) (sizeof(arr) / sizeof(arr[0])) struct memcg_counters { long low; long high; long max; long oom; long oom_kill; long oom_group_kill; }; struct memcg_events { struct memcg_counters counters; char path[PATH_MAX]; int inotify_fd; int inotify_wd; }; static void print_memcg_counters(const struct memcg_counters *counters) { printf("MEMCG events:\n"); printf("\tlow: %ld\n", counters->low); printf("\thigh: %ld\n", counters->high); printf("\tmax: %ld\n", counters->max); printf("\toom: %ld\n", counters->oom); printf("\toom_kill: %ld\n", counters->oom_kill); printf("\toom_group_kill: %ld\n", counters->oom_group_kill); } static int get_memcg_counter(char *line, const char *name, long *counter) { size_t len = strlen(name); char *endptr; long tmp; if (memcmp(line, name, len)) { warnx("Counter line %s has wrong name, %s is expected", line, name); return -EINVAL; } /* skip the whitespace delimiter */ len += 1; errno = 0; tmp = strtol(&line[len], &endptr, 10); if (((tmp == LONG_MAX || tmp == LONG_MIN) && errno == ERANGE) || (errno && !tmp)) { warnx("Failed to parse: %s", &line[len]); return -ERANGE; } if (endptr == &line[len]) { warnx("Not digits were found in line %s", &line[len]); return -EINVAL; } if (!(*endptr == '\0' || (*endptr == '\n' && *++endptr == '\0'))) { warnx("Further characters after number: %s", endptr); return -EINVAL; } *counter = tmp; return 0; } static int read_memcg_events(struct memcg_events *events, bool show_diff) { FILE *fp = fopen(events->path, "re"); size_t i; int ret = 0; bool any_new_events = false; char *line = NULL; size_t len = 0; struct memcg_counters new_counters; struct memcg_counters *counters = &events->counters; struct { const char *name; long *new; long *old; } map[] = { { .name = "low", .new = &new_counters.low, .old = &counters->low, }, { .name = "high", .new = &new_counters.high, .old = &counters->high, }, { .name = "max", .new = &new_counters.max, .old = &counters->max, }, { .name = "oom", .new = &new_counters.oom, .old = &counters->oom, }, { .name = "oom_kill", .new = &new_counters.oom_kill, .old = &counters->oom_kill, }, { .name = "oom_group_kill", .new = &new_counters.oom_group_kill, .old = &counters->oom_group_kill, }, }; if (!fp) { warn("Failed to open memcg events file %s", events->path); return -EBADF; } /* Read new values for memcg counters */ for (i = 0; i < ARRAY_SIZE(map); ++i) { ssize_t nread; errno = 0; nread = getline(&line, &len, fp); if (nread == -1) { if (errno) { warn("Failed to read line for counter %s", map[i].name); ret = -EIO; goto exit; } break; } ret = get_memcg_counter(line, map[i].name, map[i].new); if (ret) { warnx("Failed to get counter value from line %s", line); goto exit; } } for (i = 0; i < ARRAY_SIZE(map); ++i) { long diff; if (*map[i].new > *map[i].old) { diff = *map[i].new - *map[i].old; if (show_diff) printf("*** %ld MEMCG %s event%s, " "change counter %ld => %ld\n", diff, map[i].name, (diff == 1) ? "" : "s", *map[i].old, *map[i].new); *map[i].old += diff; any_new_events = true; } } if (show_diff && !any_new_events) printf("*** No new untracked memcg events available\n"); exit: free(line); fclose(fp); return ret; } static void process_memcg_events(struct memcg_events *events, struct inotify_event *event) { int ret; if (events->inotify_wd != event->wd) { warnx("Unknown inotify event %d, should be %d", event->wd, events->inotify_wd); return; } printf("Received event in %s:\n", events->path); if (!(event->mask & IN_MODIFY)) { warnx("No IN_MODIFY event, skip it"); return; } ret = read_memcg_events(events, /* show_diff = */true); if (ret) warnx("Can't read memcg events"); } static void monitor_events(struct memcg_events *events) { struct pollfd fds[1]; int ret; printf("Started monitoring memory events from '%s'...\n", events->path); fds[0].fd = events->inotify_fd; fds[0].events = POLLIN; for (;;) { ret = poll(fds, ARRAY_SIZE(fds), -1); if (ret < 0 && errno != EAGAIN) err(EXIT_FAILURE, "Can't poll memcg events (%d)", ret); if (fds[0].revents & POLLERR) err(EXIT_FAILURE, "Got POLLERR during monitor events"); if (fds[0].revents & POLLIN) { struct inotify_event *event; char buffer[INOTIFY_BUFFER_SIZE]; ssize_t length; length = read(fds[0].fd, buffer, INOTIFY_BUFFER_SIZE); if (length <= 0) continue; event = (struct inotify_event *)buffer; while (INOTIFY_EVENT_OK(event, length)) { process_memcg_events(events, event); event = INOTIFY_EVENT_NEXT(event, length); } } } } static int initialize_memcg_events(struct memcg_events *events, const char *cgroup) { int ret; memset(events, 0, sizeof(struct memcg_events)); ret = snprintf(events->path, PATH_MAX, "/sys/fs/cgroup/%s/memory.events", cgroup); if (ret >= PATH_MAX) { warnx("Path to cgroup memory.events is too long"); return -EMSGSIZE; } else if (ret < 0) { warn("Can't generate cgroup event full name"); return ret; } ret = read_memcg_events(events, /* show_diff = */false); if (ret) { warnx("Failed to read initial memcg events state (%d)", ret); return ret; } events->inotify_fd = inotify_init(); if (events->inotify_fd < 0) { warn("Failed to setup new inotify device"); return -EMFILE; } events->inotify_wd = inotify_add_watch(events->inotify_fd, events->path, IN_MODIFY); if (events->inotify_wd < 0) { warn("Couldn't add monitor in dir %s", events->path); return -EIO; } printf("Initialized MEMCG events with counters:\n"); print_memcg_counters(&events->counters); return 0; } static void cleanup_memcg_events(struct memcg_events *events) { inotify_rm_watch(events->inotify_fd, events->inotify_wd); close(events->inotify_fd); } int main(int argc, const char **argv) { struct memcg_events events; ssize_t ret; if (argc != 2) errx(EXIT_FAILURE, "Usage: %s ", argv[0]); ret = initialize_memcg_events(&events, argv[1]); if (ret) errx(EXIT_FAILURE, "Can't initialize memcg events (%zd)", ret); monitor_events(&events); cleanup_memcg_events(&events); printf("Exiting memcg event listener...\n"); return EXIT_SUCCESS; }