master
c 547 lines 18.7 KB
Raw
1 // SPDX-License-Identifier: GPL-3.0-or-later
2
3 #include "plugin_proc.h"
4
5 #include <sys/sem.h>
6 #include <sys/msg.h>
7 #include <sys/shm.h>
8
9 #define _COMMON_PLUGIN_NAME PLUGIN_PROC_NAME
10 #define _COMMON_PLUGIN_MODULE_NAME "ipc"
11 #include "../common-contexts/common-contexts.h"
12
13 #ifndef SEMVMX
14 #define SEMVMX 32767 /* <= 32767 semaphore maximum value */
15 #endif
16
17 /* Some versions of libc only define IPC_INFO when __USE_GNU is defined. */
18 #ifndef IPC_INFO
19 #define IPC_INFO 3
20 #endif
21
22 struct ipc_limits {
23 uint64_t shmmni; /* max number of segments */
24 uint64_t shmmax; /* max segment size */
25 uint64_t shmall; /* max total shared memory */
26 uint64_t shmmin; /* min segment size */
27
28 int semmni; /* max number of arrays */
29 int semmsl; /* max semaphores per array */
30 int semmns; /* max semaphores system wide */
31 int semopm; /* max ops per semop call */
32 unsigned int semvmx; /* semaphore max value (constant) */
33
34 int msgmni; /* max queues system wide */
35 size_t msgmax; /* max size of message */
36 int msgmnb; /* default max size of queue */
37 };
38
39 struct ipc_status {
40 int semusz; /* current number of arrays */
41 int semaem; /* current semaphores system wide */
42 };
43
44 /*
45 * The last arg of semctl is a union semun, but where is it defined? X/OPEN
46 * tells us to define it ourselves, but until recently Linux include files
47 * would also define it.
48 */
49 #ifndef HAVE_UNION_SEMUN
50 /* according to X/OPEN we have to define it ourselves */
51 union semun {
52 int val;
53 struct semid_ds *buf;
54 unsigned short int *array;
55 struct seminfo *__buf;
56 };
57 #endif
58
59 struct message_queue {
60 unsigned long long id;
61 int found;
62
63 RRDDIM *rd_messages;
64 RRDDIM *rd_bytes;
65 unsigned long long messages;
66 unsigned long long bytes;
67
68 struct message_queue * next;
69 };
70
71 struct shm_stats {
72 unsigned long long segments;
73 unsigned long long bytes;
74 };
75
76 static inline int ipc_sem_get_limits(struct ipc_limits *lim) {
77 static procfile *ff = NULL;
78 static int error_shown = 0;
79 static char filename[FILENAME_MAX + 1] = "";
80
81 if(unlikely(!filename[0]))
82 snprintfz(filename, FILENAME_MAX, "%s/proc/sys/kernel/sem", netdata_configured_host_prefix);
83
84 if(unlikely(!ff)) {
85 ff = procfile_open(filename, NULL, PROCFILE_FLAG_DEFAULT);
86 if(unlikely(!ff)) {
87 if(unlikely(!error_shown)) {
88 collector_error("IPC: Cannot open file '%s'.", filename);
89 error_shown = 1;
90 }
91 goto ipc;
92 }
93 }
94
95 ff = procfile_readall(ff);
96 if(unlikely(!ff)) {
97 if(unlikely(!error_shown)) {
98 collector_error("IPC: Cannot read file '%s'.", filename);
99 error_shown = 1;
100 }
101 goto ipc;
102 }
103
104 if(procfile_lines(ff) >= 1 && procfile_linewords(ff, 0) >= 4) {
105 lim->semvmx = SEMVMX;
106 lim->semmsl = str2i(procfile_lineword(ff, 0, 0));
107 lim->semmns = str2i(procfile_lineword(ff, 0, 1));
108 lim->semopm = str2i(procfile_lineword(ff, 0, 2));
109 lim->semmni = str2i(procfile_lineword(ff, 0, 3));
110 return 0;
111 }
112 else {
113 if(unlikely(!error_shown)) {
114 collector_error("IPC: Invalid content in file '%s'.", filename);
115 error_shown = 1;
116 }
117 goto ipc;
118 }
119
120 ipc:
121 // cannot do it from the file
122 // query IPC
123 {
124 struct seminfo seminfo = {.semmni = 0};
125 union semun arg = {.array = (ushort *) &seminfo};
126
127 if(unlikely(semctl(0, 0, IPC_INFO, arg) < 0)) {
128 collector_error("IPC: Failed to read '%s' and request IPC_INFO with semctl().", filename);
129 goto error;
130 }
131
132 lim->semvmx = SEMVMX;
133 lim->semmni = seminfo.semmni;
134 lim->semmsl = seminfo.semmsl;
135 lim->semmns = seminfo.semmns;
136 lim->semopm = seminfo.semopm;
137 return 0;
138 }
139
140 error:
141 lim->semvmx = 0;
142 lim->semmni = 0;
143 lim->semmsl = 0;
144 lim->semmns = 0;
145 lim->semopm = 0;
146 return -1;
147 }
148
149 /*
150 printf ("------ Semaphore Limits --------\n");
151 printf ("max number of arrays = %d\n", limits.semmni);
152 printf ("max semaphores per array = %d\n", limits.semmsl);
153 printf ("max semaphores system wide = %d\n", limits.semmns);
154 printf ("max ops per semop call = %d\n", limits.semopm);
155 printf ("semaphore max value = %u\n", limits.semvmx);
156
157 printf ("------ Semaphore Status --------\n");
158 printf ("used arrays = %d\n", status.semusz);
159 printf ("allocated semaphores = %d\n", status.semaem);
160 */
161
162 static inline int ipc_sem_get_status(struct ipc_status *st) {
163 struct seminfo seminfo;
164 union semun arg;
165
166 arg.array = (ushort *) (void *) &seminfo;
167
168 if(unlikely(semctl (0, 0, SEM_INFO, arg) < 0)) {
169 /* kernel not configured for semaphores */
170 static int error_shown = 0;
171 if(unlikely(!error_shown)) {
172 collector_error("IPC: kernel is not configured for semaphores");
173 error_shown = 1;
174 }
175 st->semusz = 0;
176 st->semaem = 0;
177 return -1;
178 }
179
180 st->semusz = seminfo.semusz;
181 st->semaem = seminfo.semaem;
182 return 0;
183 }
184
185 static int ipc_msq_get_info(const char *msg_filename, struct message_queue **message_queue_root) {
186 static procfile *ff;
187 struct message_queue *msq;
188
189 if(unlikely(!ff)) {
190 ff = procfile_open(msg_filename, " \t:", PROCFILE_FLAG_DEFAULT);
191 if(unlikely(!ff)) return 1;
192 }
193
194 ff = procfile_readall(ff);
195 if(unlikely(!ff)) return 1;
196
197 size_t lines = procfile_lines(ff);
198 size_t words = 0;
199
200 if(unlikely(lines < 2)) {
201 collector_error("Cannot read %s. Expected 2 or more lines, read %zu.", procfile_filename(ff), lines);
202 return 1;
203 }
204
205 // loop through all lines except the first and the last ones
206 size_t l;
207 for(l = 1; l < lines - 1; l++) {
208 words = procfile_linewords(ff, l);
209 if(unlikely(words < 2)) continue;
210 if(unlikely(words < 14)) {
211 collector_error("Cannot read %s line. Expected 14 params, read %zu.", procfile_filename(ff), words);
212 continue;
213 }
214
215 // find the id in the linked list or create a new structure
216 int found = 0;
217
218 unsigned long long id = str2ull(procfile_lineword(ff, l, 1), NULL);
219 for(msq = *message_queue_root; msq ; msq = msq->next) {
220 if(unlikely(id == msq->id)) {
221 found = 1;
222 break;
223 }
224 }
225
226 if(unlikely(!found)) {
227 msq = callocz(1, sizeof(struct message_queue));
228 msq->next = *message_queue_root;
229 *message_queue_root = msq;
230 msq->id = id;
231 }
232
233 msq->messages = str2ull(procfile_lineword(ff, l, 4), NULL);
234 msq->bytes = str2ull(procfile_lineword(ff, l, 3), NULL);
235 msq->found = 1;
236 }
237
238 return 0;
239 }
240
241 static int ipc_shm_get_info(const char *shm_filename, struct shm_stats *shm) {
242 static procfile *ff;
243
244 if(unlikely(!ff)) {
245 ff = procfile_open(shm_filename, " \t:", PROCFILE_FLAG_DEFAULT);
246 if(unlikely(!ff)) return 1;
247 }
248
249 ff = procfile_readall(ff);
250 if(unlikely(!ff)) return 1;
251
252 size_t lines = procfile_lines(ff);
253 size_t words = 0;
254
255 if(unlikely(lines < 2)) {
256 collector_error("Cannot read %s. Expected 2 or more lines, read %zu.", procfile_filename(ff), lines);
257 return 1;
258 }
259
260 shm->segments = 0;
261 shm->bytes = 0;
262
263 // loop through all lines except the first and the last ones
264 size_t l;
265 for(l = 1; l < lines - 1; l++) {
266 words = procfile_linewords(ff, l);
267 if(unlikely(words < 2)) continue;
268 if(unlikely(words < 16)) {
269 collector_error("Cannot read %s line. Expected 16 params, read %zu.", procfile_filename(ff), words);
270 continue;
271 }
272
273 shm->segments++;
274 shm->bytes += str2ull(procfile_lineword(ff, l, 3), NULL);
275 }
276
277 return 0;
278 }
279
280 static const RRDVAR_ACQUIRED *arrays_max = NULL, *semaphores_max = NULL;
281 void proc_ipc_cleanup(void) {
282 if(arrays_max)
283 rrdvar_host_variable_release(localhost, arrays_max);
284
285 if(semaphores_max)
286 rrdvar_host_variable_release(localhost, semaphores_max);
287 }
288
289 int do_proc_ipc(int update_every, usec_t dt) {
290 (void)dt;
291
292 static int do_sem = -1, do_msg = -1, do_shm = -1;
293 static int read_limits_next = -1;
294 static struct ipc_limits limits;
295 static struct ipc_status status;
296 static RRDSET *st_arrays = NULL;
297 static RRDDIM *rd_arrays = NULL;
298 static const char *msg_filename = NULL;
299 static struct message_queue *message_queue_root = NULL;
300 static long long dimensions_limit;
301 static const char *shm_filename = NULL;
302
303 if(unlikely(do_sem == -1)) {
304 do_msg = inicfg_get_boolean(&netdata_config, "plugin:proc:ipc", "message queues", CONFIG_BOOLEAN_YES);
305 do_sem = inicfg_get_boolean(&netdata_config, "plugin:proc:ipc", "semaphore totals", CONFIG_BOOLEAN_YES);
306 do_shm = inicfg_get_boolean(&netdata_config, "plugin:proc:ipc", "shared memory totals", CONFIG_BOOLEAN_YES);
307
308 char filename[FILENAME_MAX + 1];
309
310 snprintfz(filename, FILENAME_MAX, "%s%s", netdata_configured_host_prefix, "/proc/sysvipc/msg");
311 msg_filename = inicfg_get(&netdata_config, "plugin:proc:ipc", "msg filename to monitor", filename);
312
313 snprintfz(filename, FILENAME_MAX, "%s%s", netdata_configured_host_prefix, "/proc/sysvipc/shm");
314 shm_filename = inicfg_get(&netdata_config, "plugin:proc:ipc", "shm filename to monitor", filename);
315
316 dimensions_limit = inicfg_get_number(&netdata_config, "plugin:proc:ipc", "max dimensions in memory allowed", 50);
317
318 // make sure it works
319 if(ipc_sem_get_limits(&limits) == -1) {
320 collector_error("unable to fetch semaphore limits");
321 do_sem = CONFIG_BOOLEAN_NO;
322 }
323 else if(ipc_sem_get_status(&status) == -1) {
324 collector_error("unable to fetch semaphore statistics");
325 do_sem = CONFIG_BOOLEAN_NO;
326 }
327 else {
328 // create the chart
329 if(unlikely(!st_arrays)) {
330 st_arrays = rrdset_create_localhost(
331 "system"
332 , "ipc_semaphore_arrays"
333 , NULL
334 , "ipc semaphores"
335 , NULL
336 , "IPC Semaphore Arrays"
337 , "arrays"
338 , PLUGIN_PROC_NAME
339 , "ipc"
340 , NETDATA_CHART_PRIO_SYSTEM_IPC_SEM_ARRAYS
341 , localhost->rrd_update_every
342 , RRDSET_TYPE_AREA
343 );
344 rd_arrays = rrddim_add(st_arrays, "arrays", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
345 }
346
347 // variables
348 semaphores_max = rrdvar_host_variable_add_and_acquire(localhost, "ipc_semaphores_max");
349 arrays_max = rrdvar_host_variable_add_and_acquire(localhost, "ipc_semaphores_arrays_max");
350 }
351
352 struct stat stbuf;
353 if (stat(msg_filename, &stbuf)) {
354 do_msg = CONFIG_BOOLEAN_NO;
355 }
356
357 if(unlikely(do_sem == CONFIG_BOOLEAN_NO && do_msg == CONFIG_BOOLEAN_NO)) {
358 collector_error("ipc module disabled");
359 return 1;
360 }
361 }
362
363 if(likely(do_sem != CONFIG_BOOLEAN_NO)) {
364 if(unlikely(read_limits_next < 0)) {
365 if(unlikely(ipc_sem_get_limits(&limits) == -1)) {
366 collector_error("Unable to fetch semaphore limits.");
367 }
368 else {
369 if(semaphores_max)
370 rrdvar_host_variable_set(localhost, semaphores_max, limits.semmns);
371 if(arrays_max)
372 rrdvar_host_variable_set(localhost, arrays_max, limits.semmni);
373
374 st_arrays->red = limits.semmni;
375
376 read_limits_next = 60 / update_every;
377 }
378 }
379 else
380 read_limits_next--;
381
382 if(unlikely(ipc_sem_get_status(&status) == -1)) {
383 collector_error("Unable to get semaphore statistics");
384 return 0;
385 }
386
387 common_semaphore_ipc(status.semaem, limits.semmns, "ipc", localhost->rrd_update_every);
388
389 rrddim_set_by_pointer(st_arrays, rd_arrays, status.semusz);
390 rrdset_done(st_arrays);
391 }
392
393 if(likely(do_msg != CONFIG_BOOLEAN_NO)) {
394 static RRDSET *st_msq_messages = NULL, *st_msq_bytes = NULL;
395
396 int ret = ipc_msq_get_info(msg_filename, &message_queue_root);
397
398 if(!ret && message_queue_root) {
399 if(unlikely(!st_msq_messages))
400 st_msq_messages = rrdset_create_localhost(
401 "system"
402 , "message_queue_messages"
403 , NULL
404 , "ipc message queues"
405 , NULL
406 , "IPC Message Queue Number of Messages"
407 , "messages"
408 , PLUGIN_PROC_NAME
409 , "ipc"
410 , NETDATA_CHART_PRIO_SYSTEM_IPC_MSQ_MESSAGES
411 , update_every
412 , RRDSET_TYPE_STACKED
413 );
414
415 if(unlikely(!st_msq_bytes))
416 st_msq_bytes = rrdset_create_localhost(
417 "system"
418 , "message_queue_bytes"
419 , NULL
420 , "ipc message queues"
421 , NULL
422 , "IPC Message Queue Used Bytes"
423 , "bytes"
424 , PLUGIN_PROC_NAME
425 , "ipc"
426 , NETDATA_CHART_PRIO_SYSTEM_IPC_MSQ_SIZE
427 , update_every
428 , RRDSET_TYPE_STACKED
429 );
430
431 struct message_queue *msq = message_queue_root, *msq_prev = NULL;
432 while(likely(msq)){
433 if(likely(msq->found)) {
434 if(unlikely(!msq->rd_messages || !msq->rd_bytes)) {
435 char id[RRD_ID_LENGTH_MAX + 1];
436 snprintfz(id, RRD_ID_LENGTH_MAX, "%llu", msq->id);
437 if(likely(!msq->rd_messages)) msq->rd_messages = rrddim_add(st_msq_messages, id, NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
438 if(likely(!msq->rd_bytes)) msq->rd_bytes = rrddim_add(st_msq_bytes, id, NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
439 }
440
441 rrddim_set_by_pointer(st_msq_messages, msq->rd_messages, msq->messages);
442 rrddim_set_by_pointer(st_msq_bytes, msq->rd_bytes, msq->bytes);
443
444 msq->found = 0;
445 }
446 else {
447 rrddim_is_obsolete___safe_from_collector_thread(st_msq_messages, msq->rd_messages);
448 rrddim_is_obsolete___safe_from_collector_thread(st_msq_bytes, msq->rd_bytes);
449
450 // remove message queue from the linked list
451 if(!msq_prev)
452 message_queue_root = msq->next;
453 else
454 msq_prev->next = msq->next;
455 freez(msq);
456 msq = NULL;
457 }
458 if(likely(msq)) {
459 msq_prev = msq;
460 msq = msq->next;
461 }
462 else if(!msq_prev)
463 msq = message_queue_root;
464 else
465 msq = msq_prev->next;
466 }
467
468 rrdset_done(st_msq_messages);
469 rrdset_done(st_msq_bytes);
470
471 long long dimensions_num = rrdset_number_of_dimensions(st_msq_messages);
472
473 if(unlikely(dimensions_num > dimensions_limit)) {
474 collector_info("Message queue statistics has been disabled");
475 collector_info("There are %lld dimensions in memory but limit was set to %lld", dimensions_num, dimensions_limit);
476 rrdset_is_obsolete___safe_from_collector_thread(st_msq_messages);
477 rrdset_is_obsolete___safe_from_collector_thread(st_msq_bytes);
478 st_msq_messages = NULL;
479 st_msq_bytes = NULL;
480 do_msg = CONFIG_BOOLEAN_NO;
481 }
482 else if(unlikely(!message_queue_root)) {
483 collector_info("Making chart %s (%s) obsolete since it does not have any dimensions", rrdset_name(st_msq_messages), rrdset_id(st_msq_messages));
484 rrdset_is_obsolete___safe_from_collector_thread(st_msq_messages);
485 st_msq_messages = NULL;
486
487 collector_info("Making chart %s (%s) obsolete since it does not have any dimensions", rrdset_name(st_msq_bytes), rrdset_id(st_msq_bytes));
488 rrdset_is_obsolete___safe_from_collector_thread(st_msq_bytes);
489 st_msq_bytes = NULL;
490 }
491 }
492 }
493
494 if(likely(do_shm != CONFIG_BOOLEAN_NO)) {
495 static RRDSET *st_shm_segments = NULL, *st_shm_bytes = NULL;
496 static RRDDIM *rd_shm_segments = NULL, *rd_shm_bytes = NULL;
497 struct shm_stats shm;
498
499 if(!ipc_shm_get_info(shm_filename, &shm)) {
500 if(unlikely(!st_shm_segments)) {
501 st_shm_segments = rrdset_create_localhost(
502 "system"
503 , "shared_memory_segments"
504 , NULL
505 , "ipc shared memory"
506 , NULL
507 , "IPC Shared Memory Number of Segments"
508 , "segments"
509 , PLUGIN_PROC_NAME
510 , "ipc"
511 , NETDATA_CHART_PRIO_SYSTEM_IPC_SHARED_MEM_SEGS
512 , update_every
513 , RRDSET_TYPE_STACKED
514 );
515
516 rd_shm_segments = rrddim_add(st_shm_segments, "segments", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
517 }
518
519 rrddim_set_by_pointer(st_shm_segments, rd_shm_segments, shm.segments);
520 rrdset_done(st_shm_segments);
521
522 if(unlikely(!st_shm_bytes)) {
523 st_shm_bytes = rrdset_create_localhost(
524 "system"
525 , "shared_memory_bytes"
526 , NULL
527 , "ipc shared memory"
528 , NULL
529 , "IPC Shared Memory Used Bytes"
530 , "bytes"
531 , PLUGIN_PROC_NAME
532 , "ipc"
533 , NETDATA_CHART_PRIO_SYSTEM_IPC_SHARED_MEM_SIZE
534 , update_every
535 , RRDSET_TYPE_STACKED
536 );
537
538 rd_shm_bytes = rrddim_add(st_shm_bytes, "bytes", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
539 }
540
541 rrddim_set_by_pointer(st_shm_bytes, rd_shm_bytes, shm.bytes);
542 rrdset_done(st_shm_bytes);
543 }
544 }
545
546 return 0;
547 }