| 1 | // SPDX-License-Identifier: GPL-3.0-or-later |
| 2 | |
| 3 | #include "apps_plugin.h" |
| 4 | #include "libnetdata/parsers/duration.h" |
| 5 | |
| 6 | #define APPS_PLUGIN_FUNCTIONS() do { \ |
| 7 | fprintf(stdout, PLUGINSD_KEYWORD_FUNCTION " \"processes\" %d \"%s\" \"top\" "HTTP_ACCESS_FORMAT" %d\n", \ |
| 8 | PLUGINS_FUNCTIONS_TIMEOUT_DEFAULT, APPS_PLUGIN_PROCESSES_FUNCTION_DESCRIPTION, \ |
| 9 | (HTTP_ACCESS_FORMAT_CAST)(HTTP_ACCESS_SIGNED_ID|HTTP_ACCESS_SAME_SPACE|HTTP_ACCESS_SENSITIVE_DATA), \ |
| 10 | RRDFUNCTIONS_PRIORITY_DEFAULT / 10); \ |
| 11 | } while(0) |
| 12 | |
| 13 | #define APPS_PLUGIN_GLOBAL_FUNCTIONS() do { \ |
| 14 | fprintf(stdout, PLUGINSD_KEYWORD_FUNCTION " GLOBAL \"processes\" %d \"%s\" \"top\" "HTTP_ACCESS_FORMAT" %d\n", \ |
| 15 | PLUGINS_FUNCTIONS_TIMEOUT_DEFAULT, APPS_PLUGIN_PROCESSES_FUNCTION_DESCRIPTION, \ |
| 16 | (HTTP_ACCESS_FORMAT_CAST)(HTTP_ACCESS_SIGNED_ID|HTTP_ACCESS_SAME_SPACE|HTTP_ACCESS_SENSITIVE_DATA), \ |
| 17 | RRDFUNCTIONS_PRIORITY_DEFAULT / 10); \ |
| 18 | } while(0) |
| 19 | |
| 20 | // ---------------------------------------------------------------------------- |
| 21 | // options |
| 22 | |
| 23 | bool debug_enabled = false; |
| 24 | |
| 25 | bool enable_detailed_uptime_charts = false; |
| 26 | bool enable_users_charts = true; |
| 27 | bool enable_groups_charts = true; |
| 28 | #if (PROCESSES_HAVE_SERVICE == 1) |
| 29 | bool enable_services_charts = true; |
| 30 | #endif |
| 31 | bool include_exited_childs = true; |
| 32 | bool proc_pid_cmdline_is_needed = true; // true when we need to read /proc/cmdline |
| 33 | |
| 34 | #if defined(OS_FREEBSD) || defined(OS_MACOS) |
| 35 | int enable_file_charts = CONFIG_BOOLEAN_NO; |
| 36 | #elif defined(OS_LINUX) |
| 37 | int enable_file_charts = CONFIG_BOOLEAN_AUTO; |
| 38 | #elif defined(OS_WINDOWS) |
| 39 | int enable_file_charts = CONFIG_BOOLEAN_YES; |
| 40 | #endif |
| 41 | bool obsolete_file_charts = false; |
| 42 | |
| 43 | // ---------------------------------------------------------------------------- |
| 44 | // internal counters |
| 45 | |
| 46 | size_t |
| 47 | global_iterations_counter = 1, |
| 48 | calls_counter = 0, |
| 49 | file_counter = 0, |
| 50 | filenames_allocated_counter = 0, |
| 51 | inodes_changed_counter = 0, |
| 52 | links_changed_counter = 0, |
| 53 | targets_assignment_counter = 0, |
| 54 | apps_groups_targets_count = 0; // # of apps_groups.conf targets |
| 55 | |
| 56 | #if (PROCESSES_HAVE_CPU_GUEST_TIME == 1) |
| 57 | bool enable_guest_charts = false; |
| 58 | bool show_guest_time = false; // set when guest values are collected |
| 59 | #endif |
| 60 | |
| 61 | uint32_t |
| 62 | all_files_len = 0, |
| 63 | all_files_size = 0; |
| 64 | |
| 65 | // -------------------------------------------------------------------------------------------------------------------- |
| 66 | // Normalization |
| 67 | // |
| 68 | // With normalization we lower the collected metrics by a factor to make them |
| 69 | // match the total utilization of the system. |
| 70 | // The discrepancy exists because apps.plugin needs some time to collect all |
| 71 | // the metrics. This results in utilization that exceeds the total utilization |
| 72 | // of the system. |
| 73 | // |
| 74 | // During normalization, we align the per-process utilization to the global |
| 75 | // utilization of the system. We first consume the exited children utilization |
| 76 | // and it the collected values is above the total, we proportionally scale each |
| 77 | // reported metric. |
| 78 | |
| 79 | // the total system time, as reported by /proc/stat |
| 80 | #if (ALL_PIDS_ARE_READ_INSTANTLY == 0) |
| 81 | kernel_uint_t |
| 82 | global_utime = 0, |
| 83 | global_stime = 0, |
| 84 | global_gtime = 0; |
| 85 | #endif |
| 86 | |
| 87 | // the normalization ratios, as calculated by normalize_utilization() |
| 88 | NETDATA_DOUBLE |
| 89 | utime_fix_ratio = 1.0, |
| 90 | stime_fix_ratio = 1.0, |
| 91 | gtime_fix_ratio = 1.0, |
| 92 | minflt_fix_ratio = 1.0, |
| 93 | majflt_fix_ratio = 1.0, |
| 94 | cutime_fix_ratio = 1.0, |
| 95 | cstime_fix_ratio = 1.0, |
| 96 | cgtime_fix_ratio = 1.0, |
| 97 | cminflt_fix_ratio = 1.0, |
| 98 | cmajflt_fix_ratio = 1.0; |
| 99 | |
| 100 | // -------------------------------------------------------------------------------------------------------------------- |
| 101 | |
| 102 | int update_every = 1; |
| 103 | |
| 104 | #if defined(OS_LINUX) |
| 105 | proc_state proc_state_count[PROC_STATUS_END]; |
| 106 | const char *proc_states[] = { |
| 107 | [PROC_STATUS_RUNNING] = "running", |
| 108 | [PROC_STATUS_SLEEPING] = "sleeping_interruptible", |
| 109 | [PROC_STATUS_SLEEPING_D] = "sleeping_uninterruptible", |
| 110 | [PROC_STATUS_ZOMBIE] = "zombie", |
| 111 | [PROC_STATUS_STOPPED] = "stopped", |
| 112 | }; |
| 113 | #endif |
| 114 | |
| 115 | // will be changed to getenv(NETDATA_USER_CONFIG_DIR) if it exists |
| 116 | static char *user_config_dir = CONFIG_DIR; |
| 117 | static char *stock_config_dir = LIBCONFIG_DIR; |
| 118 | |
| 119 | size_t pagesize; |
| 120 | |
| 121 | void sanitize_apps_plugin_chart_meta(char *buf) { |
| 122 | external_plugins_sanitize(buf, buf, strlen(buf) + 1); |
| 123 | } |
| 124 | |
| 125 | // ---------------------------------------------------------------------------- |
| 126 | // update chart dimensions |
| 127 | |
| 128 | // Helper function to count the number of processes in the linked list |
| 129 | int count_processes(struct pid_stat *root) { |
| 130 | int count = 0; |
| 131 | |
| 132 | for(struct pid_stat *p = root; p ; p = p->next) |
| 133 | if(p->updated) count++; |
| 134 | |
| 135 | return count; |
| 136 | } |
| 137 | |
| 138 | // Comparator function to sort by pid |
| 139 | int compare_by_pid(const void *a, const void *b) { |
| 140 | struct pid_stat *pa = *(struct pid_stat **)a; |
| 141 | struct pid_stat *pb = *(struct pid_stat **)b; |
| 142 | return ((int)pa->pid - (int)pb->pid); |
| 143 | } |
| 144 | |
| 145 | // Function to print a process and its children recursively |
| 146 | void print_process_tree(struct pid_stat *root, struct pid_stat *parent, int depth, int total_processes) { |
| 147 | // Allocate an array of pointers for processes with the given parent |
| 148 | struct pid_stat **children = (struct pid_stat **)malloc(total_processes * sizeof(struct pid_stat *)); |
| 149 | int children_count = 0; |
| 150 | |
| 151 | // Populate the array with processes that have the given parent |
| 152 | struct pid_stat *p = root; |
| 153 | while (p != NULL) { |
| 154 | if (p->updated && p->parent == parent) { |
| 155 | children[children_count++] = p; |
| 156 | } |
| 157 | p = p->next; |
| 158 | } |
| 159 | |
| 160 | // Sort the children array by pid |
| 161 | qsort(children, children_count, sizeof(struct pid_stat *), compare_by_pid); |
| 162 | |
| 163 | // Print each child and recurse |
| 164 | for (int i = 0; i < children_count; i++) { |
| 165 | // Print the current process with indentation based on depth |
| 166 | if (depth > 0) { |
| 167 | for (int j = 0; j < (depth - 1) * 4; j++) { |
| 168 | printf(" "); |
| 169 | } |
| 170 | printf(" \\_ "); |
| 171 | } |
| 172 | |
| 173 | #if (PROCESSES_HAVE_COMM_AND_NAME == 1) |
| 174 | printf("[%d] %s (name: %s) [%s]: %s\n", children[i]->pid, |
| 175 | string2str(children[i]->comm), |
| 176 | string2str(children[i]->name), |
| 177 | string2str(children[i]->target->name), |
| 178 | string2str(children[i]->cmdline)); |
| 179 | #else |
| 180 | printf("[%d] orig: '%s' new: '%s' [target: %s]: cmdline: %s\n", children[i]->pid, |
| 181 | string2str(children[i]->comm_orig), |
| 182 | string2str(children[i]->comm), |
| 183 | string2str(children[i]->target->name), |
| 184 | string2str(children[i]->cmdline)); |
| 185 | #endif |
| 186 | |
| 187 | // Recurse to print this child's children |
| 188 | print_process_tree(root, children[i], depth + 1, total_processes); |
| 189 | } |
| 190 | |
| 191 | // Free the allocated array |
| 192 | free(children); |
| 193 | } |
| 194 | |
| 195 | // Function to print the full hierarchy |
| 196 | void print_hierarchy(struct pid_stat *root) { |
| 197 | // Count the total number of processes |
| 198 | int total_processes = count_processes(root); |
| 199 | |
| 200 | // Start printing from processes with parent = NULL (i.e., root processes) |
| 201 | print_process_tree(root, NULL, 0, total_processes); |
| 202 | } |
| 203 | |
| 204 | // ---------------------------------------------------------------------------- |
| 205 | // update chart dimensions |
| 206 | |
| 207 | #if (ALL_PIDS_ARE_READ_INSTANTLY == 0) |
| 208 | static void normalize_utilization(struct target *root) { |
| 209 | struct target *w; |
| 210 | |
| 211 | // children processing introduces spikes, |
| 212 | // here we try to eliminate them by disabling children processing either |
| 213 | // for specific dimensions or entirely. |
| 214 | // of course, either way, we disable it just for a single iteration. |
| 215 | |
| 216 | kernel_uint_t max_time = os_get_system_cpus() * NSEC_PER_SEC; |
| 217 | kernel_uint_t utime = 0, cutime = 0, stime = 0, cstime = 0, gtime = 0, cgtime = 0, minflt = 0, cminflt = 0, majflt = 0, cmajflt = 0; |
| 218 | |
| 219 | if(global_utime > max_time) global_utime = max_time; |
| 220 | if(global_stime > max_time) global_stime = max_time; |
| 221 | if(global_gtime > max_time) global_gtime = max_time; |
| 222 | |
| 223 | for(w = root; w ; w = w->next) { |
| 224 | if(w->target || (!w->values[PDF_PROCESSES] && !w->exposed)) continue; |
| 225 | |
| 226 | utime += w->values[PDF_UTIME]; |
| 227 | stime += w->values[PDF_STIME]; |
| 228 | gtime += w->values[PDF_GTIME]; |
| 229 | cutime += w->values[PDF_CUTIME]; |
| 230 | cstime += w->values[PDF_CSTIME]; |
| 231 | cgtime += w->values[PDF_CGTIME]; |
| 232 | |
| 233 | minflt += w->values[PDF_MINFLT]; |
| 234 | majflt += w->values[PDF_MAJFLT]; |
| 235 | cminflt += w->values[PDF_CMINFLT]; |
| 236 | cmajflt += w->values[PDF_CMAJFLT]; |
| 237 | } |
| 238 | |
| 239 | if(global_utime || global_stime || global_gtime) { |
| 240 | if(global_utime + global_stime + global_gtime > utime + cutime + stime + cstime + gtime + cgtime) { |
| 241 | // everything we collected fits |
| 242 | utime_fix_ratio = |
| 243 | stime_fix_ratio = |
| 244 | gtime_fix_ratio = |
| 245 | cutime_fix_ratio = |
| 246 | cstime_fix_ratio = |
| 247 | cgtime_fix_ratio = 1.0; //(NETDATA_DOUBLE)(global_utime + global_stime) / (NETDATA_DOUBLE)(utime + cutime + stime + cstime); |
| 248 | } |
| 249 | else if((global_utime + global_stime > utime + stime) && (cutime || cstime)) { |
| 250 | // children resources are too high, |
| 251 | // lower only the children resources |
| 252 | utime_fix_ratio = |
| 253 | stime_fix_ratio = |
| 254 | gtime_fix_ratio = 1.0; |
| 255 | cutime_fix_ratio = |
| 256 | cstime_fix_ratio = |
| 257 | cgtime_fix_ratio = (NETDATA_DOUBLE)((global_utime + global_stime) - (utime + stime)) / (NETDATA_DOUBLE)(cutime + cstime); |
| 258 | } |
| 259 | else if(utime || stime) { |
| 260 | // even running processes are unrealistic |
| 261 | // zero the children resources |
| 262 | // lower the running processes resources |
| 263 | utime_fix_ratio = |
| 264 | stime_fix_ratio = |
| 265 | gtime_fix_ratio = (NETDATA_DOUBLE)(global_utime + global_stime) / (NETDATA_DOUBLE)(utime + stime); |
| 266 | cutime_fix_ratio = |
| 267 | cstime_fix_ratio = |
| 268 | cgtime_fix_ratio = 0.0; |
| 269 | } |
| 270 | else { |
| 271 | utime_fix_ratio = |
| 272 | stime_fix_ratio = |
| 273 | gtime_fix_ratio = |
| 274 | cutime_fix_ratio = |
| 275 | cstime_fix_ratio = |
| 276 | cgtime_fix_ratio = 0.0; |
| 277 | } |
| 278 | } |
| 279 | else { |
| 280 | utime_fix_ratio = |
| 281 | stime_fix_ratio = |
| 282 | gtime_fix_ratio = |
| 283 | cutime_fix_ratio = |
| 284 | cstime_fix_ratio = |
| 285 | cgtime_fix_ratio = 0.0; |
| 286 | } |
| 287 | |
| 288 | if(utime_fix_ratio > 1.0) utime_fix_ratio = 1.0; |
| 289 | if(cutime_fix_ratio > 1.0) cutime_fix_ratio = 1.0; |
| 290 | if(stime_fix_ratio > 1.0) stime_fix_ratio = 1.0; |
| 291 | if(cstime_fix_ratio > 1.0) cstime_fix_ratio = 1.0; |
| 292 | if(gtime_fix_ratio > 1.0) gtime_fix_ratio = 1.0; |
| 293 | if(cgtime_fix_ratio > 1.0) cgtime_fix_ratio = 1.0; |
| 294 | |
| 295 | // if(utime_fix_ratio < 0.0) utime_fix_ratio = 0.0; |
| 296 | // if(cutime_fix_ratio < 0.0) cutime_fix_ratio = 0.0; |
| 297 | // if(stime_fix_ratio < 0.0) stime_fix_ratio = 0.0; |
| 298 | // if(cstime_fix_ratio < 0.0) cstime_fix_ratio = 0.0; |
| 299 | // if(gtime_fix_ratio < 0.0) gtime_fix_ratio = 0.0; |
| 300 | // if(cgtime_fix_ratio < 0.0) cgtime_fix_ratio = 0.0; |
| 301 | |
| 302 | // TODO |
| 303 | // we use cpu time to normalize page faults |
| 304 | // the problem is that to find the proper max values |
| 305 | // for page faults we have to parse /proc/vmstat |
| 306 | // which is quite big to do it again (netdata does it already) |
| 307 | // |
| 308 | // a better solution could be to somehow have netdata |
| 309 | // do this normalization for us |
| 310 | |
| 311 | if(utime || stime || gtime) |
| 312 | majflt_fix_ratio = |
| 313 | minflt_fix_ratio = (NETDATA_DOUBLE)(utime * utime_fix_ratio + stime * stime_fix_ratio + gtime * gtime_fix_ratio) / (NETDATA_DOUBLE)(utime + stime + gtime); |
| 314 | else |
| 315 | minflt_fix_ratio = |
| 316 | majflt_fix_ratio = 1.0; |
| 317 | |
| 318 | if(cutime || cstime || cgtime) |
| 319 | cmajflt_fix_ratio = |
| 320 | cminflt_fix_ratio = (NETDATA_DOUBLE)(cutime * cutime_fix_ratio + cstime * cstime_fix_ratio + cgtime * cgtime_fix_ratio) / (NETDATA_DOUBLE)(cutime + cstime + cgtime); |
| 321 | else |
| 322 | cminflt_fix_ratio = |
| 323 | cmajflt_fix_ratio = 1.0; |
| 324 | |
| 325 | // the report |
| 326 | |
| 327 | debug_log( |
| 328 | "SYSTEM: u=" KERNEL_UINT_FORMAT " s=" KERNEL_UINT_FORMAT " g=" KERNEL_UINT_FORMAT " " |
| 329 | "COLLECTED: u=" KERNEL_UINT_FORMAT " s=" KERNEL_UINT_FORMAT " g=" KERNEL_UINT_FORMAT " cu=" KERNEL_UINT_FORMAT " cs=" KERNEL_UINT_FORMAT " cg=" KERNEL_UINT_FORMAT " " |
| 330 | "DELTA: u=" KERNEL_UINT_FORMAT " s=" KERNEL_UINT_FORMAT " g=" KERNEL_UINT_FORMAT " " |
| 331 | "FIX: u=%0.2f s=%0.2f g=%0.2f cu=%0.2f cs=%0.2f cg=%0.2f " |
| 332 | "FINALLY: u=" KERNEL_UINT_FORMAT " s=" KERNEL_UINT_FORMAT " g=" KERNEL_UINT_FORMAT " cu=" KERNEL_UINT_FORMAT " cs=" KERNEL_UINT_FORMAT " cg=" KERNEL_UINT_FORMAT " " |
| 333 | , global_utime |
| 334 | , global_stime |
| 335 | , global_gtime |
| 336 | , utime |
| 337 | , stime |
| 338 | , gtime |
| 339 | , cutime |
| 340 | , cstime |
| 341 | , cgtime |
| 342 | , utime + cutime - global_utime |
| 343 | , stime + cstime - global_stime |
| 344 | , gtime + cgtime - global_gtime |
| 345 | , utime_fix_ratio |
| 346 | , stime_fix_ratio |
| 347 | , gtime_fix_ratio |
| 348 | , cutime_fix_ratio |
| 349 | , cstime_fix_ratio |
| 350 | , cgtime_fix_ratio |
| 351 | , (kernel_uint_t)(utime * utime_fix_ratio) |
| 352 | , (kernel_uint_t)(stime * stime_fix_ratio) |
| 353 | , (kernel_uint_t)(gtime * gtime_fix_ratio) |
| 354 | , (kernel_uint_t)(cutime * cutime_fix_ratio) |
| 355 | , (kernel_uint_t)(cstime * cstime_fix_ratio) |
| 356 | , (kernel_uint_t)(cgtime * cgtime_fix_ratio) |
| 357 | ); |
| 358 | } |
| 359 | #endif |
| 360 | |
| 361 | // ---------------------------------------------------------------------------- |
| 362 | // parse command line arguments |
| 363 | |
| 364 | int check_proc_1_io() { |
| 365 | int ret = 0; |
| 366 | |
| 367 | #if defined(OS_LINUX) |
| 368 | procfile *ff = procfile_open("/proc/1/io", NULL, PROCFILE_FLAG_NO_ERROR_ON_FILE_IO); |
| 369 | if(!ff) goto cleanup; |
| 370 | |
| 371 | ff = procfile_readall(ff); |
| 372 | if(!ff) goto cleanup; |
| 373 | |
| 374 | ret = 1; |
| 375 | |
| 376 | cleanup: |
| 377 | procfile_close(ff); |
| 378 | #endif |
| 379 | |
| 380 | return ret; |
| 381 | } |
| 382 | |
| 383 | static bool profile_speed = false; |
| 384 | static bool print_tree_and_exit = false; |
| 385 | #if (PROCESSES_HAVE_SMAPS_ROLLUP == 1) |
| 386 | int pss_refresh_period = 0; // disabled by default |
| 387 | #endif |
| 388 | |
| 389 | static void parse_args(int argc, char **argv) |
| 390 | { |
| 391 | int i, freq = 0; |
| 392 | |
| 393 | for(i = 1; i < argc; i++) { |
| 394 | if(!freq) { |
| 395 | int n = (int)str2l(argv[i]); |
| 396 | if(n > 0) { |
| 397 | freq = n; |
| 398 | continue; |
| 399 | } |
| 400 | } |
| 401 | |
| 402 | if(strcmp("version", argv[i]) == 0 || strcmp("-version", argv[i]) == 0 || strcmp("--version", argv[i]) == 0 || strcmp("-v", argv[i]) == 0 || strcmp("-V", argv[i]) == 0) { |
| 403 | printf("apps.plugin %s\n", NETDATA_VERSION); |
| 404 | exit(0); |
| 405 | } |
| 406 | |
| 407 | if(strcmp("print", argv[i]) == 0 || strcmp("-print", argv[i]) == 0 || strcmp("--print", argv[i]) == 0) { |
| 408 | print_tree_and_exit = true; |
| 409 | continue; |
| 410 | } |
| 411 | |
| 412 | #if defined(OS_LINUX) |
| 413 | if(strcmp("test-permissions", argv[i]) == 0 || strcmp("-t", argv[i]) == 0) { |
| 414 | if(!check_proc_1_io()) { |
| 415 | perror("Tried to read /proc/1/io and it failed"); |
| 416 | exit(1); |
| 417 | } |
| 418 | printf("OK\n"); |
| 419 | exit(0); |
| 420 | } |
| 421 | #endif |
| 422 | |
| 423 | if(strcmp("debug", argv[i]) == 0) { |
| 424 | debug_enabled = true; |
| 425 | #ifndef NETDATA_INTERNAL_CHECKS |
| 426 | fprintf(stderr, "apps.plugin has been compiled without debugging\n"); |
| 427 | #endif |
| 428 | continue; |
| 429 | } |
| 430 | |
| 431 | if(strcmp("profile-speed", argv[i]) == 0) { |
| 432 | profile_speed = true; |
| 433 | continue; |
| 434 | } |
| 435 | |
| 436 | #if defined(OS_LINUX) |
| 437 | if(strcmp("fds-cache-secs", argv[i]) == 0) { |
| 438 | if(argc <= i + 1) { |
| 439 | fprintf(stderr, "Parameter 'fds-cache-secs' requires a number as argument.\n"); |
| 440 | exit(1); |
| 441 | } |
| 442 | i++; |
| 443 | max_fds_cache_seconds = str2i(argv[i]); |
| 444 | if(max_fds_cache_seconds < 0) max_fds_cache_seconds = 0; |
| 445 | continue; |
| 446 | } |
| 447 | |
| 448 | #if (PROCESSES_HAVE_SMAPS_ROLLUP == 1) |
| 449 | if(strcmp("--pss", argv[i]) == 0) { |
| 450 | if(argc <= i + 1) { |
| 451 | fprintf(stderr, "Parameter '--pss' requires a duration (e.g. 5m, 300s) or 'off'.\n"); |
| 452 | exit(1); |
| 453 | } |
| 454 | i++; |
| 455 | int64_t seconds = 0; |
| 456 | if(!duration_parse(argv[i], &seconds, "s", "s")) { |
| 457 | fprintf(stderr, "Cannot parse '--pss' value '%s'.\n", argv[i]); |
| 458 | exit(1); |
| 459 | } |
| 460 | if(seconds <= 0) { |
| 461 | pss_refresh_period = 0; // disabled |
| 462 | } |
| 463 | else { |
| 464 | pss_refresh_period = (int)seconds; |
| 465 | if(pss_refresh_period < 1) |
| 466 | pss_refresh_period = 1; |
| 467 | } |
| 468 | continue; |
| 469 | } |
| 470 | #endif |
| 471 | #endif |
| 472 | |
| 473 | #if (PROCESSES_HAVE_CPU_CHILDREN_TIME == 1) || (PROCESSES_HAVE_CHILDREN_FLTS == 1) |
| 474 | if(strcmp("no-childs", argv[i]) == 0 || strcmp("without-childs", argv[i]) == 0) { |
| 475 | include_exited_childs = 0; |
| 476 | continue; |
| 477 | } |
| 478 | |
| 479 | if(strcmp("with-childs", argv[i]) == 0) { |
| 480 | include_exited_childs = 1; |
| 481 | continue; |
| 482 | } |
| 483 | #endif |
| 484 | |
| 485 | #if (PROCESSES_HAVE_CPU_GUEST_TIME == 1) |
| 486 | if(strcmp("with-guest", argv[i]) == 0) { |
| 487 | enable_guest_charts = true; |
| 488 | continue; |
| 489 | } |
| 490 | |
| 491 | if(strcmp("no-guest", argv[i]) == 0 || strcmp("without-guest", argv[i]) == 0) { |
| 492 | enable_guest_charts = false; |
| 493 | continue; |
| 494 | } |
| 495 | #endif |
| 496 | |
| 497 | #if (PROCESSES_HAVE_FDS == 1) |
| 498 | if(strcmp("with-files", argv[i]) == 0) { |
| 499 | enable_file_charts = CONFIG_BOOLEAN_YES; |
| 500 | continue; |
| 501 | } |
| 502 | |
| 503 | if(strcmp("no-files", argv[i]) == 0 || strcmp("without-files", argv[i]) == 0) { |
| 504 | enable_file_charts = CONFIG_BOOLEAN_NO; |
| 505 | continue; |
| 506 | } |
| 507 | #endif |
| 508 | |
| 509 | #if (PROCESSES_HAVE_UID == 1) || (PROCESSES_HAVE_SID == 1) |
| 510 | if(strcmp("no-users", argv[i]) == 0 || strcmp("without-users", argv[i]) == 0) { |
| 511 | enable_users_charts = 0; |
| 512 | continue; |
| 513 | } |
| 514 | #endif |
| 515 | |
| 516 | #if (PROCESSES_HAVE_GID == 1) |
| 517 | if(strcmp("no-groups", argv[i]) == 0 || strcmp("without-groups", argv[i]) == 0) { |
| 518 | enable_groups_charts = 0; |
| 519 | continue; |
| 520 | } |
| 521 | #endif |
| 522 | |
| 523 | #if (PROCESSES_HAVE_SERVICE == 1) |
| 524 | if(strcmp("no-services", argv[i]) == 0 || strcmp("without-services", argv[i]) == 0) { |
| 525 | enable_services_charts = false; |
| 526 | continue; |
| 527 | } |
| 528 | #endif |
| 529 | |
| 530 | if(strcmp("with-detailed-uptime", argv[i]) == 0) { |
| 531 | enable_detailed_uptime_charts = 1; |
| 532 | continue; |
| 533 | } |
| 534 | if(strcmp("with-function-cmdline", argv[i]) == 0) { |
| 535 | enable_function_cmdline = true; |
| 536 | continue; |
| 537 | } |
| 538 | |
| 539 | if(strcmp("-h", argv[i]) == 0 || strcmp("--help", argv[i]) == 0) { |
| 540 | fprintf(stderr, |
| 541 | "\n" |
| 542 | " netdata apps.plugin %s\n" |
| 543 | " Copyright 2018-2025 Netdata Inc.\n" |
| 544 | " Released under GNU General Public License v3 or later.\n" |
| 545 | " All rights reserved.\n" |
| 546 | "\n" |
| 547 | " This program is a data collector plugin for netdata.\n" |
| 548 | "\n" |
| 549 | " Available command line options:\n" |
| 550 | "\n" |
| 551 | " SECONDS set the data collection frequency\n" |
| 552 | "\n" |
| 553 | " debug enable debugging (lot of output)\n" |
| 554 | "\n" |
| 555 | " with-function-cmdline enable reporting the complete command line for processes\n" |
| 556 | " it includes the command and passed arguments\n" |
| 557 | " it may include sensitive data such as passwords and tokens\n" |
| 558 | " enabling this could be a security risk\n" |
| 559 | "\n" |
| 560 | #if (PROCESSES_HAVE_CPU_CHILDREN_TIME == 1) || (PROCESSES_HAVE_CHILDREN_FLTS == 1) |
| 561 | " with-childs\n" |
| 562 | " without-childs enable / disable aggregating exited\n" |
| 563 | " children resources into parents\n" |
| 564 | " (default is enabled)\n" |
| 565 | "\n" |
| 566 | #endif |
| 567 | #if (PROCESSES_HAVE_CPU_GUEST_TIME == 1) |
| 568 | " with-guest\n" |
| 569 | " without-guest enable / disable reporting guest charts\n" |
| 570 | " (default is disabled)\n" |
| 571 | "\n" |
| 572 | #endif |
| 573 | #if (PROCESSES_HAVE_FDS == 1) |
| 574 | " with-files\n" |
| 575 | " without-files enable / disable reporting files, sockets, pipes\n" |
| 576 | " (default is enabled)\n" |
| 577 | "\n" |
| 578 | #endif |
| 579 | #if (PROCESSES_HAVE_UID == 1) || (PROCESSES_HAVE_SID == 1) |
| 580 | " without-users disable reporting per user charts\n" |
| 581 | "\n" |
| 582 | #endif |
| 583 | #if (PROCESSES_HAVE_GID == 1) |
| 584 | " without-groups disable reporting per user group charts\n" |
| 585 | "\n" |
| 586 | #endif |
| 587 | #if (PROCESSES_HAVE_SERVICE == 1) |
| 588 | " without-services disable reporting per Windows service charts\n" |
| 589 | "\n" |
| 590 | #endif |
| 591 | " with-detailed-uptime enable reporting min/avg/max uptime charts\n" |
| 592 | "\n" |
| 593 | #if defined(OS_LINUX) |
| 594 | " fds-cache-secs N cache the files of processed for N seconds\n" |
| 595 | " caching is adaptive per file (when a file\n" |
| 596 | " is found, it starts at 0 and while the file\n" |
| 597 | " remains open, it is incremented up to the\n" |
| 598 | " max given)\n" |
| 599 | " (default is %d seconds)\n" |
| 600 | "\n" |
| 601 | #if (PROCESSES_HAVE_SMAPS_ROLLUP == 1) |
| 602 | " --pss TIME enable estimated memory using PSS sampling at the given interval\n" |
| 603 | " (e.g. 5m, 300s). Use 'off' or '0' to disable.\n" |
| 604 | " (default is off)\n" |
| 605 | "\n" |
| 606 | #endif |
| 607 | #endif |
| 608 | " version or -v or -V print program version and exit\n" |
| 609 | "\n" |
| 610 | , NETDATA_VERSION |
| 611 | #if defined(OS_LINUX) |
| 612 | , max_fds_cache_seconds |
| 613 | #endif |
| 614 | ); |
| 615 | exit(0); |
| 616 | } |
| 617 | |
| 618 | #if !defined(OS_WINDOWS) || !defined(RUN_UNDER_CLION) |
| 619 | netdata_log_error("Cannot understand option %s", argv[i]); |
| 620 | exit(1); |
| 621 | #endif |
| 622 | } |
| 623 | |
| 624 | if(freq > 0) update_every = freq; |
| 625 | |
| 626 | if(read_apps_groups_conf(user_config_dir, "groups")) { |
| 627 | netdata_log_info("Cannot read process groups configuration file '%s/apps_groups.conf'. Will try '%s/apps_groups.conf'", user_config_dir, stock_config_dir); |
| 628 | |
| 629 | if(read_apps_groups_conf(stock_config_dir, "groups")) { |
| 630 | netdata_log_error("Cannot read process groups '%s/apps_groups.conf'. There are no internal defaults. Failing.", stock_config_dir); |
| 631 | exit(1); |
| 632 | } |
| 633 | else |
| 634 | netdata_log_info("Loaded config file '%s/apps_groups.conf'", stock_config_dir); |
| 635 | } |
| 636 | else |
| 637 | netdata_log_info("Loaded config file '%s/apps_groups.conf'", user_config_dir); |
| 638 | } |
| 639 | |
| 640 | #if !defined(OS_WINDOWS) |
| 641 | static inline int am_i_running_as_root() { |
| 642 | uid_t uid = getuid(), euid = geteuid(); |
| 643 | |
| 644 | if(uid == 0 || euid == 0) { |
| 645 | if(debug_enabled) netdata_log_info("I am running with escalated privileges, uid = %u, euid = %u.", uid, euid); |
| 646 | return 1; |
| 647 | } |
| 648 | |
| 649 | if(debug_enabled) netdata_log_info("I am not running with escalated privileges, uid = %u, euid = %u.", uid, euid); |
| 650 | return 0; |
| 651 | } |
| 652 | |
| 653 | #ifdef HAVE_CAPABILITY |
| 654 | static inline int check_capabilities() { |
| 655 | cap_t caps = cap_get_proc(); |
| 656 | if(!caps) { |
| 657 | netdata_log_error("Cannot get current capabilities."); |
| 658 | return 0; |
| 659 | } |
| 660 | else if(debug_enabled) |
| 661 | netdata_log_info("Received my capabilities from the system."); |
| 662 | |
| 663 | int ret = 1; |
| 664 | |
| 665 | cap_flag_value_t cfv = CAP_CLEAR; |
| 666 | if(cap_get_flag(caps, CAP_DAC_READ_SEARCH, CAP_EFFECTIVE, &cfv) == -1) { |
| 667 | netdata_log_error("Cannot find if CAP_DAC_READ_SEARCH is effective."); |
| 668 | ret = 0; |
| 669 | } |
| 670 | else { |
| 671 | if(cfv != CAP_SET) { |
| 672 | netdata_log_error("apps.plugin should run with CAP_DAC_READ_SEARCH."); |
| 673 | ret = 0; |
| 674 | } |
| 675 | else if(debug_enabled) |
| 676 | netdata_log_info("apps.plugin runs with CAP_DAC_READ_SEARCH."); |
| 677 | } |
| 678 | |
| 679 | cfv = CAP_CLEAR; |
| 680 | if(cap_get_flag(caps, CAP_SYS_PTRACE, CAP_EFFECTIVE, &cfv) == -1) { |
| 681 | netdata_log_error("Cannot find if CAP_SYS_PTRACE is effective."); |
| 682 | ret = 0; |
| 683 | } |
| 684 | else { |
| 685 | if(cfv != CAP_SET) { |
| 686 | netdata_log_error("apps.plugin should run with CAP_SYS_PTRACE."); |
| 687 | ret = 0; |
| 688 | } |
| 689 | else if(debug_enabled) |
| 690 | netdata_log_info("apps.plugin runs with CAP_SYS_PTRACE."); |
| 691 | } |
| 692 | |
| 693 | cap_free(caps); |
| 694 | |
| 695 | return ret; |
| 696 | } |
| 697 | #else |
| 698 | static inline int check_capabilities() { |
| 699 | return 0; |
| 700 | } |
| 701 | #endif |
| 702 | #endif |
| 703 | |
| 704 | netdata_mutex_t apps_and_stdout_mutex; |
| 705 | |
| 706 | static void __attribute__((constructor)) init_mutex(void) { |
| 707 | netdata_mutex_init(&apps_and_stdout_mutex); |
| 708 | } |
| 709 | |
| 710 | static void __attribute__((destructor)) destroy_mutex(void) { |
| 711 | netdata_mutex_destroy(&apps_and_stdout_mutex); |
| 712 | } |
| 713 | |
| 714 | static bool apps_plugin_exit = false; |
| 715 | |
| 716 | int main(int argc, char **argv) { |
| 717 | nd_log_initialize_for_external_plugins("apps.plugin"); |
| 718 | netdata_threads_init_for_external_plugins(0); |
| 719 | |
| 720 | pagesize = (size_t)sysconf(_SC_PAGESIZE); |
| 721 | |
| 722 | bool send_resource_usage = true; |
| 723 | { |
| 724 | const char *s = getenv("NETDATA_INTERNALS_MONITORING"); |
| 725 | if(s && *s && strcmp(s, "NO") == 0) |
| 726 | send_resource_usage = false; |
| 727 | } |
| 728 | |
| 729 | // since apps.plugin runs as root, prevent it from opening symbolic links |
| 730 | procfile_open_flags = O_RDONLY|O_NOFOLLOW; |
| 731 | |
| 732 | netdata_configured_host_prefix = getenv("NETDATA_HOST_PREFIX"); |
| 733 | if(verify_netdata_host_prefix(true) == -1) exit(1); |
| 734 | |
| 735 | user_config_dir = getenv("NETDATA_USER_CONFIG_DIR"); |
| 736 | if(user_config_dir == NULL) { |
| 737 | // netdata_log_info("NETDATA_CONFIG_DIR is not passed from netdata"); |
| 738 | user_config_dir = CONFIG_DIR; |
| 739 | } |
| 740 | // else netdata_log_info("Found NETDATA_USER_CONFIG_DIR='%s'", user_config_dir); |
| 741 | |
| 742 | stock_config_dir = getenv("NETDATA_STOCK_CONFIG_DIR"); |
| 743 | if(stock_config_dir == NULL) { |
| 744 | // netdata_log_info("NETDATA_CONFIG_DIR is not passed from netdata"); |
| 745 | stock_config_dir = LIBCONFIG_DIR; |
| 746 | } |
| 747 | // else netdata_log_info("Found NETDATA_USER_CONFIG_DIR='%s'", user_config_dir); |
| 748 | |
| 749 | #ifdef NETDATA_INTERNAL_CHECKS |
| 750 | if(debug_flags != 0) { |
| 751 | struct rlimit rl = { RLIM_INFINITY, RLIM_INFINITY }; |
| 752 | if(setrlimit(RLIMIT_CORE, &rl) != 0) |
| 753 | netdata_log_info("Cannot request unlimited core dumps for debugging... Proceeding anyway..."); |
| 754 | #ifdef HAVE_SYS_PRCTL_H |
| 755 | prctl(PR_SET_DUMPABLE, 1, 0, 0, 0); |
| 756 | #endif |
| 757 | } |
| 758 | #endif /* NETDATA_INTERNAL_CHECKS */ |
| 759 | |
| 760 | procfile_set_adaptive_allocation(true, 0, 0, 0); |
| 761 | os_get_system_cpus_uncached(); |
| 762 | apps_managers_and_aggregators_init(); // before parsing args! |
| 763 | parse_args(argc, argv); |
| 764 | |
| 765 | #if !defined(OS_WINDOWS) |
| 766 | if(!check_capabilities() && !am_i_running_as_root() && !check_proc_1_io()) { |
| 767 | uid_t uid = getuid(), euid = geteuid(); |
| 768 | #ifdef HAVE_CAPABILITY |
| 769 | netdata_log_error("apps.plugin should either run as root (now running with uid %u, euid %u) or have special capabilities. " |
| 770 | "Without these, apps.plugin cannot report disk I/O utilization of other processes. " |
| 771 | "To enable capabilities run: sudo setcap cap_dac_read_search,cap_sys_ptrace+ep %s; " |
| 772 | "To enable setuid to root run: sudo chown root:netdata %s; sudo chmod 4750 %s; " |
| 773 | , uid, euid, argv[0], argv[0], argv[0]); |
| 774 | #else |
| 775 | netdata_log_error("apps.plugin should either run as root (now running with uid %u, euid %u) or have special capabilities. " |
| 776 | "Without these, apps.plugin cannot report disk I/O utilization of other processes. " |
| 777 | "Your system does not support capabilities. " |
| 778 | "To enable setuid to root run: sudo chown root:netdata %s; sudo chmod 4750 %s; " |
| 779 | , uid, euid, argv[0], argv[0]); |
| 780 | #endif |
| 781 | } |
| 782 | #endif |
| 783 | |
| 784 | netdata_log_info("started on pid %d", getpid()); |
| 785 | |
| 786 | #if (PROCESSES_HAVE_UID == 1) |
| 787 | cached_usernames_init(); |
| 788 | #endif |
| 789 | |
| 790 | #if (PROCESSES_HAVE_GID == 1) |
| 791 | cached_groupnames_init(); |
| 792 | #endif |
| 793 | |
| 794 | #if (PROCESSES_HAVE_SID == 1) |
| 795 | cached_sid_username_init(); |
| 796 | #endif |
| 797 | |
| 798 | apps_pids_init(); |
| 799 | OS_FUNCTION(apps_os_init)(); |
| 800 | int exit_status = 0; |
| 801 | |
| 802 | // ------------------------------------------------------------------------ |
| 803 | // the event loop for functions |
| 804 | |
| 805 | struct functions_evloop_globals *wg = |
| 806 | functions_evloop_init(1, "APPS", &apps_and_stdout_mutex, &apps_plugin_exit, &exit_status); |
| 807 | |
| 808 | functions_evloop_add_function(wg, "processes", function_processes, PLUGINS_FUNCTIONS_TIMEOUT_DEFAULT, NULL); |
| 809 | |
| 810 | // ------------------------------------------------------------------------ |
| 811 | |
| 812 | netdata_mutex_lock(&apps_and_stdout_mutex); |
| 813 | APPS_PLUGIN_GLOBAL_FUNCTIONS(); |
| 814 | |
| 815 | global_iterations_counter = 1; |
| 816 | heartbeat_t hb; |
| 817 | heartbeat_init(&hb, update_every * USEC_PER_SEC); |
| 818 | for (; !__atomic_load_n(&apps_plugin_exit, __ATOMIC_ACQUIRE); global_iterations_counter++) { |
| 819 | netdata_mutex_unlock(&apps_and_stdout_mutex); |
| 820 | |
| 821 | usec_t dt; |
| 822 | if(profile_speed) { |
| 823 | static int profiling_count=0; |
| 824 | profiling_count++; |
| 825 | if(unlikely(profiling_count > 500)) exit(0); |
| 826 | dt = update_every * USEC_PER_SEC; |
| 827 | } |
| 828 | else |
| 829 | dt = heartbeat_next(&hb); |
| 830 | |
| 831 | netdata_mutex_lock(&apps_and_stdout_mutex); |
| 832 | |
| 833 | struct pollfd pollfd = { .fd = fileno(stdout), .events = POLLERR }; |
| 834 | if (unlikely(poll(&pollfd, 1, 0) < 0)) { |
| 835 | netdata_mutex_unlock(&apps_and_stdout_mutex); |
| 836 | fatal("Cannot check if a pipe is available"); |
| 837 | } |
| 838 | if (unlikely(pollfd.revents & POLLERR)) { |
| 839 | netdata_mutex_unlock(&apps_and_stdout_mutex); |
| 840 | fatal("Received error on read pipe."); |
| 841 | } |
| 842 | |
| 843 | if(!collect_data_for_all_pids()) { |
| 844 | netdata_log_error("Cannot collect /proc data for running processes. Disabling apps.plugin..."); |
| 845 | printf("DISABLE\n"); |
| 846 | netdata_mutex_unlock(&apps_and_stdout_mutex); |
| 847 | exit(1); |
| 848 | } |
| 849 | |
| 850 | aggregate_processes_to_targets(); |
| 851 | |
| 852 | #if (ALL_PIDS_ARE_READ_INSTANTLY == 0) |
| 853 | OS_FUNCTION(apps_os_read_global_cpu_utilization)(); |
| 854 | normalize_utilization(apps_groups_root_target); |
| 855 | #endif |
| 856 | |
| 857 | if(unlikely(print_tree_and_exit)) { |
| 858 | print_hierarchy(root_of_pids()); |
| 859 | netdata_mutex_unlock(&apps_and_stdout_mutex); |
| 860 | exit(0); |
| 861 | } |
| 862 | |
| 863 | if(send_resource_usage) |
| 864 | send_resource_usage_to_netdata(dt); |
| 865 | |
| 866 | #if (PROCESSES_HAVE_STATE == 1) |
| 867 | send_proc_states_count(dt); |
| 868 | #endif |
| 869 | |
| 870 | send_charts_updates_to_netdata(apps_groups_root_target, "app", "app_group", "Applications Groups"); |
| 871 | send_collected_data_to_netdata(apps_groups_root_target, "app", dt); |
| 872 | |
| 873 | #if (PROCESSES_HAVE_UID == 1) |
| 874 | if (enable_users_charts) { |
| 875 | send_charts_updates_to_netdata(users_root_target, "user", "user", "User"); |
| 876 | send_collected_data_to_netdata(users_root_target, "user", dt); |
| 877 | } |
| 878 | #endif |
| 879 | |
| 880 | #if (PROCESSES_HAVE_GID == 1) |
| 881 | if (enable_groups_charts) { |
| 882 | send_charts_updates_to_netdata(groups_root_target, "usergroup", "user_group", "User Group"); |
| 883 | send_collected_data_to_netdata(groups_root_target, "usergroup", dt); |
| 884 | } |
| 885 | #endif |
| 886 | |
| 887 | #if (PROCESSES_HAVE_SID == 1) |
| 888 | if (enable_users_charts) { |
| 889 | send_charts_updates_to_netdata(sids_root_target, "user", "user", "User Processes"); |
| 890 | send_collected_data_to_netdata(sids_root_target, "user", dt); |
| 891 | } |
| 892 | #endif |
| 893 | |
| 894 | #if (PROCESSES_HAVE_SERVICE == 1) |
| 895 | if (enable_services_charts) { |
| 896 | send_charts_updates_to_netdata(services_root_target, "service", "service", "Windows Service"); |
| 897 | send_collected_data_to_netdata(services_root_target, "service", dt); |
| 898 | } |
| 899 | #endif |
| 900 | |
| 901 | fflush(stdout); |
| 902 | |
| 903 | debug_log("done Loop No %zu", global_iterations_counter); |
| 904 | } |
| 905 | netdata_mutex_unlock(&apps_and_stdout_mutex); |
| 906 | exit(exit_status); |
| 907 | } |