X-Git-Url: https://git.octo.it/?p=collectd.git;a=blobdiff_plain;f=src%2Fprocesses.c;h=d73d24a2a064488c030b79040e98f26ad49239d5;hp=d6d3e87abc411fc52729b0d6dd89e1c8ba2c2265;hb=77ca1a45bab2f6adf9301723d0db68e5813a6d98;hpb=68557b1c5f551c1e46bf7588a86e9d4311c23717 diff --git a/src/processes.c b/src/processes.c index d6d3e87a..d73d24a2 100644 --- a/src/processes.c +++ b/src/processes.c @@ -1,7 +1,7 @@ /** * collectd - src/processes.c * Copyright (C) 2005 Lyonel Vincent - * Copyright (C) 2006-2010 Florian octo Forster + * Copyright (C) 2006-2017 Florian octo Forster * Copyright (C) 2008 Oleg King * Copyright (C) 2009 Sebastian Harl * Copyright (C) 2009 Andrés J. Díaz @@ -32,6 +32,8 @@ * Manuel Sanmartin * Clément Stenac * Cosmin Ioiart + * Pavel Rochnyack + * Wilfried Goesgens **/ #include "collectd.h" @@ -39,6 +41,11 @@ #include "common.h" #include "plugin.h" +#if HAVE_LIBTASKSTATS +#include "utils_complain.h" +#include "utils_taskstats.h" +#endif + /* Include header files for the mach system, if they exist.. */ #if HAVE_THREAD_INFO #if HAVE_MACH_MACH_INIT_H @@ -151,6 +158,10 @@ #include #endif +#ifdef HAVE_SYS_CAPABILITY_H +#include +#endif + #ifndef CMDLINE_BUFFER_SIZE #if defined(ARG_MAX) && (ARG_MAX < 4096) #define CMDLINE_BUFFER_SIZE ARG_MAX @@ -166,6 +177,8 @@ typedef struct process_entry_s { unsigned long num_proc; unsigned long num_lwp; + unsigned long num_fd; + unsigned long num_maps; unsigned long vmem_size; unsigned long vmem_rss; unsigned long vmem_data; @@ -183,11 +196,22 @@ typedef struct process_entry_s { derive_t io_wchar; derive_t io_syscr; derive_t io_syscw; + derive_t io_diskr; + derive_t io_diskw; _Bool has_io; derive_t cswitch_vol; derive_t cswitch_invol; _Bool has_cswitch; + +#if HAVE_LIBTASKSTATS + ts_delay_t delay; +#endif + _Bool has_delay; + + _Bool has_fd; + + _Bool has_maps; } process_entry_t; typedef struct procstat_entry_s { @@ -205,10 +229,19 @@ typedef struct procstat_entry_s { derive_t io_wchar; derive_t io_syscr; derive_t io_syscw; + derive_t io_diskr; + derive_t io_diskw; derive_t cswitch_vol; derive_t cswitch_invol; +#if HAVE_LIBTASKSTATS + value_to_rate_state_t delay_cpu; + value_to_rate_state_t delay_blkio; + value_to_rate_state_t delay_swapin; + value_to_rate_state_t delay_freepages; +#endif + struct procstat_entry_s *next; } procstat_entry_t; @@ -220,6 +253,8 @@ typedef struct procstat { unsigned long num_proc; unsigned long num_lwp; + unsigned long num_fd; + unsigned long num_maps; unsigned long vmem_size; unsigned long vmem_rss; unsigned long vmem_data; @@ -237,10 +272,23 @@ typedef struct procstat { derive_t io_wchar; derive_t io_syscr; derive_t io_syscw; + derive_t io_diskr; + derive_t io_diskw; derive_t cswitch_vol; derive_t cswitch_invol; + /* Linux Delay Accounting. Unit is ns/s. */ + gauge_t delay_cpu; + gauge_t delay_blkio; + gauge_t delay_swapin; + gauge_t delay_freepages; + + _Bool report_fd_num; + _Bool report_maps_num; + _Bool report_ctx_switch; + _Bool report_delay; + struct procstat *next; struct procstat_entry_s *instances; } procstat_t; @@ -249,6 +297,9 @@ static procstat_t *list_head_g = NULL; static _Bool want_init = 1; static _Bool report_ctx_switch = 0; +static _Bool report_fd_num = 0; +static _Bool report_maps_num = 0; +static _Bool report_delay = 0; #if HAVE_THREAD_INFO static mach_port_t port_host_self; @@ -282,10 +333,14 @@ int getthrds64(pid_t, void *, int, tid64_t *, int); int getargs(void *processBuffer, int bufferLen, char *argsBuffer, int argsLen); #endif /* HAVE_PROCINFO_H */ +#if HAVE_LIBTASKSTATS +static ts_t *taskstats_handle = NULL; +#endif + /* put name of process from config to list_head_g tree * list_head_g is a list of 'procstat_t' structs with * processes names we want to watch */ -static void ps_list_register(const char *name, const char *regexp) { +static procstat_t *ps_list_register(const char *name, const char *regexp) { procstat_t *new; procstat_t *ptr; int status; @@ -293,7 +348,7 @@ static void ps_list_register(const char *name, const char *regexp) { new = calloc(1, sizeof(*new)); if (new == NULL) { ERROR("processes plugin: ps_list_register: calloc failed."); - return; + return NULL; } sstrncpy(new->name, name, sizeof(new->name)); @@ -301,9 +356,16 @@ static void ps_list_register(const char *name, const char *regexp) { new->io_wchar = -1; new->io_syscr = -1; new->io_syscw = -1; + new->io_diskr = -1; + new->io_diskw = -1; new->cswitch_vol = -1; new->cswitch_invol = -1; + new->report_fd_num = report_fd_num; + new->report_maps_num = report_maps_num; + new->report_ctx_switch = report_ctx_switch; + new->report_delay = report_delay; + #if HAVE_REGEX_H if (regexp != NULL) { DEBUG("ProcessMatch: adding \"%s\" as criteria to process %s.", regexp, @@ -312,7 +374,7 @@ static void ps_list_register(const char *name, const char *regexp) { if (new->re == NULL) { ERROR("processes plugin: ps_list_register: malloc failed."); sfree(new); - return; + return NULL; } status = regcomp(new->re, regexp, REG_EXTENDED | REG_NOSUB); @@ -321,7 +383,7 @@ static void ps_list_register(const char *name, const char *regexp) { regexp); sfree(new->re); sfree(new); - return; + return NULL; } } #else @@ -332,7 +394,7 @@ static void ps_list_register(const char *name, const char *regexp) { "has been disabled at compile time.", regexp); sfree(new); - return; + return NULL; } #endif @@ -347,7 +409,7 @@ static void ps_list_register(const char *name, const char *regexp) { sfree(new->re); #endif sfree(new); - return; + return NULL; } if (ptr->next == NULL) @@ -358,6 +420,8 @@ static void ps_list_register(const char *name, const char *regexp) { list_head_g = new; else ptr->next = new; + + return new; } /* void ps_list_register */ /* try to match name against entry, returns 1 if success */ @@ -379,13 +443,13 @@ static int ps_list_match(const char *name, const char *cmdline, /* pmatch = */ NULL, /* eflags = */ 0); if (status == 0) - return (1); + return 1; } else #endif if (strcmp(ps->name, name) == 0) - return (1); + return 1; - return (0); + return 0; } /* int ps_list_match */ static void ps_update_counter(derive_t *group_counter, derive_t *curr_counter, @@ -409,6 +473,39 @@ static void ps_update_counter(derive_t *group_counter, derive_t *curr_counter, *group_counter += curr_value; } +#if HAVE_LIBTASKSTATS +static void ps_update_delay_one(gauge_t *out_rate_sum, + value_to_rate_state_t *state, uint64_t cnt, + cdtime_t t) { + gauge_t rate = NAN; + int status = value_to_rate(&rate, (value_t){.counter = (counter_t)cnt}, + DS_TYPE_COUNTER, t, state); + if ((status != 0) || isnan(rate)) { + return; + } + + if (isnan(*out_rate_sum)) { + *out_rate_sum = rate; + } else { + *out_rate_sum += rate; + } +} + +static void ps_update_delay(procstat_t *out, procstat_entry_t *prev, + process_entry_t *curr) { + cdtime_t now = cdtime(); + + ps_update_delay_one(&out->delay_cpu, &prev->delay_cpu, curr->delay.cpu_ns, + now); + ps_update_delay_one(&out->delay_blkio, &prev->delay_blkio, + curr->delay.blkio_ns, now); + ps_update_delay_one(&out->delay_swapin, &prev->delay_swapin, + curr->delay.swapin_ns, now); + ps_update_delay_one(&out->delay_freepages, &prev->delay_freepages, + curr->delay.freepages_ns, now); +} +#endif + /* add process entry to 'instances' of process 'name' (or refresh it) */ static void ps_list_add(const char *name, const char *cmdline, process_entry_t *entry) { @@ -449,6 +546,8 @@ static void ps_list_add(const char *name, const char *cmdline, ps->num_proc += entry->num_proc; ps->num_lwp += entry->num_lwp; + ps->num_fd += entry->num_fd; + ps->num_maps += entry->num_maps; ps->vmem_size += entry->vmem_size; ps->vmem_rss += entry->vmem_rss; ps->vmem_data += entry->vmem_data; @@ -465,7 +564,12 @@ static void ps_list_add(const char *name, const char *cmdline, ps_update_counter(&ps->io_syscw, &pse->io_syscw, entry->io_syscw); } - if ((entry->cswitch_vol != -1) && (entry->cswitch_vol != -1)) { + if ((entry->io_diskr != -1) && (entry->io_diskw != -1)) { + ps_update_counter(&ps->io_diskr, &pse->io_diskr, entry->io_diskr); + ps_update_counter(&ps->io_diskw, &pse->io_diskw, entry->io_diskw); + } + + if ((entry->cswitch_vol != -1) && (entry->cswitch_invol != -1)) { ps_update_counter(&ps->cswitch_vol, &pse->cswitch_vol, entry->cswitch_vol); ps_update_counter(&ps->cswitch_invol, &pse->cswitch_invol, @@ -481,6 +585,10 @@ static void ps_list_add(const char *name, const char *cmdline, entry->cpu_user_counter); ps_update_counter(&ps->cpu_system_counter, &pse->cpu_system_counter, entry->cpu_system_counter); + +#if HAVE_LIBTASKSTATS + ps_update_delay(ps, pse, entry); +#endif } } @@ -492,12 +600,19 @@ static void ps_list_reset(void) { for (procstat_t *ps = list_head_g; ps != NULL; ps = ps->next) { ps->num_proc = 0; ps->num_lwp = 0; + ps->num_fd = 0; + ps->num_maps = 0; ps->vmem_size = 0; ps->vmem_rss = 0; ps->vmem_data = 0; ps->vmem_code = 0; ps->stack_size = 0; + ps->delay_cpu = NAN; + ps->delay_blkio = NAN; + ps->delay_swapin = NAN; + ps->delay_freepages = NAN; + pse_prev = NULL; pse = ps->instances; while (pse != NULL) { @@ -524,6 +639,29 @@ static void ps_list_reset(void) { } /* for (ps = list_head_g; ps != NULL; ps = ps->next) */ } +static void ps_tune_instance(oconfig_item_t *ci, procstat_t *ps) { + for (int i = 0; i < ci->children_num; i++) { + oconfig_item_t *c = ci->children + i; + + if (strcasecmp(c->key, "CollectContextSwitch") == 0) + cf_util_get_boolean(c, &ps->report_ctx_switch); + else if (strcasecmp(c->key, "CollectFileDescriptor") == 0) + cf_util_get_boolean(c, &ps->report_fd_num); + else if (strcasecmp(c->key, "CollectMemoryMaps") == 0) + cf_util_get_boolean(c, &ps->report_maps_num); + else if (strcasecmp(c->key, "CollectDelayAccounting") == 0) { +#if HAVE_LIBTASKSTATS + cf_util_get_boolean(c, &ps->report_delay); +#else + WARNING("processes plugin: The plugin has been compiled without support " + "for the \"CollectDelayAccounting\" option."); +#endif + } else { + ERROR("processes plugin: Option \"%s\" not allowed here.", c->key); + } + } /* for (ci->children) */ +} /* void ps_tune_instance */ + /* put all pre-defined 'Process' names from config to list_head_g tree */ static int ps_config(oconfig_item_t *ci) { #if KERNEL_LINUX @@ -532,6 +670,8 @@ static int ps_config(oconfig_item_t *ci) { const size_t max_procname_len = MAXCOMLEN - 1; #endif + procstat_t *ps; + for (int i = 0; i < ci->children_num; ++i) { oconfig_item_t *c = ci->children + i; @@ -543,23 +683,20 @@ static int ps_config(oconfig_item_t *ci) { continue; } - if (c->children_num != 0) { - WARNING("processes plugin: the `Process' config option " - "does not expect any child elements -- ignoring " - "content (%i elements) of the block.", - c->children_num, c->values[0].value.string); - } - #if KERNEL_LINUX || KERNEL_SOLARIS || KERNEL_FREEBSD if (strlen(c->values[0].value.string) > max_procname_len) { - WARNING("processes plugin: this platform has a %zu character limit " + WARNING("processes plugin: this platform has a %" PRIsz + " character limit " "to process names. The `Process \"%s\"' option will " "not work as expected.", max_procname_len, c->values[0].value.string); } #endif - ps_list_register(c->values[0].value.string, NULL); + ps = ps_list_register(c->values[0].value.string, NULL); + + if (c->children_num != 0 && ps != NULL) + ps_tune_instance(c, ps); } else if (strcasecmp(c->key, "ProcessMatch") == 0) { if ((c->values_num != 2) || (OCONFIG_TYPE_STRING != c->values[0].type) || (OCONFIG_TYPE_STRING != c->values[1].type)) { @@ -569,18 +706,24 @@ static int ps_config(oconfig_item_t *ci) { continue; } - if (c->children_num != 0) { - WARNING("processes plugin: the `ProcessMatch' config option " - "does not expect any child elements -- ignoring " - "content (%i elements) of the " - "block.", - c->children_num, c->values[0].value.string, - c->values[1].value.string); - } + ps = ps_list_register(c->values[0].value.string, + c->values[1].value.string); - ps_list_register(c->values[0].value.string, c->values[1].value.string); + if (c->children_num != 0 && ps != NULL) + ps_tune_instance(c, ps); } else if (strcasecmp(c->key, "CollectContextSwitch") == 0) { cf_util_get_boolean(c, &report_ctx_switch); + } else if (strcasecmp(c->key, "CollectFileDescriptor") == 0) { + cf_util_get_boolean(c, &report_fd_num); + } else if (strcasecmp(c->key, "CollectMemoryMaps") == 0) { + cf_util_get_boolean(c, &report_maps_num); + } else if (strcasecmp(c->key, "CollectDelayAccounting") == 0) { +#if HAVE_LIBTASKSTATS + cf_util_get_boolean(c, &report_delay); +#else + WARNING("processes plugin: The plugin has been compiled without support " + "for the \"CollectDelayAccounting\" option."); +#endif } else { ERROR("processes plugin: The `%s' configuration option is not " "understood and will be ignored.", @@ -589,7 +732,7 @@ static int ps_config(oconfig_item_t *ci) { } } - return (0); + return 0; } static int ps_init(void) { @@ -611,13 +754,22 @@ static int ps_init(void) { ERROR("host_processor_sets failed: %s\n", mach_error_string(status)); pset_list = NULL; pset_list_len = 0; - return (-1); + return -1; } /* #endif HAVE_THREAD_INFO */ #elif KERNEL_LINUX pagesize_g = sysconf(_SC_PAGESIZE); DEBUG("pagesize_g = %li; CONFIG_HZ = %i;", pagesize_g, CONFIG_HZ); + +#if HAVE_LIBTASKSTATS + if (taskstats_handle == NULL) { + taskstats_handle = ts_create(); + if (taskstats_handle == NULL) { + WARNING("processes plugin: Creating taskstats handle failed."); + } + } +#endif /* #endif KERNEL_LINUX */ #elif HAVE_LIBKVM_GETPROCS && \ @@ -630,7 +782,7 @@ static int ps_init(void) { pagesize = getpagesize(); #endif /* HAVE_PROCINFO_H */ - return (0); + return 0; } /* int ps_init */ /* submit global state (e.g.: qty of zombies, running, etc..) */ @@ -700,7 +852,7 @@ static void ps_submit_proc_list(procstat_t *ps) { plugin_dispatch_values(&vl); if ((ps->io_rchar != -1) && (ps->io_wchar != -1)) { - sstrncpy(vl.type, "ps_disk_octets", sizeof(vl.type)); + sstrncpy(vl.type, "io_octets", sizeof(vl.type)); vl.values[0].derive = ps->io_rchar; vl.values[1].derive = ps->io_wchar; vl.values_len = 2; @@ -708,13 +860,36 @@ static void ps_submit_proc_list(procstat_t *ps) { } if ((ps->io_syscr != -1) && (ps->io_syscw != -1)) { - sstrncpy(vl.type, "ps_disk_ops", sizeof(vl.type)); + sstrncpy(vl.type, "io_ops", sizeof(vl.type)); vl.values[0].derive = ps->io_syscr; vl.values[1].derive = ps->io_syscw; vl.values_len = 2; plugin_dispatch_values(&vl); } + if ((ps->io_diskr != -1) && (ps->io_diskw != -1)) { + sstrncpy(vl.type, "disk_octets", sizeof(vl.type)); + vl.values[0].derive = ps->io_diskr; + vl.values[1].derive = ps->io_diskw; + vl.values_len = 2; + plugin_dispatch_values(&vl); + } + + if (ps->num_fd > 0) { + sstrncpy(vl.type, "file_handles", sizeof(vl.type)); + vl.values[0].gauge = ps->num_fd; + vl.values_len = 1; + plugin_dispatch_values(&vl); + } + + if (ps->num_maps > 0) { + sstrncpy(vl.type, "file_handles", sizeof(vl.type)); + sstrncpy(vl.type_instance, "mapped", sizeof(vl.type_instance)); + vl.values[0].gauge = ps->num_maps; + vl.values_len = 1; + plugin_dispatch_values(&vl); + } + if ((ps->cswitch_vol != -1) && (ps->cswitch_invol != -1)) { sstrncpy(vl.type, "contextswitch", sizeof(vl.type)); sstrncpy(vl.type_instance, "voluntary", sizeof(vl.type_instance)); @@ -729,19 +904,51 @@ static void ps_submit_proc_list(procstat_t *ps) { plugin_dispatch_values(&vl); } - DEBUG("name = %s; num_proc = %lu; num_lwp = %lu; " - "vmem_size = %lu; vmem_rss = %lu; vmem_data = %lu; " - "vmem_code = %lu; " - "vmem_minflt_counter = %" PRIi64 "; vmem_majflt_counter = %" PRIi64 "; " - "cpu_user_counter = %" PRIi64 "; cpu_system_counter = %" PRIi64 "; " - "io_rchar = %" PRIi64 "; io_wchar = %" PRIi64 "; " - "io_syscr = %" PRIi64 "; io_syscw = %" PRIi64 "; " - "cswitch_vol = %" PRIi64 "; cswitch_invol = %" PRIi64 ";", - ps->name, ps->num_proc, ps->num_lwp, ps->vmem_size, ps->vmem_rss, - ps->vmem_data, ps->vmem_code, ps->vmem_minflt_counter, - ps->vmem_majflt_counter, ps->cpu_user_counter, ps->cpu_system_counter, - ps->io_rchar, ps->io_wchar, ps->io_syscr, ps->io_syscw, ps->cswitch_vol, - ps->cswitch_invol); + /* The ps->delay_* metrics are in nanoseconds per second. Convert to seconds + * per second. */ + gauge_t const delay_factor = 1000000000.0; + + struct { + char *type_instance; + gauge_t rate_ns; + } delay_metrics[] = { + {"delay-cpu", ps->delay_cpu}, + {"delay-blkio", ps->delay_blkio}, + {"delay-swapin", ps->delay_swapin}, + {"delay-freepages", ps->delay_freepages}, + }; + for (size_t i = 0; i < STATIC_ARRAY_SIZE(delay_metrics); i++) { + if (isnan(delay_metrics[i].rate_ns)) { + continue; + } + sstrncpy(vl.type, "delay_rate", sizeof(vl.type)); + sstrncpy(vl.type_instance, delay_metrics[i].type_instance, + sizeof(vl.type_instance)); + vl.values[0].gauge = delay_metrics[i].rate_ns * delay_factor; + vl.values_len = 1; + plugin_dispatch_values(&vl); + } + + DEBUG( + "name = %s; num_proc = %lu; num_lwp = %lu; num_fd = %lu; num_maps = %lu; " + "vmem_size = %lu; vmem_rss = %lu; vmem_data = %lu; " + "vmem_code = %lu; " + "vmem_minflt_counter = %" PRIi64 "; vmem_majflt_counter = %" PRIi64 "; " + "cpu_user_counter = %" PRIi64 "; cpu_system_counter = %" PRIi64 "; " + "io_rchar = %" PRIi64 "; io_wchar = %" PRIi64 "; " + "io_syscr = %" PRIi64 "; io_syscw = %" PRIi64 "; " + "io_diskr = %" PRIi64 "; io_diskw = %" PRIi64 "; " + "cswitch_vol = %" PRIi64 "; cswitch_invol = %" PRIi64 "; " + "delay_cpu = %g; delay_blkio = %g; " + "delay_swapin = %g; delay_freepages = %g;", + ps->name, ps->num_proc, ps->num_lwp, ps->num_fd, ps->num_maps, + ps->vmem_size, ps->vmem_rss, ps->vmem_data, ps->vmem_code, + ps->vmem_minflt_counter, ps->vmem_majflt_counter, ps->cpu_user_counter, + ps->cpu_system_counter, ps->io_rchar, ps->io_wchar, ps->io_syscr, + ps->io_syscw, ps->io_diskr, ps->io_diskw, ps->cswitch_vol, + ps->cswitch_invol, ps->delay_cpu, ps->delay_blkio, ps->delay_swapin, + ps->delay_freepages); + } /* void ps_submit_proc_list */ #if KERNEL_LINUX || KERNEL_SOLARIS @@ -773,11 +980,11 @@ static int ps_read_tasks_status(process_entry_t *ps) { char *fields[8]; int numfields; - ssnprintf(dirname, sizeof(dirname), "/proc/%li/task", ps->id); + snprintf(dirname, sizeof(dirname), "/proc/%li/task", ps->id); if ((dh = opendir(dirname)) == NULL) { DEBUG("Failed to open directory `%s'", dirname); - return (-1); + return -1; } while ((ent = readdir(dh)) != NULL) { @@ -788,8 +995,12 @@ static int ps_read_tasks_status(process_entry_t *ps) { tpid = ent->d_name; - ssnprintf(filename, sizeof(filename), "/proc/%li/task/%s/status", ps->id, - tpid); + if (snprintf(filename, sizeof(filename), "/proc/%li/task/%s/status", ps->id, + tpid) >= sizeof(filename)) { + DEBUG("Filename too long: `%s'", filename); + continue; + } + if ((fh = fopen(filename, "r")) == NULL) { DEBUG("Failed to open file `%s'", filename); continue; @@ -821,9 +1032,7 @@ static int ps_read_tasks_status(process_entry_t *ps) { } /* while (fgets) */ if (fclose(fh)) { - char errbuf[1024]; - WARNING("processes: fclose: %s", - sstrerror(errno, errbuf, sizeof(errbuf))); + WARNING("processes: fclose: %s", STRERRNO); } } closedir(dh); @@ -831,7 +1040,7 @@ static int ps_read_tasks_status(process_entry_t *ps) { ps->cswitch_vol = cswitch_vol; ps->cswitch_invol = cswitch_invol; - return (0); + return 0; } /* int *ps_read_tasks_status */ /* Read data from /proc/pid/status */ @@ -846,9 +1055,9 @@ static int ps_read_status(long pid, process_entry_t *ps) { char *fields[8]; int numfields; - ssnprintf(filename, sizeof(filename), "/proc/%li/status", pid); + snprintf(filename, sizeof(filename), "/proc/%li/status", pid); if ((fh = fopen(filename, "r")) == NULL) - return (-1); + return -1; while (fgets(buffer, sizeof(buffer), fh) != NULL) { unsigned long tmp; @@ -879,8 +1088,7 @@ static int ps_read_status(long pid, process_entry_t *ps) { } /* while (fgets) */ if (fclose(fh)) { - char errbuf[1024]; - WARNING("processes: fclose: %s", sstrerror(errno, errbuf, sizeof(errbuf))); + WARNING("processes: fclose: %s", STRERRNO); } ps->vmem_data = data * 1024; @@ -888,7 +1096,7 @@ static int ps_read_status(long pid, process_entry_t *ps) { if (threads != 0) ps->num_lwp = threads; - return (0); + return 0; } /* int *ps_read_status */ static int ps_read_io(process_entry_t *ps) { @@ -899,9 +1107,11 @@ static int ps_read_io(process_entry_t *ps) { char *fields[8]; int numfields; - ssnprintf(filename, sizeof(filename), "/proc/%li/io", ps->id); - if ((fh = fopen(filename, "r")) == NULL) - return (-1); + snprintf(filename, sizeof(filename), "/proc/%li/io", ps->id); + if ((fh = fopen(filename, "r")) == NULL) { + DEBUG("ps_read_io: Failed to open file `%s'", filename); + return -1; + } while (fgets(buffer, sizeof(buffer), fh) != NULL) { derive_t *val = NULL; @@ -916,6 +1126,10 @@ static int ps_read_io(process_entry_t *ps) { val = &(ps->io_syscr); else if (strncasecmp(buffer, "syscw:", 6) == 0) val = &(ps->io_syscw); + else if (strncasecmp(buffer, "read_bytes:", 11) == 0) + val = &(ps->io_diskr); + else if (strncasecmp(buffer, "write_bytes:", 12) == 0) + val = &(ps->io_diskw); else continue; @@ -934,37 +1148,148 @@ static int ps_read_io(process_entry_t *ps) { } /* while (fgets) */ if (fclose(fh)) { - char errbuf[1024]; - WARNING("processes: fclose: %s", sstrerror(errno, errbuf, sizeof(errbuf))); + WARNING("processes: fclose: %s", STRERRNO); } - return (0); + return 0; } /* int ps_read_io (...) */ +static int ps_count_maps(pid_t pid) { + FILE *fh; + char buffer[1024]; + char filename[64]; + int count = 0; + + snprintf(filename, sizeof(filename), "/proc/%d/maps", pid); + if ((fh = fopen(filename, "r")) == NULL) { + DEBUG("ps_count_maps: Failed to open file `%s'", filename); + return -1; + } + + while (fgets(buffer, sizeof(buffer), fh) != NULL) { + if (strchr(buffer, '\n')) { + count++; + } + } /* while (fgets) */ + + if (fclose(fh)) { + WARNING("processes: fclose: %s", STRERRNO); + } + return count; +} /* int ps_count_maps (...) */ + +static int ps_count_fd(int pid) { + char dirname[64]; + DIR *dh; + struct dirent *ent; + int count = 0; + + snprintf(dirname, sizeof(dirname), "/proc/%i/fd", pid); + + if ((dh = opendir(dirname)) == NULL) { + DEBUG("Failed to open directory `%s'", dirname); + return -1; + } + while ((ent = readdir(dh)) != NULL) { + if (!isdigit((int)ent->d_name[0])) + continue; + else + count++; + } + closedir(dh); + + return (count >= 1) ? count : 1; +} /* int ps_count_fd (pid) */ + +#if HAVE_LIBTASKSTATS +static int ps_delay(process_entry_t *ps) { + if (taskstats_handle == NULL) { + return ENOTCONN; + } + + int status = ts_delay_by_tgid(taskstats_handle, (uint32_t)ps->id, &ps->delay); + if (status == EPERM) { + static c_complain_t c; +#if defined(HAVE_SYS_CAPABILITY_H) && defined(CAP_NET_ADMIN) + if (check_capability(CAP_NET_ADMIN) != 0) { + if (getuid() == 0) { + c_complain( + LOG_ERR, &c, + "processes plugin: Reading Delay Accounting metric failed: %s. " + "collectd is running as root, but missing the CAP_NET_ADMIN " + "capability. The most common cause for this is that the init " + "system is dropping capabilities.", + STRERROR(status)); + } else { + c_complain( + LOG_ERR, &c, + "processes plugin: Reading Delay Accounting metric failed: %s. " + "collectd is not running as root and missing the CAP_NET_ADMIN " + "capability. Either run collectd as root or grant it the " + "CAP_NET_ADMIN capability using \"setcap cap_net_admin=ep " PREFIX + "/sbin/collectd\".", + STRERROR(status)); + } + } else { + ERROR("processes plugin: ts_delay_by_tgid failed: %s. The CAP_NET_ADMIN " + "capability is available (I checked), so this error is utterly " + "unexpected.", + STRERROR(status)); + } +#else + c_complain(LOG_ERR, &c, + "processes plugin: Reading Delay Accounting metric failed: %s. " + "Reading Delay Accounting metrics requires root privileges.", + STRERROR(status)); +#endif + return status; + } else if (status != 0) { + ERROR("processes plugin: ts_delay_by_tgid failed: %s", STRERROR(status)); + return status; + } + + return 0; +} +#endif + static void ps_fill_details(const procstat_t *ps, process_entry_t *entry) { - if (entry->has_io == 0 && ps_read_io(entry) != 0) { - /* no io data */ - entry->io_rchar = -1; - entry->io_wchar = -1; - entry->io_syscr = -1; - entry->io_syscw = -1; - - DEBUG("ps_read_io: not get io data for pid %li", entry->id); + if (entry->has_io == 0) { + ps_read_io(entry); + entry->has_io = 1; } - entry->has_io = 1; - if (report_ctx_switch) { - if (entry->has_cswitch == 0 && ps_read_tasks_status(entry) != 0) { - entry->cswitch_vol = -1; - entry->cswitch_invol = -1; + if (ps->report_ctx_switch) { + if (entry->has_cswitch == 0) { + ps_read_tasks_status(entry); + entry->has_cswitch = 1; + } + } + + if (ps->report_maps_num) { + int num_maps; + if (entry->has_maps == 0 && (num_maps = ps_count_maps(entry->id)) > 0) { + entry->num_maps = num_maps; + } + entry->has_maps = 1; + } - DEBUG("ps_read_tasks_status: not get context " - "switch data for pid %li", - entry->id); + if (ps->report_fd_num) { + int num_fd; + if (entry->has_fd == 0 && (num_fd = ps_count_fd(entry->id)) > 0) { + entry->num_fd = num_fd; } - entry->has_cswitch = 1; + entry->has_fd = 1; } + +#if HAVE_LIBTASKSTATS + if (ps->report_delay && !entry->has_delay) { + if (ps_delay(entry) == 0) { + entry->has_delay = 1; + } + } +#endif } /* void ps_fill_details (...) */ +/* ps_read_process reads process counters on Linux. */ static int ps_read_process(long pid, process_entry_t *ps, char *state) { char filename[64]; char buffer[1024]; @@ -987,11 +1312,11 @@ static int ps_read_process(long pid, process_entry_t *ps, char *state) { ssize_t status; - ssnprintf(filename, sizeof(filename), "/proc/%li/stat", pid); + snprintf(filename, sizeof(filename), "/proc/%li/stat", pid); status = read_file_contents(filename, buffer, sizeof(buffer) - 1); if (status <= 0) - return (-1); + return -1; buffer_len = (size_t)status; buffer[buffer_len] = 0; @@ -1011,9 +1336,10 @@ static int ps_read_process(long pid, process_entry_t *ps, char *state) { /* Either '(' or ')' is not found or they are in the wrong order. * Anyway, something weird that shouldn't happen ever. */ if (name_start_pos >= name_end_pos) { - ERROR("processes plugin: name_start_pos = %zu >= name_end_pos = %zu", + ERROR("processes plugin: name_start_pos = %" PRIsz + " >= name_end_pos = %" PRIsz, name_start_pos, name_end_pos); - return (-1); + return -1; } name_len = (name_end_pos - name_start_pos) - 1; @@ -1023,7 +1349,7 @@ static int ps_read_process(long pid, process_entry_t *ps, char *state) { sstrncpy(ps->name, &buffer[name_start_pos + 1], name_len + 1); if ((buffer_len - name_end_pos) < 2) - return (-1); + return -1; buffer_ptr = &buffer[name_end_pos + 2]; fields_len = strsplit(buffer_ptr, fields, STATIC_ARRAY_SIZE(fields)); @@ -1031,7 +1357,7 @@ static int ps_read_process(long pid, process_entry_t *ps, char *state) { DEBUG("processes plugin: ps_read_process (pid = %li):" " `%s' has only %i fields..", pid, filename, fields_len); - return (-1); + return -1; } *state = fields[0][0]; @@ -1057,7 +1383,7 @@ static int ps_read_process(long pid, process_entry_t *ps, char *state) { DEBUG("processes plugin: This is only a zombie: pid = %li; " "name = %s;", pid, ps->name); - return (0); + return 0; } cpu_user_counter = atoll(fields[11]); @@ -1086,8 +1412,19 @@ static int ps_read_process(long pid, process_entry_t *ps, char *state) { ps->vmem_rss = (unsigned long)vmem_rss; ps->stack_size = (unsigned long)stack_size; + /* no data by default. May be filled by ps_fill_details () */ + ps->io_rchar = -1; + ps->io_wchar = -1; + ps->io_syscr = -1; + ps->io_syscw = -1; + ps->io_diskr = -1; + ps->io_diskw = -1; + + ps->cswitch_vol = -1; + ps->cswitch_invol = -1; + /* success */ - return (0); + return 0; } /* int ps_read_process (...) */ static char *ps_get_cmdline(long pid, char *name, char *buf, size_t buf_len) { @@ -1102,17 +1439,15 @@ static char *ps_get_cmdline(long pid, char *name, char *buf, size_t buf_len) { if ((pid < 1) || (NULL == buf) || (buf_len < 2)) return NULL; - ssnprintf(file, sizeof(file), "/proc/%li/cmdline", pid); + snprintf(file, sizeof(file), "/proc/%li/cmdline", pid); errno = 0; fd = open(file, O_RDONLY); if (fd < 0) { - char errbuf[4096]; /* ENOENT means the process exited while we were handling it. * Don't complain about this, it only fills the logs. */ if (errno != ENOENT) - WARNING("processes plugin: Failed to open `%s': %s.", file, - sstrerror(errno, errbuf, sizeof(errbuf))); + WARNING("processes plugin: Failed to open `%s': %s.", file, STRERRNO); return NULL; } @@ -1127,13 +1462,12 @@ static char *ps_get_cmdline(long pid, char *name, char *buf, size_t buf_len) { status = read(fd, (void *)buf_ptr, len); if (status < 0) { - char errbuf[1024]; if ((EAGAIN == errno) || (EINTR == errno)) continue; WARNING("processes plugin: Failed to read from `%s': %s.", file, - sstrerror(errno, errbuf, sizeof(errbuf))); + STRERRNO); close(fd); return NULL; } @@ -1157,7 +1491,7 @@ static char *ps_get_cmdline(long pid, char *name, char *buf, size_t buf_len) { if (NULL == name) return NULL; - ssnprintf(buf, buf_len, "[%s]", name); + snprintf(buf, buf_len, "[%s]", name); return buf; } @@ -1191,10 +1525,8 @@ static int read_fork_rate(void) { proc_stat = fopen("/proc/stat", "r"); if (proc_stat == NULL) { - char errbuf[1024]; - ERROR("processes plugin: fopen (/proc/stat) failed: %s", - sstrerror(errno, errbuf, sizeof(errbuf))); - return (-1); + ERROR("processes plugin: fopen (/proc/stat) failed: %s", STRERRNO); + return -1; } while (fgets(buffer, sizeof(buffer), proc_stat) != NULL) { @@ -1218,10 +1550,10 @@ static int read_fork_rate(void) { fclose(proc_stat); if (!value_valid) - return (-1); + return -1; ps_submit_fork_rate(value.derive); - return (0); + return 0; } #endif /*KERNEL_LINUX */ @@ -1239,15 +1571,15 @@ static char *ps_get_cmdline(long pid, if ((status < 0) || (((size_t)status) != sizeof(info))) { ERROR("processes plugin: Unexpected return value " "while reading \"%s\": " - "Returned %zd but expected %zu.", + "Returned %zd but expected %" PRIsz ".", path, status, buffer_size); - return (NULL); + return NULL; } info.pr_psargs[sizeof(info.pr_psargs) - 1] = 0; sstrncpy(buffer, info.pr_psargs, buffer_size); - return (buffer); + return buffer; } /* }}} int ps_get_cmdline */ /* @@ -1292,7 +1624,7 @@ static int ps_read_process(long pid, process_entry_t *ps, char *state) { sfree(myStatus); sfree(myInfo); sfree(myUsage); - return (0); + return 0; } else { ps->num_proc = 1; ps->num_lwp = myInfo->pr_nlwp; @@ -1322,6 +1654,14 @@ static int ps_read_process(long pid, process_entry_t *ps, char *state) { ps->stack_size = myStatus->pr_stksize; /* + * TODO: File descriptor count for Solaris + */ + ps->num_fd = 0; + + /* Number of memory mappings */ + ps->num_maps = 0; + + /* * Calculating input/ouput chars * Formula used is total chars / total blocks => chars/block * then convert input/output blocks to chars @@ -1335,6 +1675,8 @@ static int ps_read_process(long pid, process_entry_t *ps, char *state) { ps->io_wchar = myUsage->pr_oublk * chars_per_block; ps->io_syscr = myUsage->pr_sysc; ps->io_syscw = myUsage->pr_sysc; + ps->io_diskr = -1; + ps->io_diskw = -1; /* * TODO: context switch counters for Solaris @@ -1364,7 +1706,7 @@ static int ps_read_process(long pid, process_entry_t *ps, char *state) { sfree(myInfo); sfree(myUsage); - return (0); + return 0; } /* @@ -1377,7 +1719,7 @@ static int read_fork_rate(void) { derive_t result = 0; if (kc == NULL) - return (-1); + return -1; for (kstat_t *ksp_chain = kc->kc_chain; ksp_chain != NULL; ksp_chain = ksp_chain->ks_next) { @@ -1395,7 +1737,7 @@ static int read_fork_rate(void) { } ps_submit_fork_rate(result); - return (0); + return 0; } #endif /* KERNEL_SOLARIS */ @@ -1412,12 +1754,12 @@ static int mach_get_task_name(task_t t, int *pid, char *name, mib[2] = KERN_PROC_PID; if (pid_for_task(t, pid) != KERN_SUCCESS) - return (-1); + return -1; mib[3] = *pid; kp_size = sizeof(kp); if (sysctl(mib, 4, &kp, &kp_size, NULL, 0) != 0) - return (-1); + return -1; if (name_max_len > (MAXCOMLEN + 1)) name_max_len = MAXCOMLEN + 1; @@ -1431,11 +1773,10 @@ static int mach_get_task_name(task_t t, int *pid, char *name, * `top' does it, because it is a lot of work and only used when * debugging. -octo */ - return (0); + return 0; } #endif /* HAVE_THREAD_INFO */ -/* ------- end of additional functions for KERNEL_LINUX/HAVE_THREAD_INFO ------- - */ +/* end of additional functions for KERNEL_LINUX/HAVE_THREAD_INFO */ /* do actual readings from kernel */ static int ps_read(void) { @@ -1544,6 +1885,19 @@ static int ps_read(void) { pse.vmem_data = 0; pse.vmem_code = 0; + pse.io_rchar = -1; + pse.io_wchar = -1; + pse.io_syscr = -1; + pse.io_syscw = -1; + pse.io_diskr = -1; + pse.io_diskw = -1; + + /* File descriptor count not implemented */ + pse.num_fd = 0; + + /* Number of memory mappings */ + pse.num_maps = 0; + pse.vmem_minflt_counter = task_events_info.cow_faults; pse.vmem_majflt_counter = task_events_info.faults; @@ -1682,9 +2036,8 @@ static int ps_read(void) { ps_list_reset(); if ((proc = opendir("/proc")) == NULL) { - char errbuf[1024]; - ERROR("Cannot open `/proc': %s", sstrerror(errno, errbuf, sizeof(errbuf))); - return (-1); + ERROR("Cannot open `/proc': %s", STRERRNO); + return -1; } while ((ent = readdir(proc)) != NULL) { @@ -1766,7 +2119,7 @@ static int ps_read(void) { kd = kvm_openfiles(NULL, "/dev/null", NULL, 0, errbuf); if (kd == NULL) { ERROR("processes plugin: Cannot open kvm interface: %s", errbuf); - return (0); + return 0; } /* Get the list of processes. */ @@ -1775,7 +2128,7 @@ static int ps_read(void) { ERROR("processes plugin: Cannot get kvm processes list: %s", kvm_geterr(kd)); kvm_close(kd); - return (0); + return 0; } /* Iterate through the processes in kinfo_proc */ @@ -1843,6 +2196,14 @@ static int ps_read(void) { pse.io_wchar = -1; pse.io_syscr = -1; pse.io_syscw = -1; + pse.io_diskr = -1; + pse.io_diskw = -1; + + /* file descriptor count not implemented */ + pse.num_fd = 0; + + /* Number of memory mappings */ + pse.num_maps = 0; /* context switch counters not implemented */ pse.cswitch_vol = -1; @@ -1913,7 +2274,7 @@ static int ps_read(void) { kd = kvm_openfiles(NULL, NULL, NULL, KVM_NO_FILES, errbuf); if (kd == NULL) { ERROR("processes plugin: Cannot open kvm interface: %s", errbuf); - return (0); + return 0; } /* Get the list of processes. */ @@ -1922,7 +2283,7 @@ static int ps_read(void) { ERROR("processes plugin: Cannot get kvm processes list: %s", kvm_geterr(kd)); kvm_close(kd); - return (0); + return 0; } /* Iterate through the processes in kinfo_proc */ @@ -1979,6 +2340,14 @@ static int ps_read(void) { pse.io_wchar = -1; pse.io_syscr = -1; pse.io_syscw = -1; + pse.io_diskr = -1; + pse.io_diskw = -1; + + /* file descriptor count not implemented */ + pse.num_fd = 0; + + /* Number of memory mappings */ + pse.num_maps = 0; /* context switch counters not implemented */ pse.cswitch_vol = -1; @@ -2130,7 +2499,7 @@ static int ps_read(void) { pse.vmem_size = procentry[i].pi_tsize + procentry[i].pi_dvm * pagesize; pse.vmem_rss = (procentry[i].pi_drss + procentry[i].pi_trss) * pagesize; - /* Not supported */ + /* Not supported/implemented */ pse.vmem_data = 0; pse.vmem_code = 0; pse.stack_size = 0; @@ -2139,6 +2508,11 @@ static int ps_read(void) { pse.io_wchar = -1; pse.io_syscr = -1; pse.io_syscw = -1; + pse.io_diskr = -1; + pse.io_diskw = -1; + + pse.num_fd = 0; + pse.num_maps = 0; pse.cswitch_vol = -1; pse.cswitch_invol = -1; @@ -2188,7 +2562,7 @@ static int ps_read(void) { proc = opendir("/proc"); if (proc == NULL) - return (-1); + return -1; while ((ent = readdir(proc)) != NULL) { long pid; @@ -2260,7 +2634,7 @@ static int ps_read(void) { want_init = 0; - return (0); + return 0; } /* int ps_read */ void module_register(void) {