1
+// SPDX-License-Identifier: GPL-3.0-or-later
2
+
3
+
4
+#include "ebpf.h"
5
+#include "ebpf_apps.h"
6
+
7
+// ----------------------------------------------------------------------------
8
+// internal flags
9
+// handled in code (automatically set)
10
+
11
+static int
12
+ proc_pid_cmdline_is_needed = 0; // 1 when we need to read /proc/cmdline
13
+
14
+/*****************************************************************
15
+ *
16
+ * FUNCTIONS USED TO READ HASH TABLES
17
+ *
18
+ *****************************************************************/
19
+
20
+/**
21
+ * Read statistic hash table.
22
+ *
23
+ * @param ep the output structure.
24
+ * @param fd the file descriptor mapped from kernel ring.
25
+ * @param pid the index used to select the data.
26
+ * @param bpf_map_lookup_elem a pointer for the function used to read data.
27
+ *
28
+ * @return It returns 0 when the data was copied and -1 otherwise
29
+ */
30
+#ifndef STATIC
31
+int ebpf_read_hash_table(void *ep, int fd, uint32_t pid,
32
+ int (*bpf_map_lookup_elem)(int, const void *, void *))
33
+#else
34
+int ebpf_read_hash_table(void *ep, int fd, pid_t pid)
35
+#endif
36
+{
37
+ if (!ep)
38
+ return -1;
39
+
40
+ if (!bpf_map_lookup_elem(fd, &pid, ep))
41
+ return 0;
42
+
43
+ return -1;
44
+}
45
+
46
+/**
47
+ * Read socket statistic
48
+ *
49
+ * Read information from kernel ring to user ring.
50
+ *
51
+ * @param ep the table with all process stats values.
52
+ * @param fd the file descriptor mapped from kernel
53
+ * @param ef a pointer for the functions mapped from dynamic library
54
+ * @param pids the list of pids associated to a target.
55
+ *
56
+ * @return
57
+ */
58
+#ifndef STATIC
59
+size_t read_bandwidth_statistic_using_pid_on_target(ebpf_bandwidth_t **ep, int fd,
60
+ ebpf_functions_t *ef, struct pid_on_target *pids)
61
+#else
62
+size_t read_bandwidth_statistic_using_pid_on_target(ebpf_bandwidth_t **ep, int fd,struct pid_on_target *pids)
63
+#endif
64
+{
65
+ size_t count = 0;
66
+ while(pids) {
67
+ uint32_t current_pid = pids->pid;
68
+#ifndef STATIC
69
+ if (!ebpf_read_hash_table(ep[current_pid], fd, current_pid, ef->bpf_map_lookup_elem))
70
+#else
71
+ if (!ebpf_read_hash_table(ep[current_pid], fd, current_pid))
72
+#endif
73
+ count++;
74
+
75
+ pids = pids->next;
76
+ }
77
+
78
+ return count;
79
+}
80
+
81
+/**
82
+ * Read bandwidth statistic using hash table
83
+ *
84
+ * @param out the output tensor that will receive the information.
85
+ * @param fd the file descriptor that has the data
86
+ * @param bpf_map_lookup_elem a pointer for the function to read the data
87
+ * @param bpf_map_get_next_key a pointer fo the function to read the index.
88
+ */
89
+#ifndef STATIC
90
+size_t read_bandwidth_statistic_using_hash_table(ebpf_bandwidth_t **out, int fd,
91
+ int (*bpf_map_lookup_elem)(int, const void *, void *),
92
+ int (*bpf_map_get_next_key)(int, const void *, void *))
93
+#else
94
+size_t read_bandwidth_statistic_using_hash_table(ebpf_bandwidth_t **out, int fd)
95
+#endif
96
+{
97
+ size_t count = 0;
98
+ uint32_t key =0;
99
+ uint32_t next_key = 0;
100
+
101
+ while (bpf_map_get_next_key(fd, &key, &next_key) == 0) {
102
+ ebpf_bandwidth_t *eps = out[next_key];
103
+ if (!eps) {
104
+ eps = callocz(1, sizeof(ebpf_process_stat_t));
105
+ out[next_key] = eps;
106
+ }
107
+#ifndef STATIC
108
+ ebpf_read_hash_table(eps, fd, next_key, bpf_map_lookup_elem);
109
+#else
110
+ ebpf_read_hash_table(eps, fd, next_key);
111
+#endif
112
+ }
113
+
114
+ return count;
115
+}
116
+
117
+/*****************************************************************
118
+ *
119
+ * FUNCTIONS CALLED FROM COLLECTORS
120
+ *
121
+ *****************************************************************/
122
+
123
+/**
124
+ * Am I running as Root
125
+ *
126
+ * Verify the user that is running the collector.
127
+ *
128
+ * @return It returns 1 for root and 0 otherwise.
129
+ */
130
+int am_i_running_as_root() {
131
+ uid_t uid = getuid(), euid = geteuid();
132
+
133
+ if(uid == 0 || euid == 0) {
134
+ return 1;
135
+ }
136
+
137
+ return 0;
138
+}
139
+
140
+/**
141
+ * Reset the target values
142
+ *
143
+ * @param root the pointer to the chain that will be reseted.
144
+ *
145
+ * @return it returns the number of structures that was reseted.
146
+ */
147
+size_t zero_all_targets(struct target *root) {
148
+ struct target *w;
149
+ size_t count = 0;
150
+
151
+ for (w = root; w ; w = w->next) {
152
+ count++;
153
+
154
+ /* These variables are not necessary for eBPF collector
155
+ w->minflt = 0;
156
+ w->majflt = 0;
157
+ w->utime = 0;
158
+ w->stime = 0;
159
+ w->gtime = 0;
160
+ w->cminflt = 0;
161
+ w->cmajflt = 0;
162
+ w->cutime = 0;
163
+ w->cstime = 0;
164
+ w->cgtime = 0;
165
+ w->num_threads = 0;
166
+ // w->rss = 0;
167
+ w->processes = 0;
168
+
169
+ w->status_vmsize = 0;
170
+ w->status_vmrss = 0;
171
+ w->status_vmshared = 0;
172
+ w->status_rssfile = 0;
173
+ w->status_rssshmem = 0;
174
+ w->status_vmswap = 0;
175
+
176
+ w->io_logical_bytes_read = 0;
177
+ w->io_logical_bytes_written = 0;
178
+ // w->io_read_calls = 0;
179
+ // w->io_write_calls = 0;
180
+ w->io_storage_bytes_read = 0;
181
+ w->io_storage_bytes_written = 0;
182
+ // w->io_cancelled_write_bytes = 0;
183
+
184
+ // zero file counters
185
+ if(w->target_fds) {
186
+ memset(w->target_fds, 0, sizeof(int) * w->target_fds_size);
187
+ w->openfiles = 0;
188
+ w->openpipes = 0;
189
+ w->opensockets = 0;
190
+ w->openinotifies = 0;
191
+ w->openeventfds = 0;
192
+ w->opentimerfds = 0;
193
+ w->opensignalfds = 0;
194
+ w->openeventpolls = 0;
195
+ w->openother = 0;
196
+ }
197
+
198
+ w->collected_starttime = 0;
199
+ w->uptime_min = 0;
200
+ w->uptime_sum = 0;
201
+ w->uptime_max = 0;
202
+ */
203
+
204
+ if(unlikely(w->root_pid)) {
205
+ struct pid_on_target *pid_on_target = w->root_pid;
206
+
207
+ while(pid_on_target) {
208
+ struct pid_on_target *pid_on_target_to_free = pid_on_target;
209
+ pid_on_target = pid_on_target->next;
210
+ free(pid_on_target_to_free);
211
+ }
212
+
213
+ w->root_pid = NULL;
214
+ }
215
+ }
216
+
217
+ return count;
218
+}
219
+
220
+/**
221
+ * Clean the allocated structures
222
+ *
223
+ * @param agrt the pointer to be cleaned.
224
+ */
225
+void clean_apps_groups_target(struct target *agrt) {
226
+ struct target *current_target;
227
+ while (agrt) {
228
+ current_target = agrt;
229
+ agrt = current_target->target;
230
+
231
+ freez(current_target);
232
+ }
233
+}
234
+
235
+/**
236
+ * Find or create a new target
237
+ * there are targets that are just aggregated to other target (the second argument)
238
+ *
239
+ * @param id
240
+ * @param target
241
+ * @param name
242
+ *
243
+ * @return It returns the target on success and NULL otherwise
244
+ */
245
+struct target *get_apps_groups_target(struct target **agrt, const char *id,
246
+ struct target *target, const char *name) {
247
+ int tdebug = 0, thidden = target?target->hidden:0, ends_with = 0;
248
+ const char *nid = id;
249
+
250
+ // extract the options
251
+ while(nid[0] == '-' || nid[0] == '+' || nid[0] == '*') {
252
+ if(nid[0] == '-') thidden = 1;
253
+ if(nid[0] == '+') tdebug = 1;
254
+ if(nid[0] == '*') ends_with = 1;
255
+ nid++;
256
+ }
257
+ uint32_t hash = simple_hash(id);
258
+
259
+ // find if it already exists
260
+ struct target *w, *last = *agrt;
261
+ for(w = *agrt ; w ; w = w->next) {
262
+ if(w->idhash == hash && strncmp(nid, w->id, MAX_NAME) == 0)
263
+ return w;
264
+
265
+ last = w;
266
+ }
267
+
268
+ // find an existing target
269
+ if(unlikely(!target)) {
270
+ while(*name == '-') {
271
+ if(*name == '-') thidden = 1;
272
+ name++;
273
+ }
274
+
275
+ for(target = *agrt ; target != NULL ; target = target->next) {
276
+ if(!target->target && strcmp(name, target->name) == 0)
277
+ break;
278
+ }
279
+ }
280
+
281
+ if(target && target->target)
282
+ fatal("Internal Error: request to link process '%s' to target '%s' which is linked to target '%s'", id, target->id, target->target->id);
283
+
284
+ w = callocz(1, sizeof(struct target));
285
+ strncpyz(w->id, nid, MAX_NAME);
286
+ w->idhash = simple_hash(w->id);
287
+
288
+ if(unlikely(!target))
289
+ // copy the name
290
+ strncpyz(w->name, name, MAX_NAME);
291
+ else
292
+ // copy the id
293
+ strncpyz(w->name, nid, MAX_NAME);
294
+
295
+ strncpyz(w->compare, nid, MAX_COMPARE_NAME);
296
+ size_t len = strlen(w->compare);
297
+ if(w->compare[len - 1] == '*') {
298
+ w->compare[len - 1] = '\0';
299
+ w->starts_with = 1;
300
+ }
301
+ w->ends_with = ends_with;
302
+
303
+ if(w->starts_with && w->ends_with)
304
+ proc_pid_cmdline_is_needed = 1;
305
+
306
+ w->comparehash = simple_hash(w->compare);
307
+ w->comparelen = strlen(w->compare);
308
+
309
+ w->hidden = thidden;
310
+#ifdef NETDATA_INTERNAL_CHECKS
311
+ w->debug_enabled = tdebug;
312
+#else
313
+ if(tdebug)
314
+ fprintf(stderr, "apps.plugin has been compiled without debugging\n");
315
+#endif
316
+ w->target = target;
317
+
318
+ // append it, to maintain the order in apps_groups.conf
319
+ if(last) last->next = w;
320
+ else *agrt = w;
321
+
322
+ return w;
323
+}
324
+
325
+/**
326
+ * Read the apps_groups.conf file
327
+ *
328
+ * @param agrt a pointer to apps_group_root_target
329
+ * @param path the directory to search apps_%s.conf
330
+ * @param file the word to complement the file name.
331
+ *
332
+ * @return It returns 0 on succcess and -1 otherwise
333
+ */
334
+int ebpf_read_apps_groups_conf(struct target **agdt, struct target **agrt,
335
+ const char *path, const char *file)
336
+{
337
+ char filename[FILENAME_MAX + 1];
338
+
339
+ snprintfz(filename, FILENAME_MAX, "%s/apps_%s.conf", path, file);
340
+
341
+ // ----------------------------------------
342
+
343
+ procfile *ff = procfile_open(filename, " :\t", PROCFILE_FLAG_DEFAULT);
344
+ if(!ff) return -1;
345
+
346
+ procfile_set_quotes(ff, "'\"");
347
+
348
+ ff = procfile_readall(ff);
349
+ if(!ff)
350
+ return -1;
351
+
352
+ size_t line, lines = procfile_lines(ff);
353
+
354
+ for (line = 0; line < lines ;line++) {
355
+ size_t word, words = procfile_linewords(ff, line);
356
+ if(!words) continue;
357
+
358
+ char *name = procfile_lineword(ff, line, 0);
359
+ if (!name || !*name) continue;
360
+
361
+ // find a possibly existing target
362
+ struct target *w = NULL;
363
+
364
+ // loop through all words, skipping the first one (the name)
365
+ for (word = 0; word < words ;word++) {
366
+ char *s = procfile_lineword(ff, line, word);
367
+ if (!s || !*s) continue;
368
+ if (*s == '#') break;
369
+
370
+ // is this the first word? skip it
371
+ if (s == name) continue;
372
+
373
+ // add this target
374
+ struct target *n = get_apps_groups_target(agrt, s, w, name);
375
+ if (!n) {
376
+ error("Cannot create target '%s' (line %zu, word %zu)", s, line, word);
377
+ continue;
378
+ }
379
+
380
+ // just some optimization
381
+ // to avoid searching for a target for each process
382
+ if (!w) w = n->target?n->target:n;
383
+ }
384
+ }
385
+
386
+ procfile_close(ff);
387
+
388
+ *agdt = get_apps_groups_target(agrt, "p+!o@w#e$i^r&7*5(-i)l-o_",
389
+ NULL, "other"); // match nothing
390
+ if(!*agdt)
391
+ fatal("Cannot create default target");
392
+
393
+ struct target *ptr = *agdt;
394
+ if (ptr->target)
395
+ *agdt = ptr->target;
396
+
397
+ return 0;
398
+}
399
+
400
+// the minimum PID of the system
401
+// this is also the pid of the init process
402
+#define INIT_PID 1
403
+
404
+// ----------------------------------------------------------------------------
405
+// string lengths
406
+
407
+#define MAX_COMPARE_NAME 100
408
+#define MAX_NAME 100
409
+#define MAX_CMDLINE 16384
410
+
411
+struct pid_stat **all_pids = NULL; // to avoid allocations, we pre-allocate the
412
+ // the entire pid space.
413
+struct pid_stat *root_of_pids = NULL; // global list of all processes running
414
+
415
+size_t
416
+ all_pids_count = 0; // the number of processes running
417
+
418
+struct target
419
+ *apps_groups_default_target = NULL, // the default target
420
+ *apps_groups_root_target = NULL, // apps_groups.conf defined
421
+ *users_root_target = NULL, // users
422
+ *groups_root_target = NULL; // user groups
423
+
424
+size_t
425
+ apps_groups_targets_count = 0; // # of apps_groups.conf targets
426
+
427
+
428
+// ----------------------------------------------------------------------------
429
+// internal counters
430
+
431
+static size_t
432
+ // global_iterations_counter = 1,
433
+ calls_counter = 0,
434
+ // file_counter = 0,
435
+ // filenames_allocated_counter = 0,
436
+ // inodes_changed_counter = 0,
437
+ // links_changed_counter = 0,
438
+ targets_assignment_counter = 0;
439
+
440
+// ----------------------------------------------------------------------------
441
+// debugging
442
+
443
+// log each problem once per process
444
+// log flood protection flags (log_thrown)
445
+#define PID_LOG_IO 0x00000001
446
+#define PID_LOG_STATUS 0x00000002
447
+#define PID_LOG_CMDLINE 0x00000004
448
+#define PID_LOG_FDS 0x00000008
449
+#define PID_LOG_STAT 0x00000010
450
+
451
+int debug_enabled = 0;
452
+
453
+#ifdef NETDATA_INTERNAL_CHECKS
454
+
455
+#define debug_log(fmt, args...) do { if(unlikely(debug_enabled)) debug_log_int(fmt, ##args); } while(0)
456
+
457
+#else
458
+
459
+static inline void debug_log_dummy(void) {}
460
+#define debug_log(fmt, args...) debug_log_dummy()
461
+
462
+#endif
463
+
464
+/**
465
+ * Managed log
466
+ *
467
+ * Store log information if it is necessary.
468
+ *
469
+ * @param p the pid stat structure
470
+ * @param log the log id
471
+ * @param status the return from a function.
472
+ *
473
+ * @return It returns the status value.
474
+ */
475
+static inline int managed_log(struct pid_stat *p, uint32_t log, int status) {
476
+ if(unlikely(!status)) {
477
+ // error("command failed log %u, errno %d", log, errno);
478
+
479
+ if(unlikely(debug_enabled || errno != ENOENT)) {
480
+ if(unlikely(debug_enabled || !(p->log_thrown & log))) {
481
+ p->log_thrown |= log;
482
+ switch(log) {
483
+ case PID_LOG_IO:
484
+ error("Cannot process %s/proc/%d/io (command '%s')", netdata_configured_host_prefix, p->pid, p->comm);
485
+ break;
486
+
487
+ case PID_LOG_STATUS:
488
+ error("Cannot process %s/proc/%d/status (command '%s')", netdata_configured_host_prefix, p->pid, p->comm);
489
+ break;
490
+
491
+ case PID_LOG_CMDLINE:
492
+ error("Cannot process %s/proc/%d/cmdline (command '%s')", netdata_configured_host_prefix, p->pid, p->comm);
493
+ break;
494
+
495
+ case PID_LOG_FDS:
496
+ error("Cannot process entries in %s/proc/%d/fd (command '%s')", netdata_configured_host_prefix, p->pid, p->comm);
497
+ break;
498
+
499
+ case PID_LOG_STAT:
500
+ break;
501
+
502
+ default:
503
+ error("unhandled error for pid %d, command '%s'", p->pid, p->comm);
504
+ break;
505
+ }
506
+ }
507
+ }
508
+ errno = 0;
509
+ }
510
+ else if(unlikely(p->log_thrown & log)) {
511
+ // error("unsetting log %u on pid %d", log, p->pid);
512
+ p->log_thrown &= ~log;
513
+ }
514
+
515
+ return status;
516
+}
517
+
518
+/**
519
+ * Get PID entry
520
+ *
521
+ * Get or allocate the PID entry for the specifid pid.
522
+ *
523
+ * @param pid the pid to search the data.
524
+ *
525
+ * @return It returns the pid entry structure
526
+ */
527
+static inline struct pid_stat *get_pid_entry(pid_t pid) {
528
+ if(unlikely(all_pids[pid]))
529
+ return all_pids[pid];
530
+
531
+ struct pid_stat *p = callocz(1, sizeof(struct pid_stat));
532
+
533
+ if(likely(root_of_pids))
534
+ root_of_pids->prev = p;
535
+
536
+ p->next = root_of_pids;
537
+ root_of_pids = p;
538
+
539
+ p->pid = pid;
540
+
541
+ all_pids[pid] = p;
542
+ all_pids_count++;
543
+
544
+ return p;
545
+}
546
+
547
+/**
548
+ * Assign the PID to a target.
549
+ *
550
+ * @param p the pid_stat structure to assign for a target.
551
+ */
552
+static inline void assign_target_to_pid(struct pid_stat *p) {
553
+ targets_assignment_counter++;
554
+
555
+ uint32_t hash = simple_hash(p->comm);
556
+ size_t pclen = strlen(p->comm);
557
+
558
+ struct target *w;
559
+ for(w = apps_groups_root_target; w ; w = w->next) {
560
+ // if(debug_enabled || (p->target && p->target->debug_enabled)) debug_log_int("\t\tcomparing '%s' with '%s'", w->compare, p->comm);
561
+
562
+ // find it - 4 cases:
563
+ // 1. the target is not a pattern
564
+ // 2. the target has the prefix
565
+ // 3. the target has the suffix
566
+ // 4. the target is something inside cmdline
567
+
568
+ if(unlikely(( (!w->starts_with && !w->ends_with && w->comparehash == hash && !strcmp(w->compare, p->comm))
569
+ || (w->starts_with && !w->ends_with && !strncmp(w->compare, p->comm, w->comparelen))
570
+ || (!w->starts_with && w->ends_with && pclen >= w->comparelen && !strcmp(w->compare, &p->comm[pclen - w->comparelen]))
571
+ || (proc_pid_cmdline_is_needed && w->starts_with && w->ends_with && p->cmdline && strstr(p->cmdline, w->compare))
572
+ ))) {
573
+
574
+ if(w->target) p->target = w->target;
575
+ else p->target = w;
576
+
577
+ if(debug_enabled || (p->target && p->target->debug_enabled))
578
+ debug_log_int("%s linked to target %s", p->comm, p->target->name);
579
+
580
+ break;
581
+ }
582
+ }
583
+}
584
+
585
+// ----------------------------------------------------------------------------
586
+// update pids from proc
587
+
588
+/**
589
+ * Read cmd line from /proc/PID/cmdline
590
+ *
591
+ * @param p the pid_stat_structure.
592
+ *
593
+ * @return It returns 1 on success and 0 otherwise.
594
+ */
595
+static inline int read_proc_pid_cmdline(struct pid_stat *p) {
596
+ static char cmdline[MAX_CMDLINE + 1];
597
+
598
+ if(unlikely(!p->cmdline_filename)) {
599
+ char filename[FILENAME_MAX + 1];
600
+ snprintfz(filename, FILENAME_MAX, "%s/proc/%d/cmdline", netdata_configured_host_prefix, p->pid);
601
+ p->cmdline_filename = strdupz(filename);
602
+ }
603
+
604
+ int fd = open(p->cmdline_filename, procfile_open_flags, 0666);
605
+ if(unlikely(fd == -1)) goto cleanup;
606
+
607
+ ssize_t i, bytes = read(fd, cmdline, MAX_CMDLINE);
608
+ close(fd);
609
+
610
+ if(unlikely(bytes < 0)) goto cleanup;
611
+
612
+ cmdline[bytes] = '\0';
613
+ for(i = 0; i < bytes ; i++) {
614
+ if(unlikely(!cmdline[i])) cmdline[i] = ' ';
615
+ }
616
+
617
+ if(p->cmdline) freez(p->cmdline);
618
+ p->cmdline = strdupz(cmdline);
619
+
620
+ debug_log("Read file '%s' contents: %s", p->cmdline_filename, p->cmdline);
621
+
622
+ return 1;
623
+
624
+cleanup:
625
+ // copy the command to the command line
626
+ if(p->cmdline) freez(p->cmdline);
627
+ p->cmdline = strdupz(p->comm);
628
+ return 0;
629
+}
630
+
631
+/**
632
+ * Read information from /proc/PID/stat and /proc/PID/cmdline
633
+ * Assign target to pid
634
+ *
635
+ * @param p the pid stat structure to store the data.
636
+ * @param ptr an useless argument.
637
+ */
638
+static inline int read_proc_pid_stat(struct pid_stat *p, void *ptr) {
639
+ (void)ptr;
640
+
641
+ static procfile *ff = NULL;
642
+
643
+ if(unlikely(!p->stat_filename)) {
644
+ char filename[FILENAME_MAX + 1];
645
+ snprintfz(filename, FILENAME_MAX, "%s/proc/%d/stat", netdata_configured_host_prefix, p->pid);
646
+ p->stat_filename = strdupz(filename);
647
+ }
648
+
649
+ int set_quotes = (!ff)?1:0;
650
+
651
+ struct stat statbuf;
652
+ if (stat(p->stat_filename, &statbuf)) return 0;
653
+
654
+ ff = procfile_reopen(ff, p->stat_filename, NULL, PROCFILE_FLAG_NO_ERROR_ON_FILE_IO);
655
+ if(unlikely(!ff)) return 0;
656
+
657
+ if(unlikely(set_quotes))
658
+ procfile_set_open_close(ff, "(", ")");
659
+
660
+ ff = procfile_readall(ff);
661
+ if(unlikely(!ff)) return 0;
662
+
663
+ p->last_stat_collected_usec = p->stat_collected_usec;
664
+ p->stat_collected_usec = now_monotonic_usec();
665
+ calls_counter++;
666
+
667
+ char *comm = procfile_lineword(ff, 0, 1);
668
+ p->ppid = (int32_t)str2pid_t(procfile_lineword(ff, 0, 3));
669
+
670
+ if(strcmp(p->comm, comm) != 0) {
671
+ if(unlikely(debug_enabled)) {
672
+ if(p->comm[0])
673
+ debug_log("\tpid %d (%s) changed name to '%s'", p->pid, p->comm, comm);
674
+ else
675
+ debug_log("\tJust added %d (%s)", p->pid, comm);
676
+ }
677
+
678
+ strncpyz(p->comm, comm, MAX_COMPARE_NAME);
679
+
680
+ // /proc/<pid>/cmdline
681
+ if(likely(proc_pid_cmdline_is_needed))
682
+ managed_log(p, PID_LOG_CMDLINE, read_proc_pid_cmdline(p));
683
+
684
+ assign_target_to_pid(p);
685
+ }
686
+
687
+ if(unlikely(debug_enabled || (p->target && p->target->debug_enabled)))
688
+ debug_log_int(
689
+ "READ PROC/PID/STAT: %s/proc/%d/stat, process: '%s' on target '%s' (dt=%llu)",
690
+ netdata_configured_host_prefix, p->pid, p->comm, (p->target) ? p->target->name : "UNSET",
691
+ p->stat_collected_usec - p->last_stat_collected_usec);
692
+
693
+ return 1;
694
+}
695
+
696
+/**
697
+ * Collect data for PID
698
+ *
699
+ * @param pid the current pid that we are working
700
+ * @param ptr a NULL value
701
+ *
702
+ * @return It returns 1 on succcess and 0 otherwise
703
+ */
704
+static inline int collect_data_for_pid(pid_t pid, void *ptr) {
705
+ if(unlikely(pid < 0 || pid > pid_max)) {
706
+ error("Invalid pid %d read (expected %d to %d). Ignoring process.", pid, 0, pid_max);
707
+ return 0;
708
+ }
709
+
710
+ struct pid_stat *p = get_pid_entry(pid);
711
+ if(unlikely(!p || p->read)) return 0;
712
+ p->read = 1;
713
+
714
+ if(unlikely(!managed_log(p, PID_LOG_STAT, read_proc_pid_stat(p, ptr))))
715
+ // there is no reason to proceed if we cannot get its status
716
+ return 0;
717
+
718
+
719
+ // check its parent pid
720
+ if(unlikely(p->ppid < 0 || p->ppid > pid_max)) {
721
+ error("Pid %d (command '%s') states invalid parent pid %d. Using 0.", pid, p->comm, p->ppid);
722
+ p->ppid = 0;
723
+ }
724
+
725
+ /*
726
+ if(unlikely(debug_enabled && all_pids_count && p->ppid && all_pids[p->ppid] && !all_pids[p->ppid]->read))
727
+ 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);
728
+ */
729
+
730
+ // mark it as updated
731
+ p->updated = 1;
732
+ p->keep = 0;
733
+ p->keeploops = 0;
734
+
735
+ return 1;
736
+}
737
+
738
+/**
739
+ * Fill link list of parents with children PIDs
740
+ */
741
+static inline void link_all_processes_to_their_parents(void) {
742
+ struct pid_stat *p, *pp;
743
+
744
+ // link all children to their parents
745
+ // and update children count on parents
746
+ for(p = root_of_pids; p ; p = p->next) {
747
+ // for each process found
748
+
749
+ p->sortlist = 0;
750
+ p->parent = NULL;
751
+
752
+ if(unlikely(!p->ppid)) {
753
+ p->parent = NULL;
754
+ continue;
755
+ }
756
+
757
+ pp = all_pids[p->ppid];
758
+ if(likely(pp)) {
759
+ p->parent = pp;
760
+ pp->children_count++;
761
+
762
+ if(unlikely(debug_enabled || (p->target && p->target->debug_enabled)))
763
+ debug_log_int("child %d (%s, %s) on target '%s' has parent %d (%s, %s).", p->pid, p->comm, p->updated?"running":"exited", (p->target)?p->target->name:"UNSET", pp->pid, pp->comm, pp->updated?"running":"exited");
764
+ }
765
+ else {
766
+ p->parent = NULL;
767
+ error("pid %d %s states parent %d, but the later does not exist.", p->pid, p->comm, p->ppid);
768
+ }
769
+ }
770
+}
771
+
772
+/**
773
+ * Aggregate PIDs to targets.
774
+ */
775
+static void apply_apps_groups_targets_inheritance(void) {
776
+ struct pid_stat *p = NULL;
777
+
778
+ // children that do not have a target
779
+ // inherit their target from their parent
780
+ int found = 1, loops = 0;
781
+ while(found) {
782
+ if(unlikely(debug_enabled)) loops++;
783
+ found = 0;
784
+ for(p = root_of_pids; p ; p = p->next) {
785
+ // if this process does not have a target
786
+ // and it has a parent
787
+ // and its parent has a target
788
+ // then, set the parent's target to this process
789
+ if(unlikely(!p->target && p->parent && p->parent->target)) {
790
+ p->target = p->parent->target;
791
+ found++;
792
+
793
+ if(debug_enabled || (p->target && p->target->debug_enabled))
794
+ debug_log_int("TARGET INHERITANCE: %s is inherited by %d (%s) from its parent %d (%s).", p->target->name, p->pid, p->comm, p->parent->pid, p->parent->comm);
795
+ }
796
+ }
797
+ }
798
+
799
+ // find all the procs with 0 childs and merge them to their parents
800
+ // repeat, until nothing more can be done.
801
+ int sortlist = 1;
802
+ found = 1;
803
+ while(found) {
804
+ if(unlikely(debug_enabled)) loops++;
805
+ found = 0;
806
+
807
+ for(p = root_of_pids; p ; p = p->next) {
808
+ if(unlikely(!p->sortlist && !p->children_count))
809
+ p->sortlist = sortlist++;
810
+
811
+ if(unlikely(
812
+ !p->children_count // if this process does not have any children
813
+ && !p->merged // and is not already merged
814
+ && p->parent // and has a parent
815
+ && p->parent->children_count // and its parent has children
816
+ // and the target of this process and its parent is the same,
817
+ // or the parent does not have a target
818
+ && (p->target == p->parent->target || !p->parent->target)
819
+ && p->ppid != INIT_PID // and its parent is not init
820
+ )) {
821
+ // mark it as merged
822
+ p->parent->children_count--;
823
+ p->merged = 1;
824
+
825
+ // the parent inherits the child's target, if it does not have a target itself
826
+ if(unlikely(p->target && !p->parent->target)) {
827
+ p->parent->target = p->target;
828
+
829
+ if(debug_enabled || (p->target && p->target->debug_enabled))
830
+ debug_log_int("TARGET INHERITANCE: %s is inherited by %d (%s) from its child %d (%s).", p->target->name, p->parent->pid, p->parent->comm, p->pid, p->comm);
831
+ }
832
+
833
+ found++;
834
+ }
835
+ }
836
+
837
+ debug_log("TARGET INHERITANCE: merged %d processes", found);
838
+ }
839
+
840
+ // init goes always to default target
841
+ if(all_pids[INIT_PID])
842
+ all_pids[INIT_PID]->target = apps_groups_default_target;
843
+
844
+ // pid 0 goes always to default target
845
+ if(all_pids[0])
846
+ all_pids[0]->target = apps_groups_default_target;
847
+
848
+ // give a default target on all top level processes
849
+ if(unlikely(debug_enabled)) loops++;
850
+ for(p = root_of_pids; p ; p = p->next) {
851
+ // if the process is not merged itself
852
+ // then is is a top level process
853
+ if(unlikely(!p->merged && !p->target))
854
+ p->target = apps_groups_default_target;
855
+
856
+ // make sure all processes have a sortlist
857
+ if(unlikely(!p->sortlist))
858
+ p->sortlist = sortlist++;
859
+ }
860
+
861
+ if(all_pids[1])
862
+ all_pids[1]->sortlist = sortlist++;
863
+
864
+ // give a target to all merged child processes
865
+ found = 1;
866
+ while(found) {
867
+ if(unlikely(debug_enabled)) loops++;
868
+ found = 0;
869
+ for(p = root_of_pids; p ; p = p->next) {
870
+ if(unlikely(!p->target && p->merged && p->parent && p->parent->target)) {
871
+ p->target = p->parent->target;
872
+ found++;
873
+
874
+ if(debug_enabled || (p->target && p->target->debug_enabled))
875
+ debug_log_int("TARGET INHERITANCE: %s is inherited by %d (%s) from its parent %d (%s) at phase 2.", p->target->name, p->pid, p->comm, p->parent->pid, p->parent->comm);
876
+ }
877
+ }
878
+ }
879
+
880
+ debug_log("apply_apps_groups_targets_inheritance() made %d loops on the process tree", loops);
881
+}
882
+
883
+/**
884
+ * Update target timestamp.
885
+ *
886
+ * @param root the targets that will be updated.
887
+ */
888
+static inline void post_aggregate_targets(struct target *root) {
889
+ struct target *w;
890
+ for (w = root; w ; w = w->next) {
891
+ if(w->collected_starttime) {
892
+ if (!w->starttime || w->collected_starttime < w->starttime) {
893
+ w->starttime = w->collected_starttime;
894
+ }
895
+ } else {
896
+ w->starttime = 0;
897
+ }
898
+ }
899
+}
900
+
901
+/**
902
+ * Remove PID from the link list.
903
+ *
904
+ * @param pid the PID that will be removed.
905
+ */
906
+static inline void del_pid_entry(pid_t pid) {
907
+ struct pid_stat *p = all_pids[pid];
908
+
909
+ if(unlikely(!p)) {
910
+ error("attempted to free pid %d that is not allocated.", pid);
911
+ return;
912
+ }
913
+
914
+ debug_log("process %d %s exited, deleting it.", pid, p->comm);
915
+
916
+ if(root_of_pids == p)
917
+ root_of_pids = p->next;
918
+
919
+ if(p->next) p->next->prev = p->prev;
920
+ if(p->prev) p->prev->next = p->next;
921
+
922
+ freez(p->stat_filename);
923
+ freez(p->status_filename);
924
+ freez(p->io_filename);
925
+ freez(p->cmdline_filename);
926
+ freez(p->cmdline);
927
+ freez(p);
928
+
929
+ all_pids[pid] = NULL;
930
+ all_pids_count--;
931
+}
932
+
933
+/**
934
+ * Remove PIDs when they are not running more.
935
+ *
936
+ * @param out is the structure where PIDs are stored.
937
+ */
938
+void cleanup_exited_pids(ebpf_process_stat_t **out) {
939
+ struct pid_stat *p = NULL;
940
+
941
+ for(p = root_of_pids; p ;) {
942
+ if(!p->updated && (!p->keep || p->keeploops > 0)) {
943
+ if(unlikely(debug_enabled && (p->keep || p->keeploops)))
944
+ debug_log(" > CLEANUP cannot keep exited process %d (%s) anymore - removing it.", p->pid, p->comm);
945
+
946
+ pid_t r = p->pid;
947
+ p = p->next;
948
+ del_pid_entry(r);
949
+
950
+ ebpf_process_stat_t *w = out[r];
951
+ if (w) {
952
+ freez(w);
953
+ out[r] = NULL;
954
+ }
955
+ }
956
+ else {
957
+ if(unlikely(p->keep)) p->keeploops++;
958
+ p->keep = 0;
959
+ p = p->next;
960
+ }
961
+ }
962
+}
963
+
964
+/**
965
+ * Read proc filesystem for the first time.
966
+ *
967
+ * @return It returns 0 on success and -1 otherwise.
968
+ */
969
+static inline void read_proc_filesystem()
970
+{
971
+ char dirname[FILENAME_MAX + 1];
972
+
973
+ snprintfz(dirname, FILENAME_MAX, "%s/proc", netdata_configured_host_prefix);
974
+ DIR *dir = opendir(dirname);
975
+ if(!dir) return;
976
+
977
+ struct dirent *de = NULL;
978
+
979
+ while((de = readdir(dir))) {
980
+ char *endptr = de->d_name;
981
+
982
+ if(unlikely(de->d_type != DT_DIR || de->d_name[0] < '0' || de->d_name[0] > '9'))
983
+ continue;
984
+
985
+ pid_t pid = (pid_t) strtoul(de->d_name, &endptr, 10);
986
+
987
+ // make sure we read a valid number
988
+ if(unlikely(endptr == de->d_name || *endptr != '\0'))
989
+ continue;
990
+
991
+ collect_data_for_pid(pid, NULL);
992
+ }
993
+ closedir(dir);
994
+}
995
+
996
+/**
997
+ * Aggregated PID on target
998
+ *
999
+ * @param w the target output
1000
+ * @param p the pid with information to update
1001
+ * @param o never used
1002
+ */
1003
+static inline void aggregate_pid_on_target(struct target *w, struct pid_stat *p, struct target *o)
1004
+{
1005
+ (void)o;
1006
+
1007
+ if(unlikely(!p->updated)) {
1008
+ // the process is not running
1009
+ return;
1010
+ }
1011
+
1012
+ if(unlikely(!w)) {
1013
+ error("pid %d %s was left without a target!", p->pid, p->comm);
1014
+ return;
1015
+ }
1016
+
1017
+ /*
1018
+ w->cutime += p->cutime;
1019
+ w->cstime += p->cstime;
1020
+ w->cgtime += p->cgtime;
1021
+ w->cminflt += p->cminflt;
1022
+ w->cmajflt += p->cmajflt;
1023
+
1024
+ w->utime += p->utime;
1025
+ w->stime += p->stime;
1026
+ w->gtime += p->gtime;
1027
+ w->minflt += p->minflt;
1028
+ w->majflt += p->majflt;
1029
+
1030
+ // w->rss += p->rss;
1031
+
1032
+ w->status_vmsize += p->status_vmsize;
1033
+ w->status_vmrss += p->status_vmrss;
1034
+ w->status_vmshared += p->status_vmshared;
1035
+ w->status_rssfile += p->status_rssfile;
1036
+ w->status_rssshmem += p->status_rssshmem;
1037
+ w->status_vmswap += p->status_vmswap;
1038
+
1039
+ w->io_logical_bytes_read += p->io_logical_bytes_read;
1040
+ w->io_logical_bytes_written += p->io_logical_bytes_written;
1041
+ // w->io_read_calls += p->io_read_calls;
1042
+ // w->io_write_calls += p->io_write_calls;
1043
+ w->io_storage_bytes_read += p->io_storage_bytes_read;
1044
+ w->io_storage_bytes_written += p->io_storage_bytes_written;
1045
+ // w->io_cancelled_write_bytes += p->io_cancelled_write_bytes;
1046
+ */
1047
+
1048
+ w->processes++;
1049
+ struct pid_on_target *pid_on_target = mallocz(sizeof(struct pid_on_target));
1050
+ pid_on_target->pid = p->pid;
1051
+ pid_on_target->next = w->root_pid;
1052
+ w->root_pid = pid_on_target;
1053
+ /*
1054
+ w->num_threads += p->num_threads;
1055
+
1056
+ if(!w->collected_starttime || p->collected_starttime < w->collected_starttime) w->collected_starttime = p->collected_starttime;
1057
+ if(!w->uptime_min || p->uptime < w->uptime_min) w->uptime_min = p->uptime;
1058
+ w->uptime_sum += p->uptime;
1059
+ if(!w->uptime_max || w->uptime_max < p->uptime) w->uptime_max = p->uptime;
1060
+
1061
+ if(unlikely(debug_enabled || w->debug_enabled)) {
1062
+ //debug_log_int("aggregating '%s' pid %d on target '%s' utime=" KERNEL_UINT_FORMAT ", stime=" KERNEL_UINT_FORMAT ", gtime=" KERNEL_UINT_FORMAT ", cutime=" KERNEL_UINT_FORMAT ", cstime=" KERNEL_UINT_FORMAT ", cgtime=" KERNEL_UINT_FORMAT ", minflt=" KERNEL_UINT_FORMAT ", majflt=" KERNEL_UINT_FORMAT ", cminflt=" KERNEL_UINT_FORMAT ", cmajflt=" KERNEL_UINT_FORMAT "", p->comm, p->pid, w->name, p->utime, p->stime, p->gtime, p->cutime, p->cstime, p->cgtime, p->minflt, p->majflt, p->cminflt, p->cmajflt);
1063
+
1064
+ struct pid_on_target *pid_on_target = mallocz(sizeof(struct pid_on_target));
1065
+ pid_on_target->pid = p->pid;
1066
+ pid_on_target->next = w->root_pid;
1067
+ w->root_pid = pid_on_target;
1068
+ }
1069
+ */
1070
+}
1071
+
1072
+/**
1073
+ * Collect data for all process
1074
+ *
1075
+ * Read data from hash table and store it in appropriate vectors.
1076
+ * It also creates the link between targets and PIDs.
1077
+ *
1078
+ * @param out the output vector where we store data read from hash table.
1079
+ * @param index the vector to store the indexes read.
1080
+ * @param bpf_map_lookup_elem A pointer to the function that reads the data.
1081
+ * @param tbl_pid_stats_fd The mapped file descriptor for the hash table.
1082
+ */
1083
+#ifndef STATIC
1084
+void collect_data_for_all_processes(ebpf_process_stat_t **out,
1085
+ pid_t *index,
1086
+ int (*bpf_map_lookup_elem)(int, const void *, void *),
1087
+ int tbl_pid_stats_fd)
1088
+#else
1089
+void collect_data_for_all_processes(ebpf_process_stat_t **out,
1090
+ pid_t *index,
1091
+ int tbl_pid_stats_fd)
1092
+#endif
1093
+{
1094
+ struct pid_stat *pids = root_of_pids; // global list of all processes running
1095
+ while (pids) {
1096
+ if (pids->updated_twice) {
1097
+ pids->read = 0; // mark it as not read, so that collect_data_for_pid() will read it
1098
+ pids->updated = 0;
1099
+ pids->merged = 0;
1100
+ pids->children_count = 0;
1101
+ pids->parent = NULL;
1102
+ } else {
1103
+ if (pids->updated)
1104
+ pids->updated_twice = 1;
1105
+ }
1106
+
1107
+ pids = pids->next;
1108
+ }
1109
+
1110
+ read_proc_filesystem();
1111
+
1112
+ int counter = 0;
1113
+ uint32_t key;
1114
+ pids = root_of_pids; // global list of all processes running
1115
+ //while (bpf_map_get_next_key(tbl_pid_stats_fd, &key, &next_key) == 0) {
1116
+ while (pids) {
1117
+ key = pids->pid;
1118
+ ebpf_process_stat_t *w = out[key];
1119
+ if (!w) {
1120
+ w = mallocz(sizeof(ebpf_process_stat_t));
1121
+ out[key] = w;
1122
+ }
1123
+
1124
+ if (bpf_map_lookup_elem(tbl_pid_stats_fd, &key, w)) {
1125
+ pids = pids->next;
1126
+ continue;
1127
+ }
1128
+
1129
+ index[counter] = key;
1130
+ counter++;
1131
+
1132
+ pids = pids->next;
1133
+ }
1134
+
1135
+ link_all_processes_to_their_parents();
1136
+
1137
+ apply_apps_groups_targets_inheritance();
1138
+
1139
+ /* These lines are not necessary for ebpf plugin
1140
+ zero_all_targets(users_root_target);
1141
+ zero_all_targets(groups_root_target);
1142
+ */
1143
+
1144
+ apps_groups_targets_count = zero_all_targets(apps_groups_root_target);
1145
+
1146
+ // this has to be done, before the cleanup
1147
+ struct pid_stat *p = NULL;
1148
+ // struct target *w = NULL, *o = NULL;
1149
+
1150
+ // // concentrate everything on the targets
1151
+ for(p = root_of_pids; p ; p = p->next) {
1152
+
1153
+ // --------------------------------------------------------------------
1154
+ // apps_groups target
1155
+
1156
+ aggregate_pid_on_target(p->target, p, NULL);
1157
+
1158
+
1159
+ // // --------------------------------------------------------------------
1160
+ // // user target
1161
+
1162
+ // o = p->user_target;
1163
+ // if(likely(p->user_target && p->user_target->uid == p->uid))
1164
+ // w = p->user_target;
1165
+ // else {
1166
+ // if(unlikely(debug_enabled && p->user_target))
1167
+ // debug_log("pid %d (%s) switched user from %u (%s) to %u.", p->pid, p->comm, p->user_target->uid, p->user_target->name, p->uid);
1168
+
1169
+ // w = p->user_target = get_users_target(p->uid);
1170
+ // }
1171
+
1172
+ // aggregate_pid_on_target(w, p, o);
1173
+
1174
+
1175
+ // // --------------------------------------------------------------------
1176
+ // // user group target
1177
+
1178
+ // o = p->group_target;
1179
+ // if(likely(p->group_target && p->group_target->gid == p->gid))
1180
+ // w = p->group_target;
1181
+ // else {
1182
+ // if(unlikely(debug_enabled && p->group_target))
1183
+ // debug_log("pid %d (%s) switched group from %u (%s) to %u.", p->pid, p->comm, p->group_target->gid, p->group_target->name, p->gid);
1184
+
1185
+ // w = p->group_target = get_groups_target(p->gid);
1186
+ // }
1187
+
1188
+ // aggregate_pid_on_target(w, p, o);
1189
+
1190
+
1191
+ // // --------------------------------------------------------------------
1192
+ // // aggregate all file descriptors
1193
+
1194
+ // if(enable_file_charts)
1195
+ // aggregate_pid_fds_on_targets(p);
1196
+ }
1197
+
1198
+ post_aggregate_targets(apps_groups_root_target);
1199
+ /* These lines are not necessary for ebpf plugin
1200
+ post_aggregate_targets(users_root_target);
1201
+ post_aggregate_targets(groups_root_target);
1202
+ */
1203
+
1204
+ //cleanup_exited_pids(out);
1205
+}