X-Git-Url: https://git.octo.it/?a=blobdiff_plain;f=src%2Fprocesses.c;h=c77859d9e7d9f6953abe3fb87c3e16aebdd44357;hb=111f4f08030989bf4a2282905b97f45a8cab1c8d;hp=8a3df644d00e71be94f343faaa65c3b29bd9aafb;hpb=37e18082cfb22491138e282074c5267df48dd8de;p=collectd.git diff --git a/src/processes.c b/src/processes.c index 8a3df644..519d1360 100644 --- a/src/processes.c +++ b/src/processes.c @@ -1,12 +1,13 @@ /** * collectd - src/processes.c * Copyright (C) 2005 Lyonel Vincent - * Copyright (C) 2006-2008 Florian octo Forster + * Copyright (C) 2006-2010 Florian octo Forster * Copyright (C) 2008 Oleg King * Copyright (C) 2009 Sebastian Harl * Copyright (C) 2009 Andrés J. Díaz * Copyright (C) 2009 Manuel Sanmartin * Copyright (C) 2010 Clément Stenac + * Copyright (C) 2012 Cosmin Ioiart * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the @@ -24,12 +25,13 @@ * * Authors: * Lyonel Vincent - * Florian octo Forster + * Florian octo Forster * Oleg King * Sebastian Harl * Andrés J. Díaz * Manuel Sanmartin * Clément Stenac + * Cosmin Ioiart **/ #include "collectd.h" @@ -92,13 +94,13 @@ # endif /* #endif KERNEL_LINUX */ -#elif HAVE_LIBKVM_GETPROCS && HAVE_STRUCT_KINFO_PROC_FREEBSD +#elif HAVE_LIBKVM_GETPROCS && (HAVE_STRUCT_KINFO_PROC_FREEBSD || HAVE_STRUCT_KINFO_PROC_OPENBSD) # include # include # include # include # include -/* #endif HAVE_LIBKVM_GETPROCS && HAVE_STRUCT_KINFO_PROC_FREEBSD */ +/* #endif HAVE_LIBKVM_GETPROCS && (HAVE_STRUCT_KINFO_PROC_FREEBSD || HAVE_STRUCT_KINFO_PROC_OPENBSD) */ #elif HAVE_PROCINFO_H # include @@ -109,6 +111,26 @@ #define MAXARGLN 1024 /* #endif HAVE_PROCINFO_H */ +#elif KERNEL_SOLARIS +/* Hack: Avoid #error when building a 32-bit binary with + * _FILE_OFFSET_BITS=64. There is a reason for this #error, as one + * of the structures in uses an off_t, but that + * isn't relevant to our usage of procfs. */ +#if !defined(_LP64) && _FILE_OFFSET_BITS == 64 +# define SAVE_FOB_64 +# undef _FILE_OFFSET_BITS +#endif + +# include + +#ifdef SAVE_FOB_64 +# define _FILE_OFFSET_BITS 64 +# undef SAVE_FOB_64 +#endif + +# include +/* #endif KERNEL_SOLARIS */ + #else # error "No applicable input method." #endif @@ -117,16 +139,17 @@ # include #endif -#ifndef ARG_MAX -# define ARG_MAX 4096 +#if HAVE_KSTAT_H +# include #endif -static const char *config_keys[] = -{ - "Process", - "ProcessMatch" -}; -static int config_keys_num = STATIC_ARRAY_SIZE (config_keys); +#ifndef CMDLINE_BUFFER_SIZE +# if defined(ARG_MAX) && (ARG_MAX < 4096) +# define CMDLINE_BUFFER_SIZE ARG_MAX +# else +# define CMDLINE_BUFFER_SIZE 4096 +# endif +#endif typedef struct procstat_entry_s { @@ -143,13 +166,13 @@ typedef struct procstat_entry_s unsigned long vmem_minflt; unsigned long vmem_majflt; - unsigned long vmem_minflt_counter; - unsigned long vmem_majflt_counter; + derive_t vmem_minflt_counter; + derive_t vmem_majflt_counter; unsigned long cpu_user; unsigned long cpu_system; - unsigned long cpu_user_counter; - unsigned long cpu_system_counter; + derive_t cpu_user_counter; + derive_t cpu_system_counter; /* io data */ derive_t io_rchar; @@ -157,6 +180,9 @@ typedef struct procstat_entry_s derive_t io_syscr; derive_t io_syscw; + derive_t cswitch_vol; + derive_t cswitch_invol; + struct procstat_entry_s *next; } procstat_entry_t; @@ -176,11 +202,11 @@ typedef struct procstat unsigned long vmem_code; unsigned long stack_size; - unsigned long vmem_minflt_counter; - unsigned long vmem_majflt_counter; + derive_t vmem_minflt_counter; + derive_t vmem_majflt_counter; - unsigned long cpu_user_counter; - unsigned long cpu_system_counter; + derive_t cpu_user_counter; + derive_t cpu_system_counter; /* io data */ derive_t io_rchar; @@ -188,12 +214,17 @@ typedef struct procstat derive_t io_syscr; derive_t io_syscw; + derive_t cswitch_vol; + derive_t cswitch_invol; + struct procstat *next; struct procstat_entry_s *instances; } procstat_t; static procstat_t *list_head_g = NULL; +static _Bool report_ctx_switch = 0; + #if HAVE_THREAD_INFO static mach_port_t port_host_self; static mach_port_t port_task_self; @@ -206,9 +237,9 @@ static mach_msg_type_number_t pset_list_len; static long pagesize_g; /* #endif KERNEL_LINUX */ -#elif HAVE_LIBKVM_GETPROCS && HAVE_STRUCT_KINFO_PROC_FREEBSD -/* no global variables */ -/* #endif HAVE_LIBKVM_GETPROCS && HAVE_STRUCT_KINFO_PROC_FREEBSD */ +#elif HAVE_LIBKVM_GETPROCS && (HAVE_STRUCT_KINFO_PROC_FREEBSD || HAVE_STRUCT_KINFO_PROC_OPENBSD) +static int pagesize; +/* #endif HAVE_LIBKVM_GETPROCS && (HAVE_STRUCT_KINFO_PROC_FREEBSD || HAVE_STRUCT_KINFO_PROC_OPENBSD) */ #elif HAVE_PROCINFO_H static struct procentry64 procentry[MAXPROCENTRY]; @@ -219,12 +250,12 @@ static int pagesize; int getprocs64 (void *procsinfo, int sizproc, void *fdsinfo, int sizfd, pid_t *index, int count); int getthrds64( pid_t, void *, int, tid64_t *, int ); #endif -int getargs (struct procentry64 *processBuffer, int bufferLen, char *argsBuffer, int argsLen); +int getargs (void *processBuffer, int bufferLen, char *argsBuffer, int argsLen); #endif /* HAVE_PROCINFO_H */ /* 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 */ + * 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) { procstat_t *new; @@ -375,6 +406,8 @@ static void ps_list_add (const char *name, const char *cmdline, procstat_entry_t pse->io_wchar = entry->io_wchar; pse->io_syscr = entry->io_syscr; pse->io_syscw = entry->io_syscw; + pse->cswitch_vol = entry->cswitch_vol; + pse->cswitch_invol = entry->cswitch_invol; ps->num_proc += pse->num_proc; ps->num_lwp += pse->num_lwp; @@ -389,6 +422,9 @@ static void ps_list_add (const char *name, const char *cmdline, procstat_entry_t ps->io_syscr += ((pse->io_syscr == -1)?0:pse->io_syscr); ps->io_syscw += ((pse->io_syscw == -1)?0:pse->io_syscw); + ps->cswitch_vol += ((pse->cswitch_vol == -1)?0:pse->cswitch_vol); + ps->cswitch_invol += ((pse->cswitch_invol == -1)?0:pse->cswitch_invol); + if ((entry->vmem_minflt_counter == 0) && (entry->vmem_majflt_counter == 0)) { @@ -485,6 +521,8 @@ static void ps_list_reset (void) ps->io_wchar = -1; ps->io_syscr = -1; ps->io_syscw = -1; + ps->cswitch_vol = -1; + ps->cswitch_invol = -1; pse_prev = NULL; pse = ps->instances; @@ -520,42 +558,65 @@ static void ps_list_reset (void) } /* put all pre-defined 'Process' names from config to list_head_g tree */ -static int ps_config (const char *key, const char *value) +static int ps_config (oconfig_item_t *ci) { - if (strcasecmp (key, "Process") == 0) - { - ps_list_register (value, NULL); - } - else if (strcasecmp (key, "ProcessMatch") == 0) - { - char *new_val; - char *fields[3]; - int fields_num; + int i; - new_val = strdup (value); - if (new_val == NULL) { - ERROR ("processes plugin: strdup failed when processing " - "`ProcessMatch %s'.", value); - return (1); + for (i = 0; i < ci->children_num; ++i) { + oconfig_item_t *c = ci->children + i; + + if (strcasecmp (c->key, "Process") == 0) + { + if ((c->values_num != 1) + || (OCONFIG_TYPE_STRING != c->values[0].type)) { + ERROR ("processes plugin: `Process' expects exactly " + "one string argument (got %i).", + c->values_num); + 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); + } + + ps_list_register (c->values[0].value.string, NULL); } + 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)) + { + ERROR ("processes plugin: `ProcessMatch' needs exactly " + "two string arguments (got %i).", + c->values_num); + continue; + } - fields_num = strsplit (new_val, fields, - STATIC_ARRAY_SIZE (fields)); - if (fields_num != 2) + 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_list_register (c->values[0].value.string, + c->values[1].value.string); + } + else if (strcasecmp (c->key, "CollectContextSwitch") == 0) { - ERROR ("processes plugin: `ProcessMatch' needs exactly " - "two string arguments."); - sfree (new_val); - return (1); + cf_util_get_boolean (c, &report_ctx_switch); + } + else + { + ERROR ("processes plugin: The `%s' configuration option is not " + "understood and will be ignored.", c->key); + continue; } - ps_list_register (fields[0], fields[1]); - sfree (new_val); - } - else - { - ERROR ("processes plugin: The `%s' configuration option is not " - "understood and will be ignored.", key); - return (-1); } return (0); @@ -596,9 +657,9 @@ static int ps_init (void) pagesize_g, CONFIG_HZ); /* #endif KERNEL_LINUX */ -#elif HAVE_LIBKVM_GETPROCS && HAVE_STRUCT_KINFO_PROC_FREEBSD -/* no initialization */ -/* #endif HAVE_LIBKVM_GETPROCS && HAVE_STRUCT_KINFO_PROC_FREEBSD */ +#elif HAVE_LIBKVM_GETPROCS && (HAVE_STRUCT_KINFO_PROC_FREEBSD || HAVE_STRUCT_KINFO_PROC_OPENBSD) + pagesize = getpagesize(); +/* #endif HAVE_LIBKVM_GETPROCS && (HAVE_STRUCT_KINFO_PROC_FREEBSD || HAVE_STRUCT_KINFO_PROC_OPENBSD) */ #elif HAVE_PROCINFO_H pagesize = getpagesize(); @@ -664,8 +725,8 @@ static void ps_submit_proc_list (procstat_t *ps) plugin_dispatch_values (&vl); sstrncpy (vl.type, "ps_cputime", sizeof (vl.type)); - vl.values[0].counter = ps->cpu_user_counter; - vl.values[1].counter = ps->cpu_system_counter; + vl.values[0].derive = ps->cpu_user_counter; + vl.values[1].derive = ps->cpu_system_counter; vl.values_len = 2; plugin_dispatch_values (&vl); @@ -676,8 +737,8 @@ static void ps_submit_proc_list (procstat_t *ps) plugin_dispatch_values (&vl); sstrncpy (vl.type, "ps_pagefaults", sizeof (vl.type)); - vl.values[0].counter = ps->vmem_minflt_counter; - vl.values[1].counter = ps->vmem_majflt_counter; + vl.values[0].derive = ps->vmem_minflt_counter; + vl.values[1].derive = ps->vmem_majflt_counter; vl.values_len = 2; plugin_dispatch_values (&vl); @@ -699,23 +760,143 @@ static void ps_submit_proc_list (procstat_t *ps) plugin_dispatch_values (&vl); } + if ( report_ctx_switch ) + { + sstrncpy (vl.type, "contextswitch", sizeof (vl.type)); + sstrncpy (vl.type_instance, "voluntary", sizeof (vl.type_instance)); + vl.values[0].derive = ps->cswitch_vol; + vl.values_len = 1; + plugin_dispatch_values (&vl); + + sstrncpy (vl.type, "contextswitch", sizeof (vl.type)); + sstrncpy (vl.type_instance, "involuntary", sizeof (vl.type_instance)); + vl.values[0].derive = ps->cswitch_invol; + vl.values_len = 1; + plugin_dispatch_values (&vl); + } + DEBUG ("name = %s; num_proc = %lu; num_lwp = %lu; " - "vmem_size = %lu; vmem_rss = %lu; vmem_data = %lu; " + "vmem_size = %lu; vmem_rss = %lu; vmem_data = %lu; " "vmem_code = %lu; " - "vmem_minflt_counter = %lu; vmem_majflt_counter = %lu; " - "cpu_user_counter = %lu; cpu_system_counter = %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_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->io_rchar, ps->io_wchar, ps->io_syscr, ps->io_syscw, + ps->cswitch_vol, ps->cswitch_invol); } /* void ps_submit_proc_list */ +#if KERNEL_LINUX || KERNEL_SOLARIS +static void ps_submit_fork_rate (derive_t value) +{ + value_t values[1]; + value_list_t vl = VALUE_LIST_INIT; + + values[0].derive = value; + + vl.values = values; + vl.values_len = 1; + sstrncpy(vl.host, hostname_g, sizeof (vl.host)); + sstrncpy(vl.plugin, "processes", sizeof (vl.plugin)); + sstrncpy(vl.plugin_instance, "", sizeof (vl.plugin_instance)); + sstrncpy(vl.type, "fork_rate", sizeof (vl.type)); + sstrncpy(vl.type_instance, "", sizeof (vl.type_instance)); + + plugin_dispatch_values(&vl); +} +#endif /* KERNEL_LINUX || KERNEL_SOLARIS*/ + /* ------- additional functions for KERNEL_LINUX/HAVE_THREAD_INFO ------- */ #if KERNEL_LINUX +static procstat_t *ps_read_tasks_status (int pid, procstat_t *ps) +{ + char dirname[64]; + DIR *dh; + char filename[64]; + FILE *fh; + struct dirent *ent; + derive_t cswitch_vol = 0; + derive_t cswitch_invol = 0; + char buffer[1024]; + char *fields[8]; + int numfields; + + ssnprintf (dirname, sizeof (dirname), "/proc/%i/task", pid); + + if ((dh = opendir (dirname)) == NULL) + { + DEBUG ("Failed to open directory `%s'", dirname); + return (NULL); + } + + while ((ent = readdir (dh)) != NULL) + { + char *tpid; + + if (!isdigit ((int) ent->d_name[0])) + continue; + + tpid = ent->d_name; + + ssnprintf (filename, sizeof (filename), "/proc/%i/task/%s/status", pid, tpid); + if ((fh = fopen (filename, "r")) == NULL) + { + DEBUG ("Failed to open file `%s'", filename); + continue; + } + + while (fgets (buffer, sizeof(buffer), fh) != NULL) + { + derive_t tmp; + char *endptr; + + if (strncmp (buffer, "voluntary_ctxt_switches", 23) != 0 + && strncmp (buffer, "nonvoluntary_ctxt_switches", 26) != 0) + continue; + + numfields = strsplit (buffer, fields, + STATIC_ARRAY_SIZE (fields)); + + if (numfields < 2) + continue; + + errno = 0; + endptr = NULL; + tmp = (derive_t) strtoll (fields[1], &endptr, /* base = */ 10); + if ((errno == 0) && (endptr != fields[1])) + { + if (strncmp (buffer, "voluntary_ctxt_switches", 23) == 0) + { + cswitch_vol += tmp; + } + else if (strncmp (buffer, "nonvoluntary_ctxt_switches", 26) == 0) + { + cswitch_invol += tmp; + } + } + } /* while (fgets) */ + + if (fclose (fh)) + { + char errbuf[1024]; + WARNING ("processes: fclose: %s", + sstrerror (errno, errbuf, sizeof (errbuf))); + } + } + closedir (dh); + + ps->cswitch_vol = cswitch_vol; + ps->cswitch_invol = cswitch_invol; + + return (ps); +} /* int *ps_read_tasks_status */ + static int ps_read_tasks (int pid) { char dirname[64]; @@ -768,7 +949,7 @@ static procstat_t *ps_read_vmem (int pid, procstat_t *ps) continue; numfields = strsplit (buffer, fields, - STATIC_ARRAY_SIZE (fields)); + STATIC_ARRAY_SIZE (fields)); if (numfields < 2) continue; @@ -778,7 +959,7 @@ static procstat_t *ps_read_vmem (int pid, procstat_t *ps) tmp = strtoll (fields[1], &endptr, /* base = */ 10); if ((errno == 0) && (endptr != fields[1])) { - if (strncmp (buffer, "VmData", 6) == 0) + if (strncmp (buffer, "VmData", 6) == 0) { data = tmp; } @@ -869,13 +1050,15 @@ int ps_read_process (int pid, procstat_t *ps, char *state) char *fields[64]; char fields_len; - int i; + int buffer_len; - int ppid; - int name_len; + char *buffer_ptr; + size_t name_start_pos; + size_t name_end_pos; + size_t name_len; - long long unsigned cpu_user_counter; - long long unsigned cpu_system_counter; + derive_t cpu_user_counter; + derive_t cpu_system_counter; long long unsigned vmem_size; long long unsigned vmem_rss; long long unsigned stack_size; @@ -884,34 +1067,56 @@ int ps_read_process (int pid, procstat_t *ps, char *state) ssnprintf (filename, sizeof (filename), "/proc/%i/stat", pid); - i = read_file_contents (filename, buffer, sizeof(buffer) - 1); - if (i <= 0) + buffer_len = read_file_contents (filename, + buffer, sizeof(buffer) - 1); + if (buffer_len <= 0) return (-1); - buffer[i] = 0; - - fields_len = strsplit (buffer, fields, STATIC_ARRAY_SIZE (fields)); - if (fields_len < 24) + buffer[buffer_len] = 0; + + /* The name of the process is enclosed in parens. Since the name can + * contain parens itself, spaces, numbers and pretty much everything + * else, use these to determine the process name. We don't use + * strchr(3) and strrchr(3) to avoid pointer arithmetic which would + * otherwise be required to determine name_len. */ + name_start_pos = 0; + while ((buffer[name_start_pos] != '(') + && (name_start_pos < buffer_len)) + name_start_pos++; + + name_end_pos = buffer_len; + while ((buffer[name_end_pos] != ')') + && (name_end_pos > 0)) + name_end_pos--; + + /* 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) { - DEBUG ("processes plugin: ps_read_process (pid = %i):" - " `%s' has only %i fields..", - (int) pid, filename, fields_len); + ERROR ("processes plugin: name_start_pos = %zu >= name_end_pos = %zu", + name_start_pos, name_end_pos); return (-1); } - /* copy the name, strip brackets in the process */ - name_len = strlen (fields[1]) - 2; - if ((fields[1][0] != '(') || (fields[1][name_len + 1] != ')')) + name_len = (name_end_pos - name_start_pos) - 1; + if (name_len >= sizeof (ps->name)) + name_len = sizeof (ps->name) - 1; + + sstrncpy (ps->name, &buffer[name_start_pos + 1], name_len + 1); + + if ((buffer_len - name_end_pos) < 2) + return (-1); + buffer_ptr = &buffer[name_end_pos + 2]; + + fields_len = strsplit (buffer_ptr, fields, STATIC_ARRAY_SIZE (fields)); + if (fields_len < 22) { - DEBUG ("No brackets found in process name: `%s'", fields[1]); + DEBUG ("processes plugin: ps_read_process (pid = %i):" + " `%s' has only %i fields..", + (int) pid, filename, fields_len); return (-1); } - fields[1] = fields[1] + 1; - fields[1][name_len] = '\0'; - strncpy (ps->name, fields[1], PROCSTAT_NAME_LEN); - ppid = atoi (fields[3]); - - *state = fields[2][0]; + *state = fields[0][0]; if (*state == 'Z') { @@ -936,16 +1141,16 @@ int ps_read_process (int pid, procstat_t *ps, char *state) return (0); } - cpu_user_counter = atoll (fields[13]); - cpu_system_counter = atoll (fields[14]); - vmem_size = atoll (fields[22]); - vmem_rss = atoll (fields[23]); - ps->vmem_minflt_counter = atol (fields[9]); - ps->vmem_majflt_counter = atol (fields[11]); + cpu_user_counter = atoll (fields[11]); + cpu_system_counter = atoll (fields[12]); + vmem_size = atoll (fields[20]); + vmem_rss = atoll (fields[21]); + ps->vmem_minflt_counter = atol (fields[7]); + ps->vmem_majflt_counter = atol (fields[9]); { - unsigned long long stack_start = atoll (fields[27]); - unsigned long long stack_ptr = atoll (fields[28]); + unsigned long long stack_start = atoll (fields[25]); + unsigned long long stack_ptr = atoll (fields[26]); stack_size = (stack_start > stack_ptr) ? stack_start - stack_ptr @@ -965,8 +1170,8 @@ int ps_read_process (int pid, procstat_t *ps, char *state) DEBUG("ps_read_process: did not get vmem data for pid %i",pid); } - ps->cpu_user_counter = (unsigned long) cpu_user_counter; - ps->cpu_system_counter = (unsigned long) cpu_system_counter; + ps->cpu_user_counter = cpu_user_counter; + ps->cpu_system_counter = cpu_system_counter; ps->vmem_size = (unsigned long) vmem_size; ps->vmem_rss = (unsigned long) vmem_rss; ps->stack_size = (unsigned long) stack_size; @@ -982,6 +1187,18 @@ int ps_read_process (int pid, procstat_t *ps, char *state) DEBUG("ps_read_process: not get io data for pid %i",pid); } + if ( report_ctx_switch ) + { + if ( (ps_read_tasks_status(pid, ps)) == NULL) + { + ps->cswitch_vol = -1; + ps->cswitch_invol = -1; + + DEBUG("ps_read_tasks_status: not get context " + "switch data for pid %i",pid); + } + } + /* success */ return (0); } /* int ps_read_process (...) */ @@ -999,13 +1216,18 @@ static char *ps_get_cmdline (pid_t pid, char *name, char *buf, size_t buf_len) if ((pid < 1) || (NULL == buf) || (buf_len < 2)) return NULL; - ssnprintf (file, sizeof (file), "/proc/%u/cmdline", pid); + ssnprintf (file, sizeof (file), "/proc/%u/cmdline", + (unsigned int) pid); + errno = 0; fd = open (file, O_RDONLY); if (fd < 0) { char errbuf[4096]; - WARNING ("processes plugin: Failed to open `%s': %s.", file, - sstrerror (errno, errbuf, sizeof (errbuf))); + /* 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))); return NULL; } @@ -1020,7 +1242,7 @@ static char *ps_get_cmdline (pid_t pid, char *name, char *buf, size_t buf_len) status = read (fd, (void *)buf_ptr, len); if (status < 0) { - char errbuf[4096]; + char errbuf[1024]; if ((EAGAIN == errno) || (EINTR == errno)) continue; @@ -1076,70 +1298,226 @@ static char *ps_get_cmdline (pid_t pid, char *name, char *buf, size_t buf_len) return buf; } /* char *ps_get_cmdline (...) */ -static unsigned long read_fork_rate () +static int read_fork_rate () { FILE *proc_stat; - char buf[1024]; - unsigned long result = 0; - int numfields; - char *fields[3]; + char buffer[1024]; + value_t value; + _Bool value_valid = 0; - proc_stat = fopen("/proc/stat", "r"); - if (proc_stat == NULL) { + 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 ULONG_MAX; + return (-1); } - while (fgets (buf, sizeof(buf), proc_stat) != NULL) + while (fgets (buffer, sizeof (buffer), proc_stat) != NULL) { - char *endptr; + int status; + char *fields[3]; + int fields_num; - numfields = strsplit(buf, fields, STATIC_ARRAY_SIZE (fields)); - if (numfields != 2) + fields_num = strsplit (buffer, fields, + STATIC_ARRAY_SIZE (fields)); + if (fields_num != 2) continue; if (strcmp ("processes", fields[0]) != 0) continue; - errno = 0; - endptr = NULL; - result = strtoul(fields[1], &endptr, /* base = */ 10); - if ((endptr == fields[1]) || (errno != 0)) { - ERROR ("processes plugin: Cannot parse fork rate: %s", - fields[1]); - result = ULONG_MAX; - break; - } + status = parse_value (fields[1], &value, DS_TYPE_DERIVE); + if (status == 0) + value_valid = 1; break; } - fclose(proc_stat); - return result; + if (!value_valid) + return (-1); + + ps_submit_fork_rate (value.derive); + return (0); } +#endif /*KERNEL_LINUX */ -static void ps_submit_fork_rate (unsigned long value) +#if KERNEL_SOLARIS +static const char *ps_get_cmdline (long pid, /* {{{ */ + char *buffer, size_t buffer_size) { - value_t values[1]; - value_list_t vl = VALUE_LIST_INIT; + char path[PATH_MAX]; + psinfo_t info; + int status; - values[0].derive = (derive_t) value; + snprintf(path, sizeof (path), "/proc/%li/psinfo", pid); - vl.values = values; - vl.values_len = 1; - sstrncpy (vl.host, hostname_g, sizeof (vl.host)); - sstrncpy (vl.plugin, "processes", sizeof (vl.plugin)); - sstrncpy (vl.plugin_instance, "", sizeof (vl.plugin_instance)); - sstrncpy (vl.type, "fork_rate", sizeof (vl.type)); - sstrncpy (vl.type_instance, "", sizeof (vl.type_instance)); + status = read_file_contents (path, (void *) &info, sizeof (info)); + if (status != sizeof (info)) + { + ERROR ("processes plugin: Unexpected return value " + "while reading \"%s\": " + "Returned %i but expected %zu.", + path, status, buffer_size); + return (NULL); + } - plugin_dispatch_values (&vl); + info.pr_psargs[sizeof (info.pr_psargs) - 1] = 0; + sstrncpy (buffer, info.pr_psargs, buffer_size); + + return (buffer); +} /* }}} int ps_get_cmdline */ + +/* + * Reads process information on the Solaris OS. The information comes mainly from + * /proc/PID/status, /proc/PID/psinfo and /proc/PID/usage + * The values for input and ouput chars are calculated "by hand" + * Added a few "solaris" specific process states as well + */ +static int ps_read_process(long pid, procstat_t *ps, char *state) +{ + char filename[64]; + char f_psinfo[64], f_usage[64]; + char *buffer; + + pstatus_t *myStatus; + psinfo_t *myInfo; + prusage_t *myUsage; + + snprintf(filename, sizeof (filename), "/proc/%li/status", pid); + snprintf(f_psinfo, sizeof (f_psinfo), "/proc/%li/psinfo", pid); + snprintf(f_usage, sizeof (f_usage), "/proc/%li/usage", pid); + + + buffer = malloc(sizeof (pstatus_t)); + memset(buffer, 0, sizeof (pstatus_t)); + read_file_contents(filename, buffer, sizeof (pstatus_t)); + myStatus = (pstatus_t *) buffer; + + buffer = malloc(sizeof (psinfo_t)); + memset(buffer, 0, sizeof(psinfo_t)); + read_file_contents(f_psinfo, buffer, sizeof (psinfo_t)); + myInfo = (psinfo_t *) buffer; + + buffer = malloc(sizeof (prusage_t)); + memset(buffer, 0, sizeof(prusage_t)); + read_file_contents(f_usage, buffer, sizeof (prusage_t)); + myUsage = (prusage_t *) buffer; + + sstrncpy(ps->name, myInfo->pr_fname, sizeof (myInfo->pr_fname)); + ps->num_lwp = myStatus->pr_nlwp; + if (myInfo->pr_wstat != 0) { + ps->num_proc = 0; + ps->num_lwp = 0; + *state = (char) 'Z'; + return (0); + } else { + ps->num_proc = 1; + ps->num_lwp = myInfo->pr_nlwp; + } + + /* + * Convert system time and user time from nanoseconds to microseconds + * for compatibility with the linux module + */ + ps->cpu_system_counter = myStatus -> pr_stime.tv_nsec / 1000; + ps->cpu_user_counter = myStatus -> pr_utime.tv_nsec / 1000; + + /* + * Convert rssize from KB to bytes to be consistent w/ the linux module + */ + ps->vmem_rss = myInfo->pr_rssize * 1024; + ps->vmem_size = myInfo->pr_size * 1024; + ps->vmem_minflt_counter = myUsage->pr_minf; + ps->vmem_majflt_counter = myUsage->pr_majf; + + /* + * TODO: Data and code segment calculations for Solaris + */ + + ps->vmem_data = -1; + ps->vmem_code = -1; + ps->stack_size = myStatus->pr_stksize; + + /* + * Calculating input/ouput chars + * Formula used is total chars / total blocks => chars/block + * then convert input/output blocks to chars + */ + ulong_t tot_chars = myUsage->pr_ioch; + ulong_t tot_blocks = myUsage->pr_inblk + myUsage->pr_oublk; + ulong_t chars_per_block = 1; + if (tot_blocks != 0) + chars_per_block = tot_chars / tot_blocks; + ps->io_rchar = myUsage->pr_inblk * chars_per_block; + ps->io_wchar = myUsage->pr_oublk * chars_per_block; + ps->io_syscr = myUsage->pr_sysc; + ps->io_syscw = myUsage->pr_sysc; + + + /* + * TODO: Find way of setting BLOCKED and PAGING status + */ + + *state = (char) 'R'; + if (myStatus->pr_flags & PR_ASLEEP) + *state = (char) 'S'; + else if (myStatus->pr_flags & PR_STOPPED) + *state = (char) 'T'; + else if (myStatus->pr_flags & PR_DETACH) + *state = (char) 'E'; + else if (myStatus->pr_flags & PR_DAEMON) + *state = (char) 'A'; + else if (myStatus->pr_flags & PR_ISSYS) + *state = (char) 'Y'; + else if (myStatus->pr_flags & PR_ORPHAN) + *state = (char) 'O'; + + sfree(myStatus); + sfree(myInfo); + sfree(myUsage); + + return (0); } -#endif /* KERNEL_LINUX */ +/* + * Reads the number of threads created since the last reboot. On Solaris these + * are retrieved from kstat (module cpu, name sys, class misc, stat nthreads). + * The result is the sum for all the threads created on each cpu + */ +static int read_fork_rate() +{ + extern kstat_ctl_t *kc; + kstat_t *ksp_chain = NULL; + derive_t result = 0; + + if (kc == NULL) + return (-1); + + for (ksp_chain = kc->kc_chain; + ksp_chain != NULL; + ksp_chain = ksp_chain->ks_next) + { + if ((strcmp (ksp_chain->ks_module, "cpu") == 0) + && (strcmp (ksp_chain->ks_name, "sys") == 0) + && (strcmp (ksp_chain->ks_class, "misc") == 0)) + { + long long tmp; + + kstat_read (kc, ksp_chain, NULL); + + tmp = get_kstat_value(ksp_chain, "nthreads"); + if (tmp != -1LL) + result += tmp; + } + } + + ps_submit_fork_rate (result); + return (0); +} +#endif /* KERNEL_SOLARIS */ #if HAVE_THREAD_INFO static int mach_get_task_name (task_t t, int *pid, char *name, size_t name_max_len) @@ -1458,15 +1836,13 @@ static int ps_read (void) DIR *proc; int pid; - char cmdline[ARG_MAX]; + char cmdline[CMDLINE_BUFFER_SIZE]; int status; procstat_t ps; procstat_entry_t pse; char state; - unsigned long fork_rate; - procstat_t *ps_ptr; running = sleeping = zombies = stopped = paging = blocked = 0; @@ -1521,6 +1897,9 @@ static int ps_read (void) pse.io_syscr = ps.io_syscr; pse.io_syscw = ps.io_syscw; + pse.cswitch_vol = ps.cswitch_vol; + pse.cswitch_invol = ps.cswitch_invol; + switch (state) { case 'R': running++; break; @@ -1548,9 +1927,7 @@ static int ps_read (void) for (ps_ptr = list_head_g; ps_ptr != NULL; ps_ptr = ps_ptr->next) ps_submit_proc_list (ps_ptr); - fork_rate = read_fork_rate(); - if (fork_rate != ULONG_MAX) - ps_submit_fork_rate(fork_rate); + read_fork_rate(); /* #endif KERNEL_LINUX */ #elif HAVE_LIBKVM_GETPROCS && HAVE_STRUCT_KINFO_PROC_FREEBSD @@ -1563,12 +1940,10 @@ static int ps_read (void) int wait = 0; kvm_t *kd; - char errbuf[1024]; - char cmdline[ARG_MAX]; - char *cmdline_ptr; - struct kinfo_proc *procs; /* array of processes */ - char **argv; - int count; /* returns number of processes */ + char errbuf[_POSIX2_LINE_MAX]; + struct kinfo_proc *procs; /* array of processes */ + struct kinfo_proc *proc_ptr = NULL; + int count; /* returns number of processes */ int i; procstat_t *ps_ptr; @@ -1577,7 +1952,7 @@ static int ps_read (void) ps_list_reset (); /* Open the kvm interface, get a descriptor */ - kd = kvm_open (NULL, NULL, NULL, 0, errbuf); + kd = kvm_openfiles (NULL, "/dev/null", NULL, 0, errbuf); if (kd == NULL) { ERROR ("processes plugin: Cannot open kvm interface: %s", @@ -1589,73 +1964,89 @@ static int ps_read (void) procs = kvm_getprocs(kd, KERN_PROC_ALL, 0, &count); if (procs == NULL) { - kvm_close (kd); ERROR ("processes plugin: Cannot get kvm processes list: %s", kvm_geterr(kd)); + kvm_close (kd); return (0); } /* Iterate through the processes in kinfo_proc */ for (i = 0; i < count; i++) { - /* retrieve the arguments */ - cmdline[0] = 0; - cmdline_ptr = NULL; - - argv = kvm_getargv (kd, (const struct kinfo_proc *) &(procs[i]), 0); - if (argv != NULL) + /* Create only one process list entry per _process_, i.e. + * filter out threads (duplicate PID entries). */ + if ((proc_ptr == NULL) || (proc_ptr->ki_pid != procs[i].ki_pid)) { - int status; - int argc; - - argc = 0; - while (argv[argc] != NULL) - argc++; - - status = strjoin (cmdline, sizeof (cmdline), - argv, argc, " "); + char cmdline[CMDLINE_BUFFER_SIZE] = ""; + _Bool have_cmdline = 0; - if (status < 0) - { - WARNING ("processes plugin: Command line did " - "not fit into buffer."); - } - else + proc_ptr = &(procs[i]); + /* Don't probe system processes and processes without arguments */ + if (((procs[i].ki_flag & P_SYSTEM) == 0) + && (procs[i].ki_args != NULL)) { - cmdline_ptr = &cmdline[0]; - } - } + char **argv; + int argc; + int status; + + /* retrieve the arguments */ + argv = kvm_getargv (kd, proc_ptr, /* nchr = */ 0); + argc = 0; + if ((argv != NULL) && (argv[0] != NULL)) + { + while (argv[argc] != NULL) + argc++; + + status = strjoin (cmdline, sizeof (cmdline), argv, argc, " "); + if (status < 0) + WARNING ("processes plugin: Command line did not fit into buffer."); + else + have_cmdline = 1; + } + } /* if (process has argument list) */ - pse.id = procs[i].ki_pid; - pse.age = 0; + pse.id = procs[i].ki_pid; + pse.age = 0; - pse.num_proc = 1; - pse.num_lwp = procs[i].ki_numthreads; + pse.num_proc = 1; + pse.num_lwp = procs[i].ki_numthreads; - pse.vmem_size = procs[i].ki_size; - pse.vmem_rss = procs[i].ki_rssize * getpagesize(); - pse.vmem_data = procs[i].ki_dsize * getpagesize(); - pse.vmem_code = procs[i].ki_tsize * getpagesize(); - pse.stack_size = procs[i].ki_ssize * getpagesize(); - pse.vmem_minflt = 0; - pse.vmem_minflt_counter = procs[i].ki_rusage.ru_minflt; - pse.vmem_majflt = 0; - pse.vmem_majflt_counter = procs[i].ki_rusage.ru_majflt; + pse.vmem_size = procs[i].ki_size; + pse.vmem_rss = procs[i].ki_rssize * pagesize; + pse.vmem_data = procs[i].ki_dsize * pagesize; + pse.vmem_code = procs[i].ki_tsize * pagesize; + pse.stack_size = procs[i].ki_ssize * pagesize; + pse.vmem_minflt = 0; + pse.vmem_minflt_counter = procs[i].ki_rusage.ru_minflt; + pse.vmem_majflt = 0; + pse.vmem_majflt_counter = procs[i].ki_rusage.ru_majflt; - pse.cpu_user = 0; - pse.cpu_user_counter = procs[i].ki_rusage.ru_utime.tv_sec - * 1000 - + procs[i].ki_rusage.ru_utime.tv_usec; - pse.cpu_system = 0; - pse.cpu_system_counter = procs[i].ki_rusage.ru_stime.tv_sec - * 1000 - + procs[i].ki_rusage.ru_stime.tv_usec; + pse.cpu_user = 0; + pse.cpu_system = 0; + pse.cpu_user_counter = 0; + pse.cpu_system_counter = 0; + /* + * The u-area might be swapped out, and we can't get + * at it because we have a crashdump and no swap. + * If it's here fill in these fields, otherwise, just + * leave them 0. + */ + if (procs[i].ki_flag & P_INMEM) + { + pse.cpu_user_counter = procs[i].ki_rusage.ru_utime.tv_usec + + (1000000lu * procs[i].ki_rusage.ru_utime.tv_sec); + pse.cpu_system_counter = procs[i].ki_rusage.ru_stime.tv_usec + + (1000000lu * procs[i].ki_rusage.ru_stime.tv_sec); + } - /* no io data */ - pse.io_rchar = -1; - pse.io_wchar = -1; - pse.io_syscr = -1; - pse.io_syscw = -1; + /* no I/O data */ + pse.io_rchar = -1; + pse.io_wchar = -1; + pse.io_syscr = -1; + pse.io_syscw = -1; + + ps_list_add (procs[i].ki_comm, have_cmdline ? cmdline : NULL, &pse); + } /* if ((proc_ptr == NULL) || (proc_ptr->ki_pid != procs[i].ki_pid)) */ switch (procs[i].ki_stat) { @@ -1667,8 +2058,6 @@ static int ps_read (void) case SLOCK: blocked++; break; case SZOMB: zombies++; break; } - - ps_list_add (procs[i].ki_comm, cmdline_ptr, &pse); } kvm_close(kd); @@ -1685,6 +2074,138 @@ static int ps_read (void) ps_submit_proc_list (ps_ptr); /* #endif HAVE_LIBKVM_GETPROCS && HAVE_STRUCT_KINFO_PROC_FREEBSD */ +#elif HAVE_LIBKVM_GETPROCS && HAVE_STRUCT_KINFO_PROC_OPENBSD + int running = 0; + int sleeping = 0; + int zombies = 0; + int stopped = 0; + int onproc = 0; + int idle = 0; + int dead = 0; + + kvm_t *kd; + char errbuf[1024]; + struct kinfo_proc *procs; /* array of processes */ + struct kinfo_proc *proc_ptr = NULL; + int count; /* returns number of processes */ + int i; + + procstat_t *ps_ptr; + procstat_entry_t pse; + + ps_list_reset (); + + /* Open the kvm interface, get a descriptor */ + kd = kvm_open (NULL, NULL, NULL, 0, errbuf); + if (kd == NULL) + { + ERROR ("processes plugin: Cannot open kvm interface: %s", + errbuf); + return (0); + } + + /* Get the list of processes. */ + procs = kvm_getprocs(kd, KERN_PROC_ALL, 0, sizeof(struct kinfo_proc), &count); + if (procs == NULL) + { + ERROR ("processes plugin: Cannot get kvm processes list: %s", + kvm_geterr(kd)); + kvm_close (kd); + return (0); + } + + /* Iterate through the processes in kinfo_proc */ + for (i = 0; i < count; i++) + { + /* Create only one process list entry per _process_, i.e. + * filter out threads (duplicate PID entries). */ + if ((proc_ptr == NULL) || (proc_ptr->p_pid != procs[i].p_pid)) + { + char cmdline[CMDLINE_BUFFER_SIZE] = ""; + _Bool have_cmdline = 0; + + proc_ptr = &(procs[i]); + /* Don't probe zombie processes */ + if (!P_ZOMBIE(proc_ptr)) + { + char **argv; + int argc; + int status; + + /* retrieve the arguments */ + argv = kvm_getargv (kd, proc_ptr, /* nchr = */ 0); + argc = 0; + if ((argv != NULL) && (argv[0] != NULL)) + { + while (argv[argc] != NULL) + argc++; + + status = strjoin (cmdline, sizeof (cmdline), argv, argc, " "); + if (status < 0) + WARNING ("processes plugin: Command line did not fit into buffer."); + else + have_cmdline = 1; + } + } /* if (process has argument list) */ + + pse.id = procs[i].p_pid; + pse.age = 0; + + pse.num_proc = 1; + pse.num_lwp = 1; /* XXX: accumulate p_tid values for a single p_pid ? */ + + pse.vmem_rss = procs[i].p_vm_rssize * pagesize; + pse.vmem_data = procs[i].p_vm_dsize * pagesize; + pse.vmem_code = procs[i].p_vm_tsize * pagesize; + pse.stack_size = procs[i].p_vm_ssize * pagesize; + pse.vmem_size = pse.stack_size + pse.vmem_code + pse.vmem_data; + pse.vmem_minflt = 0; + pse.vmem_minflt_counter = procs[i].p_uru_minflt; + pse.vmem_majflt = 0; + pse.vmem_majflt_counter = procs[i].p_uru_majflt; + + pse.cpu_user = 0; + pse.cpu_system = 0; + pse.cpu_user_counter = procs[i].p_uutime_usec + + (1000000lu * procs[i].p_uutime_sec); + pse.cpu_system_counter = procs[i].p_ustime_usec + + (1000000lu * procs[i].p_ustime_sec); + + /* no I/O data */ + pse.io_rchar = -1; + pse.io_wchar = -1; + pse.io_syscr = -1; + pse.io_syscw = -1; + + ps_list_add (procs[i].p_comm, have_cmdline ? cmdline : NULL, &pse); + } /* if ((proc_ptr == NULL) || (proc_ptr->p_pid != procs[i].p_pid)) */ + + switch (procs[i].p_stat) + { + case SSTOP: stopped++; break; + case SSLEEP: sleeping++; break; + case SRUN: running++; break; + case SIDL: idle++; break; + case SONPROC: onproc++; break; + case SDEAD: dead++; break; + case SZOMB: zombies++; break; + } + } + + kvm_close(kd); + + ps_submit_state ("running", running); + ps_submit_state ("sleeping", sleeping); + ps_submit_state ("zombies", zombies); + ps_submit_state ("stopped", stopped); + ps_submit_state ("onproc", onproc); + ps_submit_state ("idle", idle); + ps_submit_state ("dead", dead); + + for (ps_ptr = list_head_g; ps_ptr != NULL; ps_ptr = ps_ptr->next) + ps_submit_proc_list (ps_ptr); +/* #endif HAVE_LIBKVM_GETPROCS && HAVE_STRUCT_KINFO_PROC_OPENBSD */ + #elif HAVE_PROCINFO_H /* AIX */ int running = 0; @@ -1725,7 +2246,7 @@ static int ps_read (void) if (procentry[i].pi_pid == 0) cmdline = "swapper"; cargs = cmdline; - } + } else { if (getargs(&procentry[i], sizeof(struct procentry64), arglist, MAXARGLN) >= 0) @@ -1817,15 +2338,126 @@ static int ps_read (void) for (ps = list_head_g; ps != NULL; ps = ps->next) ps_submit_proc_list (ps); -#endif /* HAVE_PROCINFO_H */ +/* #endif HAVE_PROCINFO_H */ + +#elif KERNEL_SOLARIS + /* + * The Solaris section adds a few more process states and removes some + * process states compared to linux. Most notably there is no "PAGING" + * and "BLOCKED" state for a process. The rest is similar to the linux + * code. + */ + int running = 0; + int sleeping = 0; + int zombies = 0; + int stopped = 0; + int detached = 0; + int daemon = 0; + int system = 0; + int orphan = 0; + + struct dirent *ent; + DIR *proc; + + int status; + procstat_t *ps_ptr; + char state; + + char cmdline[PRARGSZ]; + + ps_list_reset (); + + proc = opendir ("/proc"); + if (proc == NULL) + return (-1); + + while ((ent = readdir(proc)) != NULL) + { + long pid; + struct procstat ps; + procstat_entry_t pse; + char *endptr; + + if (!isdigit ((int) ent->d_name[0])) + continue; + + pid = strtol (ent->d_name, &endptr, 10); + if (*endptr != 0) /* value didn't completely parse as a number */ + continue; + + status = ps_read_process (pid, &ps, &state); + if (status != 0) + { + DEBUG("ps_read_process failed: %i", status); + continue; + } + + pse.id = pid; + pse.age = 0; + + pse.num_proc = ps.num_proc; + pse.num_lwp = ps.num_lwp; + pse.vmem_size = ps.vmem_size; + pse.vmem_rss = ps.vmem_rss; + pse.vmem_data = ps.vmem_data; + pse.vmem_code = ps.vmem_code; + pse.stack_size = ps.stack_size; + + pse.vmem_minflt = 0; + pse.vmem_minflt_counter = ps.vmem_minflt_counter; + pse.vmem_majflt = 0; + pse.vmem_majflt_counter = ps.vmem_majflt_counter; + + pse.cpu_user = 0; + pse.cpu_user_counter = ps.cpu_user_counter; + pse.cpu_system = 0; + pse.cpu_system_counter = ps.cpu_system_counter; + + pse.io_rchar = ps.io_rchar; + pse.io_wchar = ps.io_wchar; + pse.io_syscr = ps.io_syscr; + pse.io_syscw = ps.io_syscw; + + switch (state) + { + case 'R': running++; break; + case 'S': sleeping++; break; + case 'E': detached++; break; + case 'Z': zombies++; break; + case 'T': stopped++; break; + case 'A': daemon++; break; + case 'Y': system++; break; + case 'O': orphan++; break; + } + + + ps_list_add (ps.name, + ps_get_cmdline (pid, cmdline, sizeof (cmdline)), + &pse); + } /* while(readdir) */ + closedir (proc); + + ps_submit_state ("running", running); + ps_submit_state ("sleeping", sleeping); + ps_submit_state ("zombies", zombies); + ps_submit_state ("stopped", stopped); + ps_submit_state ("detached", detached); + ps_submit_state ("daemon", daemon); + ps_submit_state ("system", system); + ps_submit_state ("orphan", orphan); + + for (ps_ptr = list_head_g; ps_ptr != NULL; ps_ptr = ps_ptr->next) + ps_submit_proc_list (ps_ptr); + + read_fork_rate(); +#endif /* KERNEL_SOLARIS */ return (0); } /* int ps_read */ void module_register (void) { - plugin_register_config ("processes", ps_config, - config_keys, config_keys_num); + plugin_register_complex_config ("processes", ps_config); plugin_register_init ("processes", ps_init); plugin_register_read ("processes", ps_read); } /* void module_register */