FILE
memcg_event_listener.c
File preview with syntax highlighting.
linux-6.19.14/samples/cgroup/memcg_event_listener.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.