monitor applications file descriptor limits (#15417)
Co-authored-by: ilyam8 <ilya@netdata.cloud>
Costa Tsaousis committed
Jul 18, 2023 at 00:33 UTC
b3d90d5b4bf2793ad1fba7442f2c70728904e06b
8 files changed
+273
-35
Makefile.am
+1
@@ -401,6 +401,7 @@ PROC_PLUGIN_FILES = \
401
collectors/proc.plugin/zfs_common.h \
402
collectors/proc.plugin/proc_spl_kstat_zfs.c \
403
collectors/proc.plugin/proc_stat.c \
404
+ collectors/proc.plugin/proc_sys_fs_file_nr.c \
405
collectors/proc.plugin/proc_sys_kernel_random_entropy_avail.c \
406
collectors/proc.plugin/proc_vmstat.c \
407
collectors/proc.plugin/proc_uptime.c \
collectors/all.h
+1
@@ -51,6 +51,7 @@
51
#define NETDATA_CHART_PRIO_SYSTEM_INTERRUPTS 1000
52
#define NETDATA_CHART_PRIO_SYSTEM_DEV_INTR 1000 // freebsd only
53
#define NETDATA_CHART_PRIO_SYSTEM_SOFT_INTR 1100 // freebsd only
54
+#define NETDATA_CHART_PRIO_SYSTEM_FILES_NR 1000
55
#define NETDATA_CHART_PRIO_SYSTEM_ENTROPY 1000
56
#define NETDATA_CHART_PRIO_SYSTEM_UPTIME 1000
57
#define NETDATA_CHART_PRIO_CLOCK_SYNC_STATE 1100
collectors/apps.plugin/apps_plugin.c
+157
-35
@@ -141,6 +141,17 @@ static const char *proc_states[] = {
141
// internal flags
142
// handled in code (automatically set)
143
144
+// log each problem once per process
145
+// log flood protection flags (log_thrown)
146
+typedef enum __attribute__((packed)) {
147
+ PID_LOG_IO = (1 << 0),
148
+ PID_LOG_STATUS = (1 << 1),
149
+ PID_LOG_CMDLINE = (1 << 2),
150
+ PID_LOG_FDS = (1 << 3),
151
+ PID_LOG_STAT = (1 << 4),
152
+ PID_LOG_LIMITS = (1 << 5),
153
+} PID_LOG;
154
+
155
static int
156
show_guest_time = 0, // 1 when guest values are collected
157
show_guest_time_old = 0,
@@ -211,6 +222,25 @@ struct openfds {
222
kernel_uint_t other;
223
};
224
225
+struct pid_limits {
226
+// kernel_uint_t max_cpu_time;
227
+// kernel_uint_t max_file_size;
228
+// kernel_uint_t max_data_size;
229
+// kernel_uint_t max_stack_size;
230
+// kernel_uint_t max_core_file_size;
231
+// kernel_uint_t max_resident_set;
232
+// kernel_uint_t max_processes;
233
+ kernel_uint_t max_open_files;
234
+// kernel_uint_t max_locked_memory;
235
+// kernel_uint_t max_address_space;
236
+// kernel_uint_t max_file_locks;
237
+// kernel_uint_t max_pending_signals;
238
+// kernel_uint_t max_msgqueue_size;
239
+// kernel_uint_t max_nice_priority;
240
+// kernel_uint_t max_realtime_priority;
241
+// kernel_uint_t max_realtime_timeout;
242
+};
243
+
244
// ----------------------------------------------------------------------------
245
// target
246
//
@@ -268,6 +298,8 @@ struct target {
298
299
struct openfds openfds;
300
301
+ NETDATA_DOUBLE max_open_files_percent;
302
+
303
kernel_uint_t starttime;
304
kernel_uint_t collected_starttime;
305
kernel_uint_t uptime_min;
@@ -318,12 +350,6 @@ struct pid_fd {
350
351
struct pid_stat {
352
int32_t pid;
321
- char comm[MAX_COMPARE_NAME + 1];
322
- char *cmdline;
323
-
324
- uint32_t log_thrown;
325
-
326
- char state;
353
int32_t ppid;
354
// int32_t pgrp;
355
// int32_t session;
@@ -331,6 +357,11 @@ struct pid_stat {
357
// int32_t tpgid;
358
// uint64_t flags;
359
360
+ char state;
361
+
362
+ char comm[MAX_COMPARE_NAME + 1];
363
+ char *cmdline;
364
+
365
// these are raw values collected
366
kernel_uint_t minflt_raw;
367
kernel_uint_t cminflt_raw;
@@ -415,22 +446,28 @@ struct pid_stat {
446
kernel_uint_t io_storage_bytes_written;
447
kernel_uint_t io_cancelled_write_bytes;
448
449
+ kernel_uint_t uptime;
450
+
451
struct pid_fd *fds; // array of fds it uses
452
size_t fds_size; // the size of the fds array
453
454
struct openfds openfds;
455
+ struct pid_limits limits;
456
+
457
+ int sortlist; // higher numbers = top on the process tree
458
+ // each process gets a unique number
459
460
int children_count; // number of processes directly referencing this
424
- unsigned char keep:1; // 1 when we need to keep this process in memory even after it exited
461
int keeploops; // increases by 1 every time keep is 1 and updated 0
426
- unsigned char updated:1; // 1 when the process is currently running
427
- unsigned char merged:1; // 1 when it has been merged to its parent
428
- unsigned char read:1; // 1 when we have already read this process for this iteration
462
430
- int sortlist; // higher numbers = top on the process tree
431
- // each process gets a unique number
463
+ PID_LOG log_thrown;
464
465
+ bool keep; // true when we need to keep this process in memory even after it exited
466
+ bool updated; // true when the process is currently running
467
+ bool merged; // true when it has been merged to its parent
468
+ bool read; // true when we have already read this process for this iteration
469
bool matched_by_config;
470
+
471
struct target *target; // app_groups.conf targets
472
struct target *user_target; // uid based targets
473
struct target *group_target; // gid based targets
@@ -440,8 +477,7 @@ struct pid_stat {
477
478
usec_t io_collected_usec;
479
usec_t last_io_collected_usec;
443
-
444
- kernel_uint_t uptime;
480
+ usec_t last_limits_collected_usec;
481
482
char *fds_dirname; // the full directory name in /proc/PID/fd
483
@@ -449,6 +485,7 @@ struct pid_stat {
485
char *status_filename;
486
char *io_filename;
487
char *cmdline_filename;
488
+ char *limits_filename;
489
490
struct pid_stat *parent;
491
struct pid_stat *prev;
@@ -459,14 +496,6 @@ size_t pagesize;
496
497
kernel_uint_t global_uptime;
498
462
-// log each problem once per process
463
-// log flood protection flags (log_thrown)
464
-#define PID_LOG_IO 0x00000001
465
-#define PID_LOG_STATUS 0x00000002
466
-#define PID_LOG_CMDLINE 0x00000004
467
-#define PID_LOG_FDS 0x00000008
468
-#define PID_LOG_STAT 0x00000010
469
-
499
static struct pid_stat
500
*root_of_pids = NULL, // global list of all processes running
501
**all_pids = NULL; // to avoid allocations, we pre-allocate
@@ -1019,6 +1048,7 @@ static inline void del_pid_entry(pid_t pid) {
1048
freez(p->fds_dirname);
1049
freez(p->stat_filename);
1050
freez(p->status_filename);
1051
+ freez(p->limits_filename);
1052
#ifndef __FreeBSD__
1053
arl_free(p->status_arl);
1054
#endif
@@ -1033,7 +1063,7 @@ static inline void del_pid_entry(pid_t pid) {
1063
1064
// ----------------------------------------------------------------------------
1065
1036
-static inline int managed_log(struct pid_stat *p, uint32_t log, int status) {
1066
+static inline int managed_log(struct pid_stat *p, PID_LOG log, int status) {
1067
if(unlikely(!status)) {
1068
// netdata_log_error("command failed log %u, errno %d", log, errno);
1069
@@ -1073,6 +1103,13 @@ static inline int managed_log(struct pid_stat *p, uint32_t log, int status) {
1103
#endif
1104
break;
1105
1106
+ case PID_LOG_LIMITS:
1107
+ #ifdef __FreeBSD__
1108
+ ;
1109
+ #else
1110
+ netdata_log_error("Cannot process %s/proc/%d/limits (command '%s')", netdata_configured_host_prefix, p->pid, p->comm);
1111
+ #endif
1112
+
1113
case PID_LOG_STAT:
1114
break;
1115
@@ -1314,6 +1351,54 @@ static void update_proc_state_count(char proc_state) {
1351
}
1352
#endif // !__FreeBSD__
1353
1354
+#define MAX_PROC_PID_LIMITS 8192
1355
+#define PROC_PID_LIMITS_MAX_OPEN_FILES_KEY "\nMax open files "
1356
+
1357
+static inline kernel_uint_t get_proc_pid_limits_limit(char *buf, const char *key, size_t key_len, kernel_uint_t def) {
1358
+ char *line = strstr(buf, key);
1359
+ if(!line)
1360
+ return def;
1361
+
1362
+ char *v = &line[key_len];
1363
+ while(isspace(*v)) v++;
1364
+
1365
+ return str2ull(v, NULL);
1366
+}
1367
+
1368
+static inline int read_proc_pid_limits(struct pid_stat *p, void *ptr) {
1369
+ (void)ptr;
1370
+
1371
+#ifdef __FreeBSD__
1372
+ return 0;
1373
+#else
1374
+ static char proc_pid_limits[MAX_PROC_PID_LIMITS + 1];
1375
+
1376
+ if(p->io_collected_usec > p->last_limits_collected_usec && p->io_collected_usec - p->last_limits_collected_usec <= 60 * USEC_PER_SEC)
1377
+ // too frequent, we want to collect limits once per minute
1378
+ return 0;
1379
+
1380
+ if(unlikely(!p->limits_filename)) {
1381
+ char filename[FILENAME_MAX + 1];
1382
+ snprintfz(filename, FILENAME_MAX, "%s/proc/%d/limits", netdata_configured_host_prefix, p->pid);
1383
+ p->limits_filename = strdupz(filename);
1384
+ }
1385
+
1386
+ int fd = open(p->limits_filename, procfile_open_flags, 0666);
1387
+ if(unlikely(fd == -1)) return 0;
1388
+
1389
+ ssize_t bytes = read(fd, proc_pid_limits, MAX_PROC_PID_LIMITS);
1390
+ close(fd);
1391
+
1392
+ if(bytes <= 0)
1393
+ return 0;
1394
+
1395
+ p->limits.max_open_files = get_proc_pid_limits_limit(proc_pid_limits, PROC_PID_LIMITS_MAX_OPEN_FILES_KEY, sizeof(PROC_PID_LIMITS_MAX_OPEN_FILES_KEY) - 1, 0);
1396
+ p->last_limits_collected_usec = p->io_collected_usec;
1397
+
1398
+ return 1;
1399
+#endif
1400
+}
1401
+
1402
static inline int read_proc_pid_status(struct pid_stat *p, void *ptr) {
1403
p->status_vmsize = 0;
1404
p->status_vmrss = 0;
@@ -2460,7 +2545,7 @@ static inline void process_exited_processes() {
2545
if(majflt) debug_find_lost_child(p, majflt, 2);
2546
}
2547
2463
- p->keep = 1;
2548
+ p->keep = true;
2549
2550
debug_log(" > remaining resources - KEEP - for another loop: %s (%d %s total resources: utime=" KERNEL_UINT_FORMAT " stime=" KERNEL_UINT_FORMAT " gtime=" KERNEL_UINT_FORMAT " minflt=" KERNEL_UINT_FORMAT " majflt=" KERNEL_UINT_FORMAT ")"
2551
, p->comm
@@ -2475,7 +2560,7 @@ static inline void process_exited_processes() {
2560
2561
for(pp = p->parent; pp ; pp = pp->parent) {
2562
if(pp->updated) break;
2478
- pp->keep = 1;
2563
+ pp->keep = true;
2564
2565
debug_log(" > - KEEP - parent for another loop: %s (%d %s)"
2566
, pp->comm
@@ -2573,7 +2658,7 @@ static inline int collect_data_for_pid(pid_t pid, void *ptr) {
2658
2659
struct pid_stat *p = get_pid_entry(pid);
2660
if(unlikely(!p || p->read)) return 0;
2576
- p->read = 1;
2661
+ p->read = true;
2662
2663
// debug_log("Reading process %d (%s), sortlist %d", p->pid, p->comm, p->sortlist);
2664
@@ -2605,8 +2690,10 @@ static inline int collect_data_for_pid(pid_t pid, void *ptr) {
2690
// --------------------------------------------------------------------
2691
// /proc/<pid>/fd
2692
2608
- if(enable_file_charts)
2609
- managed_log(p, PID_LOG_FDS, read_pid_file_descriptors(p, ptr));
2693
+ if(enable_file_charts) {
2694
+ managed_log(p, PID_LOG_FDS, read_pid_file_descriptors(p, ptr));
2695
+ managed_log(p, PID_LOG_LIMITS, read_proc_pid_limits(p, ptr));
2696
+ }
2697
2698
// --------------------------------------------------------------------
2699
// done!
@@ -2615,8 +2702,8 @@ static inline int collect_data_for_pid(pid_t pid, void *ptr) {
2702
debug_log("Read process %d (%s) sortlisted %d, but its parent %d (%s) sortlisted %d, is not read", p->pid, p->comm, p->sortlist, all_pids[p->ppid]->pid, all_pids[p->ppid]->comm, all_pids[p->ppid]->sortlist);
2703
2704
// mark it as updated
2618
- p->updated = 1;
2619
- p->keep = 0;
2705
+ p->updated = true;
2706
+ p->keep = false;
2707
p->keeploops = 0;
2708
2709
return 1;
@@ -2673,9 +2760,9 @@ static int collect_data_for_all_processes(void) {
2760
size_t slc = 0;
2761
#endif
2762
for(p = root_of_pids; p ; p = p->next) {
2676
- p->read = 0; // mark it as not read, so that collect_data_for_pid() will read it
2677
- p->updated = 0;
2678
- p->merged = 0;
2763
+ p->read = false; // mark it as not read, so that collect_data_for_pid() will read it
2764
+ p->updated = false;
2765
+ p->merged = false;
2766
p->children_count = 0;
2767
p->parent = NULL;
2768
@@ -2801,7 +2888,7 @@ static void cleanup_exited_pids(void) {
2888
}
2889
else {
2890
if(unlikely(p->keep)) p->keeploops++;
2804
- p->keep = 0;
2891
+ p->keep = false;
2892
p = p->next;
2893
}
2894
}
@@ -2855,7 +2942,7 @@ static void apply_apps_groups_targets_inheritance(void) {
2942
)) {
2943
// mark it as merged
2944
p->parent->children_count--;
2858
- p->merged = 1;
2945
+ p->merged = true;
2946
2947
// the parent inherits the child's target, if it does not have a target itself
2948
if(unlikely(p->target && !p->parent->target)) {
@@ -2965,6 +3052,8 @@ static size_t zero_all_targets(struct target *root) {
3052
w->openfds.signalfds = 0;
3053
w->openfds.eventpolls = 0;
3054
w->openfds.other = 0;
3055
+
3056
+ w->max_open_files_percent = 0.0;
3057
}
3058
3059
w->collected_starttime = 0;
@@ -3114,6 +3203,23 @@ static inline void aggregate_pid_on_target(struct target *w, struct pid_stat *p,
3203
return;
3204
}
3205
3206
+ if(p->limits.max_open_files > 0) {
3207
+ kernel_uint_t all_dfs =
3208
+ p->openfds.files +
3209
+ p->openfds.pipes +
3210
+ p->openfds.sockets +
3211
+ p->openfds.inotifies +
3212
+ p->openfds.eventfds +
3213
+ p->openfds.timerfds +
3214
+ p->openfds.signalfds +
3215
+ p->openfds.eventpolls +
3216
+ p->openfds.other;
3217
+
3218
+ NETDATA_DOUBLE percent = (NETDATA_DOUBLE)all_dfs * 100.0 / (NETDATA_DOUBLE)p->limits.max_open_files;
3219
+ if(percent > w->max_open_files_percent)
3220
+ w->max_open_files_percent = percent;
3221
+ }
3222
+
3223
w->cutime += p->cutime;
3224
w->cstime += p->cstime;
3225
w->cgtime += p->cgtime;
@@ -3742,6 +3848,14 @@ static void send_collected_data_to_netdata(struct target *root, const char *type
3848
send_SET(w->name, w->openfds.pipes);
3849
}
3850
send_END();
3851
+
3852
+ send_BEGIN(type, "fd_limit", dt);
3853
+ for (w = root; w; w = w->next) {
3854
+ if (unlikely(w->exposed && w->processes))
3855
+ send_SET(w->name, w->max_open_files_percent * 100.0);
3856
+ }
3857
+ send_END();
3858
+
3859
}
3860
}
3861
@@ -3987,6 +4101,14 @@ static void send_charts_updates_to_netdata(struct target *root, const char *type
4101
fprintf(stdout, "DIMENSION %s '' absolute 1 1\n", w->name);
4102
}
4103
APPS_PLUGIN_FUNCTIONS();
4104
+
4105
+ fprintf(stdout, "CHART %s.fd_limit '' '%s File Descriptors Limit' '%%' processes %s.fd_limit line 20054 %d\n", type,
4106
+ title, type, update_every);
4107
+ for (w = root; w; w = w->next) {
4108
+ if (unlikely(w->exposed))
4109
+ fprintf(stdout, "DIMENSION %s '' absolute 1 100\n", w->name);
4110
+ }
4111
+ APPS_PLUGIN_FUNCTIONS();
4112
}
4113
}
4114
collectors/proc.plugin/plugin_proc.c
+1
@@ -18,6 +18,7 @@ static struct proc_module {
18
{.name = "/proc/stat", .dim = "stat", .func = do_proc_stat},
19
{.name = "/proc/uptime", .dim = "uptime", .func = do_proc_uptime},
20
{.name = "/proc/loadavg", .dim = "loadavg", .func = do_proc_loadavg},
21
+ {.name = "/proc/sys/fs/file-nr", .dim = "file-nr", .func = do_proc_sys_fs_file_nr},
22
{.name = "/proc/sys/kernel/random/entropy_avail", .dim = "entropy", .func = do_proc_sys_kernel_random_entropy_avail},
23
24
// pressure metrics
collectors/proc.plugin/plugin_proc.h
+1
@@ -22,6 +22,7 @@ int do_proc_meminfo(int update_every, usec_t dt);
22
int do_proc_vmstat(int update_every, usec_t dt);
23
int do_proc_net_rpc_nfs(int update_every, usec_t dt);
24
int do_proc_net_rpc_nfsd(int update_every, usec_t dt);
25
+int do_proc_sys_fs_file_nr(int update_every, usec_t dt);
26
int do_proc_sys_kernel_random_entropy_avail(int update_every, usec_t dt);
27
int do_proc_interrupts(int update_every, usec_t dt);
28
int do_proc_softirqs(int update_every, usec_t dt);
collectors/proc.plugin/proc_sys_fs_file_nr.c
new
+81
@@ -0,0 +1,81 @@
1
+// SPDX-License-Identifier: GPL-3.0-or-later
2
+
3
+#include "plugin_proc.h"
4
+
5
+int do_proc_sys_fs_file_nr(int update_every, usec_t dt) {
6
+ (void)dt;
7
+
8
+ static procfile *ff = NULL;
9
+
10
+ if(unlikely(!ff)) {
11
+ char filename[FILENAME_MAX + 1];
12
+ snprintfz(filename, FILENAME_MAX, "%s%s", netdata_configured_host_prefix, "/proc/sys/fs/file-nr");
13
+ ff = procfile_open(config_get("plugin:proc:/proc/sys/fs/file-nr", "filename to monitor", filename), "", PROCFILE_FLAG_DEFAULT);
14
+ if(unlikely(!ff)) return 1;
15
+ }
16
+
17
+ ff = procfile_readall(ff);
18
+ if(unlikely(!ff)) return 0; // we return 0, so that we will retry to open it next time
19
+
20
+ uint64_t allocated = str2ull(procfile_lineword(ff, 0, 0), NULL);
21
+ uint64_t unused = str2ull(procfile_lineword(ff, 0, 1), NULL);
22
+ uint64_t max = str2ull(procfile_lineword(ff, 0, 2), NULL);
23
+
24
+ uint64_t used = allocated - unused;
25
+
26
+ static RRDSET *st_files = NULL;
27
+ static RRDDIM *rd_used = NULL;
28
+
29
+ if(unlikely(!st_files)) {
30
+ st_files = rrdset_create_localhost(
31
+ "system"
32
+ , "file_nr_used"
33
+ , NULL
34
+ , "files"
35
+ , NULL
36
+ , "File Descriptors"
37
+ , "files"
38
+ , PLUGIN_PROC_NAME
39
+ , "/proc/sys/fs/file-nr"
40
+ , NETDATA_CHART_PRIO_SYSTEM_FILES_NR
41
+ , update_every
42
+ , RRDSET_TYPE_LINE
43
+ );
44
+
45
+ rd_used = rrddim_add(st_files, "used", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
46
+ }
47
+
48
+ rrddim_set_by_pointer(st_files, rd_used, (collected_number )used);
49
+ rrdset_done(st_files);
50
+
51
+ static RRDSET *st_files_utilization = NULL;
52
+ static RRDDIM *rd_utilization = NULL;
53
+
54
+ if(unlikely(!st_files_utilization)) {
55
+ st_files_utilization = rrdset_create_localhost(
56
+ "system"
57
+ , "file_nr_utilization"
58
+ , NULL
59
+ , "files"
60
+ , NULL
61
+ , "File Descriptors Utilization"
62
+ , "percentage"
63
+ , PLUGIN_PROC_NAME
64
+ , "/proc/sys/fs/file-nr"
65
+ , NETDATA_CHART_PRIO_SYSTEM_FILES_NR + 1
66
+ , update_every
67
+ , RRDSET_TYPE_LINE
68
+ );
69
+
70
+ rd_utilization = rrddim_add(st_files_utilization, "utilization", NULL, 1, 10000, RRD_ALGORITHM_ABSOLUTE);
71
+ }
72
+
73
+ NETDATA_DOUBLE d_used = (NETDATA_DOUBLE)used;
74
+ NETDATA_DOUBLE d_max = (NETDATA_DOUBLE)max;
75
+ NETDATA_DOUBLE percent = d_used * 100.0 / d_max;
76
+
77
+ rrddim_set_by_pointer(st_files_utilization, rd_utilization, (collected_number)(percent * 10000));
78
+ rrdset_done(st_files_utilization);
79
+
80
+ return 0;
81
+}
health/Makefile.am
+1
@@ -44,6 +44,7 @@ dist_healthconfig_DATA = \
44
health.d/elasticsearch.conf \
45
health.d/entropy.conf \
46
health.d/exporting.conf \
47
+ health.d/file_descriptors.conf \
48
health.d/geth.conf \
49
health.d/ioping.conf \
50
health.d/gearman.conf \
health/health.d/file_descriptors.conf
new
+30
@@ -0,0 +1,30 @@
1
+ # you can disable an alarm notification by setting the 'to' line to: silent
2
+
3
+ template: system_file_descriptors_utilization
4
+ on: system.file_nr_utilization
5
+ class: Utilization
6
+ type: System
7
+ component: Processes
8
+ hosts: *
9
+ lookup: max -1m unaligned
10
+ units: %
11
+ every: 1m
12
+ crit: $this > 90
13
+ delay: down 15m multiplier 1.5 max 1h
14
+ info: system-wide utilization of open files
15
+ to: sysadmin
16
+
17
+ template: apps_group_file_descriptors_utilization
18
+ on: apps.fd_limit
19
+ class: Utilization
20
+ type: System
21
+component: Process
22
+ os: linux
23
+ hosts: *
24
+ lookup: max -1m unaligned foreach *
25
+ units: %
26
+ every: 1m
27
+ warn: $this > (($status >= $WARNING) ? (85) : (90))
28
+ delay: down 15m multiplier 1.5 max 1h
29
+ info: maximum utilization of open files among all application group PIDs
30
+ to: sysadmin