master
c 485 lines 14.8 KB
Raw
1 // SPDX-License-Identifier: GPL-3.0-or-later
2
3 #include "apps_plugin.h"
4
5 pid_t INIT_PID = OS_INIT_PID;
6
7 static STRING *get_clean_name(STRING *name) {
8 char *buf = strdupz(string2str(name));
9 netdata_fix_chart_name(buf);
10
11 for (char *d = buf; *d ; d++)
12 if (*d == '.') *d = '_';
13
14 STRING *clean = string_strdupz(buf);
15 freez(buf);
16 return clean;
17 }
18
19 static inline STRING *get_numeric_string(uint64_t n) {
20 char buf[UINT64_MAX_LENGTH];
21 print_uint64(buf, n);
22 return string_strdupz(buf);
23 }
24
25 struct target *find_target_by_name(struct target *base, const char *name) {
26 struct target *t;
27 for(t = base; t ; t = t->next) {
28 if (string_strcmp(t->name, name) == 0)
29 return t;
30 }
31
32 return NULL;
33 }
34
35 // --------------------------------------------------------------------------------------------------------------------
36 // Process managers and aggregators
37
38 struct comm_list {
39 APPS_MATCH match;
40 };
41
42 struct managed_list {
43 size_t used;
44 size_t size;
45 struct comm_list *array;
46 };
47
48 static struct {
49 struct managed_list managers;
50 struct managed_list aggregators;
51 struct managed_list interpreters;
52 } tree = {
53 .managers = {
54 .array = NULL,
55 .size = 0,
56 .used = 0,
57 },
58 .aggregators = {
59 .array = NULL,
60 .size = 0,
61 .used = 0,
62 }
63 };
64
65 static void managed_list_clear(struct managed_list *list) {
66 for(size_t c = 0; c < list->used ; c++)
67 pid_match_cleanup(&list->array[c].match);
68
69 freez(list->array);
70 list->array = NULL;
71 list->used = 0;
72 list->size = 0;
73 }
74
75 static void managed_list_add(struct managed_list *list, const char *s) {
76 if(list->used >= list->size) {
77 if(!list->size)
78 list->size = 16;
79 else
80 list->size *= 2;
81 list->array = reallocz(list->array, sizeof(*list->array) * list->size);
82 }
83
84 list->array[list->used++].match = pid_match_create(s);
85 }
86
87 static STRING *KernelAggregator = NULL;
88
89 void apps_managers_and_aggregators_init(void) {
90 KernelAggregator = string_strdupz("kernel");
91
92 managed_list_clear(&tree.managers);
93 #if defined(OS_LINUX)
94 managed_list_add(&tree.managers, "init"); // linux systems
95 managed_list_add(&tree.managers, "systemd"); // lxc containers and host systems (this also catches "systemd --user")
96 managed_list_add(&tree.managers, "containerd-shim-runc-v2"); // docker containers
97 managed_list_add(&tree.managers, "docker-init"); // docker containers
98 managed_list_add(&tree.managers, "tini"); // docker containers (https://github.com/krallin/tini)
99 managed_list_add(&tree.managers, "dumb-init"); // some docker containers use this
100 managed_list_add(&tree.managers, "openrc-run.sh"); // openrc
101 managed_list_add(&tree.managers, "crond"); // linux crond
102 managed_list_add(&tree.managers, "gnome-shell"); // gnome user applications
103 managed_list_add(&tree.managers, "plasmashell"); // kde user applications
104 managed_list_add(&tree.managers, "xfwm4"); // xfce4 user applications
105 managed_list_add(&tree.managers, "*python3*bin/yugabyted*"); // https://docs.yugabyte.com/preview/tutorials/quick-start/docker/#create-a-local-cluster
106 #elif defined(OS_WINDOWS)
107 managed_list_add(&tree.managers, "wininit");
108 managed_list_add(&tree.managers, "services");
109 managed_list_add(&tree.managers, "explorer");
110 managed_list_add(&tree.managers, "System");
111 #elif defined(OS_FREEBSD)
112 managed_list_add(&tree.managers, "init");
113 #elif defined(OS_MACOS)
114 managed_list_add(&tree.managers, "launchd");
115 #endif
116
117 #if defined(OS_WINDOWS)
118 managed_list_add(&tree.managers, "netdata");
119 #else
120 managed_list_add(&tree.managers, "spawn-plugins");
121 #endif
122
123 managed_list_clear(&tree.aggregators);
124 #if defined(OS_LINUX)
125 managed_list_add(&tree.aggregators, "kthread");
126 #elif defined(OS_WINDOWS)
127 #elif defined(OS_FREEBSD)
128 managed_list_add(&tree.aggregators, "kernel");
129 #elif defined(OS_MACOS)
130 #endif
131
132 managed_list_clear(&tree.interpreters);
133 managed_list_add(&tree.interpreters, "python");
134 managed_list_add(&tree.interpreters, "python2");
135 managed_list_add(&tree.interpreters, "python3");
136 managed_list_add(&tree.interpreters, "sh");
137 managed_list_add(&tree.interpreters, "bash");
138 managed_list_add(&tree.interpreters, "node");
139 managed_list_add(&tree.interpreters, "perl");
140 }
141
142 bool is_process_a_manager(struct pid_stat *p) {
143 for(size_t c = 0; c < tree.managers.used ; c++) {
144 if(pid_match_check(p, &tree.managers.array[c].match))
145 return true;
146 }
147
148 return false;
149 }
150
151 bool is_process_an_aggregator(struct pid_stat *p) {
152 for(size_t c = 0; c < tree.aggregators.used ; c++) {
153 if(pid_match_check(p, &tree.aggregators.array[c].match))
154 return true;
155 }
156
157 return false;
158 }
159
160 bool is_process_an_interpreter(struct pid_stat *p) {
161 for(size_t c = 0; c < tree.interpreters.used ; c++) {
162 if(pid_match_check(p, &tree.interpreters.array[c].match))
163 return true;
164 }
165
166 return false;
167 }
168
169 // --------------------------------------------------------------------------------------------------------------------
170 // Tree
171
172 struct target *get_tree_target(struct pid_stat *p) {
173 // // skip fast all the children that are more than 3 levels down
174 // while(p->parent && p->parent->pid != INIT_PID && p->parent->parent && p->parent->parent->parent)
175 // p = p->parent;
176
177 // keep the children of INIT_PID, and process orchestrators
178 while(p->parent && p->parent->pid != INIT_PID && p->parent->pid != 0 && !p->parent->is_manager)
179 p = p->parent;
180
181 // merge all processes into process aggregators
182 STRING *search_for = NULL;
183 if((p->ppid == 0 && p->pid != INIT_PID) || (p->parent && p->parent->is_aggregator)) {
184 search_for = string_dup(KernelAggregator);
185 }
186 else {
187 #if (PROCESSES_HAVE_COMM_AND_NAME == 1)
188 search_for = string_dup(p->name ? p->name : p->comm);
189 #else
190 search_for = string_dup(p->comm);
191 #endif
192 }
193
194 // find an existing target with the required name
195 struct target *w;
196 for(w = apps_groups_root_target; w ; w = w->next) {
197 if (w->name == search_for) {
198 string_freez(search_for);
199 return w;
200 }
201 }
202
203 w = callocz(sizeof(struct target), 1);
204 w->type = TARGET_TYPE_TREE;
205 w->match.starts_with = w->match.ends_with = false;
206 w->match.compare = string_dup(search_for);
207 w->match.pattern = NULL;
208 w->id = search_for;
209 w->name = string_dup(search_for);
210 w->clean_name = get_clean_name(w->name);
211
212 w->next = apps_groups_root_target;
213 apps_groups_root_target = w;
214
215 return w;
216 }
217
218 // --------------------------------------------------------------------------------------------------------------------
219 // Users
220
221 #if (PROCESSES_HAVE_UID == 1)
222 struct target *users_root_target = NULL;
223
224 struct target *get_uid_target(uid_t uid) {
225 struct target *w;
226 for(w = users_root_target ; w ; w = w->next)
227 if(w->uid == uid) return w;
228
229 w = callocz(sizeof(struct target), 1);
230 w->type = TARGET_TYPE_UID;
231 w->uid = uid;
232 w->id = get_numeric_string(uid);
233
234 CACHED_USERNAME cu = cached_username_get_by_uid(uid);
235 w->name = string_dup(cu.username);
236 w->clean_name = get_clean_name(w->name);
237 cached_username_release(cu);
238
239 w->next = users_root_target;
240 users_root_target = w;
241
242 debug_log("added uid %u ('%s') target", w->uid, string2str(w->name));
243
244 return w;
245 }
246 #endif
247
248 // --------------------------------------------------------------------------------------------------------------------
249 // Groups
250
251 #if (PROCESSES_HAVE_GID == 1)
252 struct target *groups_root_target = NULL;
253
254 struct target *get_gid_target(gid_t gid) {
255 struct target *w;
256 for(w = groups_root_target ; w ; w = w->next)
257 if(w->gid == gid) return w;
258
259 w = callocz(sizeof(struct target), 1);
260 w->type = TARGET_TYPE_GID;
261 w->gid = gid;
262 w->id = get_numeric_string(gid);
263
264 CACHED_GROUPNAME cg = cached_groupname_get_by_gid(gid);
265 w->name = string_dup(cg.groupname);
266 w->clean_name = get_clean_name(w->name);
267 cached_groupname_release(cg);
268
269 w->next = groups_root_target;
270 groups_root_target = w;
271
272 debug_log("added gid %u ('%s') target", w->gid, w->name);
273
274 return w;
275 }
276 #endif
277
278 // --------------------------------------------------------------------------------------------------------------------
279 // SID
280
281 #if (PROCESSES_HAVE_SID == 1)
282 struct target *sids_root_target = NULL;
283
284 struct target *get_sid_target(STRING *sid_name) {
285 struct target *w;
286 for(w = sids_root_target ; w ; w = w->next)
287 if(w->sid_name == sid_name) return w;
288
289 w = callocz(sizeof(struct target), 1);
290 w->type = TARGET_TYPE_SID;
291 w->sid_name = string_dup(sid_name);
292 w->id = string_dup(sid_name);
293 w->name = string_dup(sid_name);
294 w->clean_name = get_clean_name(w->name);
295
296 w->next = sids_root_target;
297 sids_root_target = w;
298
299 debug_log("added uid %s ('%s') target", string2str(w->sid_name), string2str(w->name));
300
301 return w;
302 }
303 #endif
304
305 // --------------------------------------------------------------------------------------------------------------------
306 // Service
307
308 #if (PROCESSES_HAVE_SERVICE == 1)
309 struct target *services_root_target = NULL;
310
311 struct target *get_service_target(STRING *service_name) {
312 struct target *w;
313 for(w = services_root_target ; w ; w = w->next)
314 if(w->service_name == service_name) return w;
315
316 w = callocz(sizeof(struct target), 1);
317 w->type = TARGET_TYPE_SERVICE;
318 w->service_name = string_dup(service_name);
319 w->id = string_dup(service_name);
320 w->name = string_dup(service_name);
321 w->clean_name = get_clean_name(w->name);
322
323 w->next = services_root_target;
324 services_root_target = w;
325
326 debug_log("added service '%s' target", string2str(w->service_name));
327
328 return w;
329 }
330 #endif
331
332 // --------------------------------------------------------------------------------------------------------------------
333 // apps_groups.conf
334
335 struct target *apps_groups_root_target = NULL;
336
337 // find or create a new target
338 // there are targets that are just aggregated to other target (the second argument)
339 static struct target *get_apps_groups_target(const char *comm, struct target *target, const char *name) {
340 APPS_MATCH match = pid_match_create(comm);
341 STRING *name_lookup = string_strdupz(name);
342
343 // find if it already exists
344 struct target *w, *last = apps_groups_root_target;
345 for(w = apps_groups_root_target ; w ; w = w->next) {
346 if(w->id == match.compare) {
347 pid_match_cleanup(&match);
348 string_freez(name_lookup);
349 return w;
350 }
351
352 last = w;
353 }
354
355 // find an existing target
356 if(unlikely(!target)) {
357 for(target = apps_groups_root_target ; target ; target = target->next) {
358 if(!target->target && name_lookup == target->name)
359 break;
360 }
361 }
362
363 if(target && target->target)
364 fatal("Internal Error: request to link process '%s' to target '%s' which is linked to target '%s'",
365 comm, string2str(target->id), string2str(target->target->id));
366
367 w = callocz(sizeof(struct target), 1);
368 w->type = TARGET_TYPE_APP_GROUP;
369 w->match = match;
370 w->id = string_dup(w->match.compare);
371
372 if(unlikely(!target))
373 w->name = string_dup(name_lookup); // copy the name
374 else
375 w->name = string_dup(w->id); // copy the id
376
377 // dots are used to distinguish chart type and id in streaming, so we should replace them
378 w->clean_name = get_clean_name(w->name);
379
380 if(w->match.starts_with && w->match.ends_with)
381 proc_pid_cmdline_is_needed = true;
382
383 w->target = target;
384
385 // append it, to maintain the order in apps_groups.conf
386 if(last) last->next = w;
387 else apps_groups_root_target = w;
388
389 debug_log("ADDING TARGET ID '%s', process name '%s' (%s), aggregated on target '%s'"
390 , string2str(w->id)
391 , string2str(w->match.compare)
392 , (w->match.starts_with && w->match.ends_with) ? "substring" : ((w->match.starts_with) ? "prefix" : ((w->match.ends_with) ? "suffix" : "exact"))
393 , w->target?w->target->name:w->name
394 );
395
396 string_freez(name_lookup);
397
398 return w;
399 }
400
401 // read the apps_groups.conf file
402 int read_apps_groups_conf(const char *path, const char *file) {
403 char filename[FILENAME_MAX + 1];
404
405 snprintfz(filename, FILENAME_MAX, "%s/apps_%s.conf", path, file);
406
407 debug_log("process groups file: '%s'", filename);
408
409 // ----------------------------------------
410
411 procfile *ff = procfile_open(filename, " :\t", PROCFILE_FLAG_DEFAULT);
412 if(!ff) return 1;
413
414 procfile_set_quotes(ff, "'\"");
415
416 ff = procfile_readall(ff);
417 if(!ff)
418 return 1;
419
420 size_t line, lines = procfile_lines(ff);
421
422 for(line = 0; line < lines ;line++) {
423 size_t word, words = procfile_linewords(ff, line);
424 if(!words) continue;
425
426 char *name = procfile_lineword(ff, line, 0);
427 if(!name || !*name || *name == '#') continue;
428
429 if(strcmp(name, "managers") == 0) {
430 if(words == 2 && strcmp(procfile_lineword(ff, line, 1), "clear") == 0)
431 managed_list_clear(&tree.managers);
432
433 for(word = 1; word < words ;word++) {
434 char *s = procfile_lineword(ff, line, word);
435 if (!s || !*s) continue;
436 if (*s == '#') break;
437
438 managed_list_add(&tree.managers, s);
439 }
440
441 // done with managers, proceed to next line
442 continue;
443 }
444
445 if(strcmp(name, "interpreters") == 0) {
446 if(words == 2 && strcmp(procfile_lineword(ff, line, 1), "clear") == 0)
447 managed_list_clear(&tree.interpreters);
448
449 for(word = 1; word < words ;word++) {
450 char *s = procfile_lineword(ff, line, word);
451 if (!s || !*s) continue;
452 if (*s == '#') break;
453
454 managed_list_add(&tree.interpreters, s);
455 }
456
457 // done with managers, proceed to the next line
458 continue;
459 }
460
461 // find a possibly existing target
462 struct target *w = NULL;
463
464 // loop through all words, skipping the first one (the name)
465 for(word = 1; word < words ;word++) {
466 char *s = procfile_lineword(ff, line, word);
467 if(!s || !*s) continue;
468 if(*s == '#') break;
469
470 // add this target
471 struct target *n = get_apps_groups_target(s, w, name);
472 if(!n) {
473 netdata_log_error("Cannot create target '%s' (line %zu, word %zu)", s, line, word);
474 continue;
475 }
476
477 // just some optimization
478 // to avoid searching for a target for each process
479 if(!w) w = n->target?n->target:n;
480 }
481 }
482
483 procfile_close(ff);
484 return 0;
485 }