| 1 | // SPDX-License-Identifier: GPL-3.0-or-later |
| 2 | |
| 3 | #include "apps_plugin.h" |
| 4 | |
| 5 | static uint32_t |
| 6 | all_files_len = 0, |
| 7 | all_files_size = 0; |
| 8 | |
| 9 | uint32_t all_files_len_get(void) { |
| 10 | (void)all_files_size; |
| 11 | return all_files_len; |
| 12 | } |
| 13 | |
| 14 | #if (PROCESSES_HAVE_FDS == 1) |
| 15 | // ---------------------------------------------------------------------------- |
| 16 | // file descriptor |
| 17 | // |
| 18 | // this is used to keep a global list of all open files of the system. |
| 19 | // it is needed in order to calculate the unique files processes have open. |
| 20 | |
| 21 | #define FILE_DESCRIPTORS_INCREASE_STEP 100 |
| 22 | |
| 23 | // types for struct file_descriptor->type |
| 24 | typedef enum __attribute__((packed)) fd_filetype { |
| 25 | FILETYPE_OTHER, |
| 26 | FILETYPE_FILE, |
| 27 | FILETYPE_PIPE, |
| 28 | FILETYPE_SOCKET, |
| 29 | FILETYPE_INOTIFY, |
| 30 | FILETYPE_EVENTFD, |
| 31 | FILETYPE_EVENTPOLL, |
| 32 | FILETYPE_TIMERFD, |
| 33 | FILETYPE_SIGNALFD |
| 34 | } FD_FILETYPE; |
| 35 | |
| 36 | struct file_descriptor { |
| 37 | avl_t avl; |
| 38 | |
| 39 | #ifdef NETDATA_INTERNAL_CHECKS |
| 40 | uint32_t magic; |
| 41 | #endif /* NETDATA_INTERNAL_CHECKS */ |
| 42 | |
| 43 | const char *name; |
| 44 | uint32_t hash; |
| 45 | uint32_t count; |
| 46 | uint32_t pos; |
| 47 | FD_FILETYPE type; |
| 48 | } *all_files = NULL; |
| 49 | |
| 50 | // ---------------------------------------------------------------------------- |
| 51 | |
| 52 | static inline void reallocate_target_fds(struct target *w) { |
| 53 | if(unlikely(!w)) |
| 54 | return; |
| 55 | |
| 56 | if(unlikely(!w->target_fds || w->target_fds_size < all_files_size)) { |
| 57 | w->target_fds = reallocz(w->target_fds, sizeof(int) * all_files_size); |
| 58 | memset(&w->target_fds[w->target_fds_size], 0, sizeof(int) * (all_files_size - w->target_fds_size)); |
| 59 | w->target_fds_size = all_files_size; |
| 60 | } |
| 61 | } |
| 62 | |
| 63 | static void aggregage_fd_type_on_openfds(FD_FILETYPE type, struct openfds *openfds) { |
| 64 | switch(type) { |
| 65 | case FILETYPE_SOCKET: |
| 66 | openfds->sockets++; |
| 67 | break; |
| 68 | |
| 69 | case FILETYPE_FILE: |
| 70 | openfds->files++; |
| 71 | break; |
| 72 | |
| 73 | case FILETYPE_PIPE: |
| 74 | openfds->pipes++; |
| 75 | break; |
| 76 | |
| 77 | case FILETYPE_INOTIFY: |
| 78 | openfds->inotifies++; |
| 79 | break; |
| 80 | |
| 81 | case FILETYPE_EVENTFD: |
| 82 | openfds->eventfds++; |
| 83 | break; |
| 84 | |
| 85 | case FILETYPE_TIMERFD: |
| 86 | openfds->timerfds++; |
| 87 | break; |
| 88 | |
| 89 | case FILETYPE_SIGNALFD: |
| 90 | openfds->signalfds++; |
| 91 | break; |
| 92 | |
| 93 | case FILETYPE_EVENTPOLL: |
| 94 | openfds->eventpolls++; |
| 95 | break; |
| 96 | |
| 97 | case FILETYPE_OTHER: |
| 98 | openfds->other++; |
| 99 | break; |
| 100 | } |
| 101 | } |
| 102 | |
| 103 | static inline void aggregate_fd_on_target(int fd, struct target *w) { |
| 104 | if(unlikely(!w)) |
| 105 | return; |
| 106 | |
| 107 | if(unlikely(w->target_fds[fd])) { |
| 108 | // it is already aggregated |
| 109 | // just increase its usage counter |
| 110 | w->target_fds[fd]++; |
| 111 | return; |
| 112 | } |
| 113 | |
| 114 | // increase its usage counter |
| 115 | // so that we will not add it again |
| 116 | w->target_fds[fd]++; |
| 117 | |
| 118 | aggregage_fd_type_on_openfds(all_files[fd].type, &w->openfds); |
| 119 | } |
| 120 | |
| 121 | void aggregate_pid_fds_on_targets(struct pid_stat *p) { |
| 122 | if(enable_file_charts == CONFIG_BOOLEAN_AUTO && all_files_len > MAX_SYSTEM_FD_TO_ALLOW_FILES_PROCESSING) { |
| 123 | nd_log(NDLS_COLLECTORS, NDLP_NOTICE, "apps.plugin: the number of system file descriptors are too many (%u), " |
| 124 | "disabling file charts. If you want this enabled, set the 'with-files' " |
| 125 | "parameter to [plugin:apps] section of netdata.conf", all_files_size); |
| 126 | |
| 127 | enable_file_charts = CONFIG_BOOLEAN_NO; |
| 128 | obsolete_file_charts = true; |
| 129 | return; |
| 130 | } |
| 131 | |
| 132 | if(unlikely(!p->updated)) { |
| 133 | // the process is not running |
| 134 | return; |
| 135 | } |
| 136 | |
| 137 | struct target |
| 138 | #if (PROCESSES_HAVE_UID == 1) |
| 139 | *u = p->uid_target, |
| 140 | #endif |
| 141 | #if (PROCESSES_HAVE_GID == 1) |
| 142 | *g = p->gid_target, |
| 143 | #endif |
| 144 | *w = p->target; |
| 145 | |
| 146 | reallocate_target_fds(w); |
| 147 | #if (PROCESSES_HAVE_UID == 1) |
| 148 | reallocate_target_fds(u); |
| 149 | #endif |
| 150 | #if (PROCESSES_HAVE_GID == 1) |
| 151 | reallocate_target_fds(g); |
| 152 | #endif |
| 153 | |
| 154 | #if (PROCESSES_HAVE_FDS == 1) |
| 155 | p->openfds.files = 0; |
| 156 | p->openfds.pipes = 0; |
| 157 | p->openfds.sockets = 0; |
| 158 | p->openfds.inotifies = 0; |
| 159 | p->openfds.eventfds = 0; |
| 160 | p->openfds.timerfds = 0; |
| 161 | p->openfds.signalfds = 0; |
| 162 | p->openfds.eventpolls = 0; |
| 163 | p->openfds.other = 0; |
| 164 | |
| 165 | uint32_t c, size = p->fds_size; |
| 166 | struct pid_fd *fds = p->fds; |
| 167 | for(c = 0; c < size ;c++) { |
| 168 | int fd = fds[c].fd; |
| 169 | |
| 170 | if(likely(fd <= 0 || (uint32_t)fd >= all_files_size)) |
| 171 | continue; |
| 172 | |
| 173 | aggregage_fd_type_on_openfds(all_files[fd].type, &p->openfds); |
| 174 | |
| 175 | aggregate_fd_on_target(fd, w); |
| 176 | #if (PROCESSES_HAVE_UID == 1) |
| 177 | aggregate_fd_on_target(fd, u); |
| 178 | #endif |
| 179 | #if (PROCESSES_HAVE_GID == 1) |
| 180 | aggregate_fd_on_target(fd, g); |
| 181 | #endif |
| 182 | } |
| 183 | #endif |
| 184 | } |
| 185 | |
| 186 | // ---------------------------------------------------------------------------- |
| 187 | |
| 188 | int file_descriptor_compare(void* a, void* b) { |
| 189 | #ifdef NETDATA_INTERNAL_CHECKS |
| 190 | if(((struct file_descriptor *)a)->magic != 0x0BADCAFE || ((struct file_descriptor *)b)->magic != 0x0BADCAFE) |
| 191 | netdata_log_error("Corrupted index data detected. Please report this."); |
| 192 | #endif /* NETDATA_INTERNAL_CHECKS */ |
| 193 | |
| 194 | if(((struct file_descriptor *)a)->hash < ((struct file_descriptor *)b)->hash) |
| 195 | return -1; |
| 196 | |
| 197 | else if(((struct file_descriptor *)a)->hash > ((struct file_descriptor *)b)->hash) |
| 198 | return 1; |
| 199 | |
| 200 | else |
| 201 | return strcmp(((struct file_descriptor *)a)->name, ((struct file_descriptor *)b)->name); |
| 202 | } |
| 203 | |
| 204 | // int file_descriptor_iterator(avl_t *a) { if(a) {}; return 0; } |
| 205 | |
| 206 | avl_tree_type all_files_index = { |
| 207 | NULL, |
| 208 | file_descriptor_compare |
| 209 | }; |
| 210 | |
| 211 | static struct file_descriptor *file_descriptor_find(const char *name, uint32_t hash) { |
| 212 | struct file_descriptor tmp; |
| 213 | tmp.hash = (hash)?hash:simple_hash(name); |
| 214 | tmp.name = name; |
| 215 | tmp.count = 0; |
| 216 | tmp.pos = 0; |
| 217 | #ifdef NETDATA_INTERNAL_CHECKS |
| 218 | tmp.magic = 0x0BADCAFE; |
| 219 | #endif /* NETDATA_INTERNAL_CHECKS */ |
| 220 | |
| 221 | return (struct file_descriptor *)avl_search(&all_files_index, (avl_t *) &tmp); |
| 222 | } |
| 223 | |
| 224 | #define file_descriptor_add(fd) avl_insert(&all_files_index, (avl_t *)(fd)) |
| 225 | #define file_descriptor_remove(fd) avl_remove(&all_files_index, (avl_t *)(fd)) |
| 226 | |
| 227 | // ---------------------------------------------------------------------------- |
| 228 | |
| 229 | void file_descriptor_not_used(int id) { |
| 230 | if(id > 0 && (uint32_t)id < all_files_size) { |
| 231 | |
| 232 | #ifdef NETDATA_INTERNAL_CHECKS |
| 233 | if(all_files[id].magic != 0x0BADCAFE) { |
| 234 | netdata_log_error("Ignoring request to remove empty file id %d.", id); |
| 235 | return; |
| 236 | } |
| 237 | #endif /* NETDATA_INTERNAL_CHECKS */ |
| 238 | |
| 239 | debug_log("decreasing slot %d (count = %d).", id, all_files[id].count); |
| 240 | |
| 241 | if(all_files[id].count > 0) { |
| 242 | all_files[id].count--; |
| 243 | |
| 244 | if(!all_files[id].count) { |
| 245 | debug_log(" >> slot %d is empty.", id); |
| 246 | |
| 247 | if(unlikely(file_descriptor_remove(&all_files[id]) != (void *)&all_files[id])) |
| 248 | netdata_log_error("INTERNAL ERROR: removal of unused fd from index, removed a different fd"); |
| 249 | |
| 250 | #ifdef NETDATA_INTERNAL_CHECKS |
| 251 | all_files[id].magic = 0x00000000; |
| 252 | #endif /* NETDATA_INTERNAL_CHECKS */ |
| 253 | all_files_len--; |
| 254 | } |
| 255 | } |
| 256 | else |
| 257 | netdata_log_error("Request to decrease counter of fd %d (%s), while the use counter is 0", |
| 258 | id, all_files[id].name); |
| 259 | } |
| 260 | else |
| 261 | netdata_log_error("Request to decrease counter of fd %d, which is outside the array size (1 to %"PRIu32")", |
| 262 | id, all_files_size); |
| 263 | } |
| 264 | |
| 265 | static inline void all_files_grow() { |
| 266 | void *old = all_files; |
| 267 | |
| 268 | uint32_t new_size = (all_files_size > 0) ? all_files_size * 2 : 2048; |
| 269 | |
| 270 | // there is no empty slot |
| 271 | all_files = reallocz(all_files, new_size * sizeof(struct file_descriptor)); |
| 272 | |
| 273 | // if the address changed, we have to rebuild the index |
| 274 | // since all pointers are now invalid |
| 275 | |
| 276 | if(unlikely(old && old != (void *)all_files)) { |
| 277 | all_files_index.root = NULL; |
| 278 | for(uint32_t i = 0; i < all_files_size; i++) { |
| 279 | if(!all_files[i].count) continue; |
| 280 | if(unlikely(file_descriptor_add(&all_files[i]) != (void *)&all_files[i])) |
| 281 | netdata_log_error("INTERNAL ERROR: duplicate indexing of fd during realloc."); |
| 282 | } |
| 283 | } |
| 284 | |
| 285 | // initialize the newly added entries |
| 286 | |
| 287 | for(uint32_t i = all_files_size; i < new_size; i++) { |
| 288 | all_files[i].count = 0; |
| 289 | all_files[i].name = NULL; |
| 290 | #ifdef NETDATA_INTERNAL_CHECKS |
| 291 | all_files[i].magic = 0x00000000; |
| 292 | #endif /* NETDATA_INTERNAL_CHECKS */ |
| 293 | all_files[i].pos = i; |
| 294 | } |
| 295 | |
| 296 | if(unlikely(!all_files_size)) all_files_len = 1; |
| 297 | all_files_size = new_size; |
| 298 | } |
| 299 | |
| 300 | static inline uint32_t file_descriptor_set_on_empty_slot(const char *name, uint32_t hash, FD_FILETYPE type) { |
| 301 | // check we have enough memory to add it |
| 302 | if(!all_files || all_files_len == all_files_size) |
| 303 | all_files_grow(); |
| 304 | |
| 305 | debug_log(" >> searching for empty slot."); |
| 306 | |
| 307 | // search for an empty slot |
| 308 | |
| 309 | static int last_pos = 0; |
| 310 | uint32_t i, c; |
| 311 | for(i = 0, c = last_pos ; i < all_files_size ; i++, c++) { |
| 312 | if(c >= all_files_size) c = 0; |
| 313 | if(c == 0) continue; |
| 314 | |
| 315 | if(!all_files[c].count) { |
| 316 | debug_log(" >> Examining slot %d.", c); |
| 317 | |
| 318 | #ifdef NETDATA_INTERNAL_CHECKS |
| 319 | if(all_files[c].magic == 0x0BADCAFE && all_files[c].name && file_descriptor_find(all_files[c].name, all_files[c].hash)) |
| 320 | netdata_log_error("fd on position %"PRIu32" is not cleared properly. It still has %s in it.", c, all_files[c].name); |
| 321 | #endif /* NETDATA_INTERNAL_CHECKS */ |
| 322 | |
| 323 | debug_log(" >> %s fd position %d for %s (last name: %s)", all_files[c].name?"re-using":"using", c, name, all_files[c].name); |
| 324 | |
| 325 | freez((void *)all_files[c].name); |
| 326 | all_files[c].name = NULL; |
| 327 | last_pos = c; |
| 328 | break; |
| 329 | } |
| 330 | } |
| 331 | |
| 332 | all_files_len++; |
| 333 | |
| 334 | if(i == all_files_size) { |
| 335 | fatal("We should find an empty slot, but there isn't any"); |
| 336 | exit(1); |
| 337 | } |
| 338 | // else we have an empty slot in 'c' |
| 339 | |
| 340 | debug_log(" >> updating slot %d.", c); |
| 341 | |
| 342 | all_files[c].name = strdupz(name); |
| 343 | all_files[c].hash = hash; |
| 344 | all_files[c].type = type; |
| 345 | all_files[c].pos = c; |
| 346 | all_files[c].count = 1; |
| 347 | #ifdef NETDATA_INTERNAL_CHECKS |
| 348 | all_files[c].magic = 0x0BADCAFE; |
| 349 | #endif /* NETDATA_INTERNAL_CHECKS */ |
| 350 | if(unlikely(file_descriptor_add(&all_files[c]) != (void *)&all_files[c])) |
| 351 | netdata_log_error("INTERNAL ERROR: duplicate indexing of fd."); |
| 352 | |
| 353 | return c; |
| 354 | } |
| 355 | |
| 356 | uint32_t file_descriptor_find_or_add(const char *name, uint32_t hash) { |
| 357 | if(unlikely(!hash)) |
| 358 | hash = simple_hash(name); |
| 359 | |
| 360 | debug_log("adding or finding name '%s' with hash %u", name, hash); |
| 361 | |
| 362 | struct file_descriptor *fd = file_descriptor_find(name, hash); |
| 363 | if(fd) { |
| 364 | // found |
| 365 | debug_log(" >> found on slot %d", fd->pos); |
| 366 | |
| 367 | fd->count++; |
| 368 | return fd->pos; |
| 369 | } |
| 370 | // not found |
| 371 | |
| 372 | FD_FILETYPE type; |
| 373 | if(likely(name[0] == '/')) type = FILETYPE_FILE; |
| 374 | else if(likely(strncmp(name, "pipe:", 5) == 0)) type = FILETYPE_PIPE; |
| 375 | else if(likely(strncmp(name, "socket:", 7) == 0)) type = FILETYPE_SOCKET; |
| 376 | else if(likely(strncmp(name, "anon_inode:", 11) == 0)) { |
| 377 | const char *t = &name[11]; |
| 378 | |
| 379 | if(strcmp(t, "inotify") == 0) type = FILETYPE_INOTIFY; |
| 380 | else if(strcmp(t, "[eventfd]") == 0) type = FILETYPE_EVENTFD; |
| 381 | else if(strcmp(t, "[eventpoll]") == 0) type = FILETYPE_EVENTPOLL; |
| 382 | else if(strcmp(t, "[timerfd]") == 0) type = FILETYPE_TIMERFD; |
| 383 | else if(strcmp(t, "[signalfd]") == 0) type = FILETYPE_SIGNALFD; |
| 384 | else { |
| 385 | debug_log("UNKNOWN anonymous inode: %s", name); |
| 386 | type = FILETYPE_OTHER; |
| 387 | } |
| 388 | } |
| 389 | else if(likely(strcmp(name, "inotify") == 0)) type = FILETYPE_INOTIFY; |
| 390 | else { |
| 391 | debug_log("UNKNOWN linkname: %s", name); |
| 392 | type = FILETYPE_OTHER; |
| 393 | } |
| 394 | |
| 395 | return file_descriptor_set_on_empty_slot(name, hash, type); |
| 396 | } |
| 397 | |
| 398 | void clear_pid_fd(struct pid_fd *pfd) { |
| 399 | pfd->fd = 0; |
| 400 | |
| 401 | #if defined(OS_LINUX) |
| 402 | pfd->link_hash = 0; |
| 403 | pfd->inode = 0; |
| 404 | pfd->cache_iterations_counter = 0; |
| 405 | pfd->cache_iterations_reset = 0; |
| 406 | #endif |
| 407 | } |
| 408 | |
| 409 | void make_all_pid_fds_negative(struct pid_stat *p) { |
| 410 | struct pid_fd *pfd = p->fds, *pfdend = &p->fds[p->fds_size]; |
| 411 | while(pfd < pfdend) { |
| 412 | pfd->fd = -(pfd->fd); |
| 413 | pfd++; |
| 414 | } |
| 415 | } |
| 416 | |
| 417 | static inline void cleanup_negative_pid_fds(struct pid_stat *p) { |
| 418 | struct pid_fd *pfd = p->fds, *pfdend = &p->fds[p->fds_size]; |
| 419 | |
| 420 | while(pfd < pfdend) { |
| 421 | int fd = pfd->fd; |
| 422 | |
| 423 | if(unlikely(fd < 0)) { |
| 424 | file_descriptor_not_used(-(fd)); |
| 425 | clear_pid_fd(pfd); |
| 426 | } |
| 427 | |
| 428 | pfd++; |
| 429 | } |
| 430 | } |
| 431 | |
| 432 | void init_pid_fds(struct pid_stat *p, size_t first, size_t size) { |
| 433 | struct pid_fd *pfd = &p->fds[first], *pfdend = &p->fds[first + size]; |
| 434 | |
| 435 | while(pfd < pfdend) { |
| 436 | #if defined(OS_LINUX) |
| 437 | pfd->filename = NULL; |
| 438 | #endif |
| 439 | clear_pid_fd(pfd); |
| 440 | pfd++; |
| 441 | } |
| 442 | } |
| 443 | |
| 444 | int read_pid_file_descriptors(struct pid_stat *p, void *ptr) { |
| 445 | bool ret = OS_FUNCTION(apps_os_read_pid_fds)(p, ptr); |
| 446 | cleanup_negative_pid_fds(p); |
| 447 | |
| 448 | return ret ? 1 : 0; |
| 449 | } |
| 450 | #endif |