Hi,

I got AIX machine, so I compiled it successfully there and it works.

Lukas.

On 09/29/2011 02:22 PM, Lukas Bednar wrote:
Hi,

I send patch with improving of process plugin. I tested it on Linux and
FreeBSD. It works. I have no access on AIX and OSX platforms.

 From f56334e2a71131e83d46aa7d9f67dbfb2c0b2eb9 Mon Sep 17 00:00:00 2001
From: Lukas Bednar <lbed...@redhat.com>
Date: Thu, 29 Sep 2011 07:56:52 +0200
Subject: [PATCH] processes_plugin: added option to account children of
processes

Added AccountChildren option in order measure data from children
of processes.
---
src/processes.c | 447
+++++++++++++++++++++++++++++++++++++++++--------------
src/rrdtool.c | 3 +-
2 files changed, 339 insertions(+), 111 deletions(-)

diff --git a/src/processes.c b/src/processes.c
index 8f4eb88..ea1b611 100644
--- a/src/processes.c
+++ b/src/processes.c
@@ -161,6 +161,8 @@ typedef struct procstat
regex_t *re;
#endif

+ unsigned short children;
+
unsigned long num_proc;
unsigned long num_lwp;
unsigned long vmem_size;
@@ -186,6 +188,9 @@ typedef struct procstat
} procstat_t;

static procstat_t *list_head_g = NULL;
+static procstat_entry_t *list_childs_head_g = NULL;
+
+static int ps_get_ppid(unsigned int pid);

#if HAVE_THREAD_INFO
static mach_port_t port_host_self;
@@ -200,12 +205,14 @@ static long pagesize_g;
/* #endif KERNEL_LINUX */

#elif HAVE_LIBKVM_GETPROCS && HAVE_STRUCT_KINFO_PROC_FREEBSD
-/* no global variables */
+static struct kinfo_proc *procs_g;
+static int procs_count_g;
/* #endif HAVE_LIBKVM_GETPROCS && HAVE_STRUCT_KINFO_PROC_FREEBSD */

#elif HAVE_PROCINFO_H
-static struct procentry64 procentry[MAXPROCENTRY];
-static struct thrdentry64 thrdentry[MAXTHRDENTRY];
+static struct procentry64 procentry[MAXPROCENTRY];
+static int procentry_count_g;
+static struct thrdentry64 thrdentry[MAXTHRDENTRY];
static int pagesize;

#ifndef _AIXVERSION_610
@@ -218,7 +225,7 @@ int getargs (struct procentry64 *processBuffer, int
bufferLen, char *argsBuffer,
/* 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 void ps_list_register (const char *name, const char *regexp,
unsigned short children)
{
procstat_t *new;
procstat_t *ptr;
@@ -233,6 +240,8 @@ static void ps_list_register (const char *name,
const char *regexp)
memset (new, 0, sizeof (procstat_t));
sstrncpy (new->name, name, sizeof (new->name));

+ new->children = children;
+
#if HAVE_REGEX_H
if (regexp != NULL)
{
@@ -320,40 +329,95 @@ static int ps_list_match (const char *name, const
char *cmdline, procstat_t *ps)
return (0);
} /* int ps_list_match */

+static procstat_entry_t * ps_new_procstat_entry(procstat_entry_t
**head, procstat_entry_t *last)
+{
+ procstat_entry_t * new = (procstat_entry_t*) malloc(sizeof
(procstat_entry_t));
+ if (new != NULL)
+ {
+ memset (new, 0, sizeof (procstat_entry_t));
+
+ if(last == NULL)
+ *head = new;
+ else
+ last->next = new;
+ }
+ else
+ ERROR ("process plugin: ps_new_procstat_entry failed to allocate "
+ "memory for new processes");
+ return new;
+}
+
+static procstat_entry_t *ps_find_procstat_entry(procstat_entry_t *pse,
unsigned long id)
+{
+ for (;pse != NULL; pse = pse->next)
+ {
+ if (pse->id == id || pse->next == NULL)
+ break;
+ }
+ return pse;
+}
+
/* add process entry to 'instances' of process 'name' (or refresh it) */
static void ps_list_add (const char *name, const char *cmdline,
procstat_entry_t *entry)
{
procstat_t *ps;
procstat_entry_t *pse;
+ int current_pid;
+ int ppid;
+ int parent_found;
+ unsigned short children;

if (entry->id == 0)
return;

for (ps = list_head_g; ps != NULL; ps = ps->next)
{
+ current_pid = 0;
+ ppid = entry->id;
+ parent_found = 0;
+ children = ps->children;
+
if ((ps_list_match (name, cmdline, ps)) == 0)
- continue;
+ {
+ while (children > 0 && parent_found == 0 && ppid != -1)
+ {
+ current_pid = ppid;
+ for (pse = ps->instances; pse != NULL; pse = pse->next)
+ if (pse->id == current_pid)
+ {
+ parent_found = 1;
+ break;
+ }
+ else if (pse->next == NULL)
+ break;
+ ppid = ps_get_ppid(current_pid);
+ children--;
+ }
+ if (parent_found == 0)
+ continue;

- for (pse = ps->instances; pse != NULL; pse = pse->next)
- if ((pse->id == entry->id) || (pse->next == NULL))
- break;
+ pse = ps_find_procstat_entry(list_childs_head_g, entry->id);
+ if (pse == NULL || (pse->id != entry->id))
+ {
+ pse = ps_new_procstat_entry (&list_childs_head_g, pse);
+ if (pse == NULL)
+ return;

- if ((pse == NULL) || (pse->id != entry->id))
+ pse->id = entry->id;
+ }
+ }
+ else
{
- procstat_entry_t *new;
-
- new = (procstat_entry_t *) malloc (sizeof (procstat_entry_t));
- if (new == NULL)
- return;
- memset (new, 0, sizeof (procstat_entry_t));
- new->id = entry->id;
+ pse = ps_find_procstat_entry(ps->instances, entry->id);
+ }

+ if ((pse == NULL) || (pse->id != entry->id))
+ {
+ pse = ps_new_procstat_entry (&ps->instances, pse);
if (pse == NULL)
- ps->instances = new;
- else
- pse->next = new;
+ return;

- pse = new;
+ pse->id = entry->id;
}

pse->age = 0;
@@ -418,7 +482,7 @@ static void ps_list_add (const char *name, const
char *cmdline, procstat_entry_t

ps->vmem_minflt_counter += pse->vmem_minflt;
ps->vmem_majflt_counter += pse->vmem_majflt;
-
+
if ((entry->cpu_user_counter == 0)
&& (entry->cpu_system_counter == 0))
{
@@ -458,12 +522,46 @@ static void ps_list_add (const char *name, const
char *cmdline, procstat_entry_t
}
}

+static void ps_list_procstat_entry_reset(procstat_entry_t **head, const
char *name)
+{
+ procstat_entry_t *pse = *head;
+ procstat_entry_t *pse_prev = NULL;
+
+ while (pse != NULL)
+ {
+ if (pse->age > 10)
+ {
+ DEBUG ("Removing this procstat entry cause it's too old: "
+ "id = %lu; name = %s;",
+ pse->id, name);
+
+ if (pse_prev == NULL)
+ {
+ *head = pse->next;
+ free (pse);
+ pse = *head;
+ }
+ else
+ {
+ pse_prev->next = pse->next;
+ free (pse);
+ pse = pse_prev->next;
+ }
+ }
+ else
+ {
+ pse->age++;
+ pse_prev = pse;
+ pse = pse->next;
+ }
+ } /* while (pse != NULL) */
+
+}
+
/* remove old entries from instances of processes in list_head_g */
static void ps_list_reset (void)
{
procstat_t *ps;
- procstat_entry_t *pse;
- procstat_entry_t *pse_prev;

for (ps = list_head_g; ps != NULL; ps = ps->next)
{
@@ -479,43 +577,84 @@ static void ps_list_reset (void)
ps->io_syscr = -1;
ps->io_syscw = -1;

- pse_prev = NULL;
- pse = ps->instances;
- while (pse != NULL)
+ ps_list_procstat_entry_reset (&ps->instances, ps->name);
+ } /* for (ps = list_head_g; ps != NULL; ps = ps->next) */
+
+ ps_list_procstat_entry_reset (&list_childs_head_g, "child of process");
+}
+
+static unsigned short ps_config_tree(oconfig_item_t *c)
+{
+ unsigned short children = 0;
+
+ if (strcasecmp (c->key, "AccountChildren") == 0)
+ {
+ if (c->values_num != 1) {
+ WARNING ("processes plugin: the `AccountChildren' expects "
+ "exactly one boolean or number argument (got %i). "
+ "-- use 'false' option by default", c->values_num);
+ }
+
+ if (c->children_num != 0)
{
- if (pse->age > 10)
- {
- DEBUG ("Removing this procstat entry cause it's too old: "
- "id = %lu; name = %s;",
- pse->id, ps->name);
+ WARNING ("processes plugin: the `AccountChildren' config "
+ "option does not expect any child elements -- ignoring "
+ "content (%i elements) of the <Process '%s'> block.",
+ c->children_num, c->values[0].value.string);
+ }

- if (pse_prev == NULL)
- {
- ps->instances = pse->next;
- free (pse);
- pse = ps->instances;
- }
- else
- {
- pse_prev->next = pse->next;
- free (pse);
- pse = pse_prev->next;
- }
+ if (c->values[0].type == OCONFIG_TYPE_STRING)
+ {
+ if (strcmp (c->values[0].value.string, "true") == 0)
+ {
+ children = (unsigned short) -1;
+ }
+ else if (strcmp (c->values[0].value.string, "false") != 0)
+ {
+ WARNING ("processes plugin: the `AccountChildren' option "
+ "expects true|false options. got '%s' -- use "
+ "'false' option by default",
+ c->values[0].value.string);
+ }
+ }
+ else if (c->values[0].type == OCONFIG_TYPE_NUMBER)
+ {
+ if (c->values[0].value.number < 0)
+ {
+ WARNING ("processes plugin: the `AccountChildren' option "
+ "expects positive number, got '%f'. -- use '0' "
+ "option by default.", c->values[0].value.number);
}
else
{
- pse->age++;
- pse_prev = pse;
- pse = pse->next;
+ children = (unsigned short) c->values[0].value.number;
}
- } /* while (pse != NULL) */
- } /* for (ps = list_head_g; ps != NULL; ps = ps->next) */
+ }
+ else if (c->values[0].type == OCONFIG_TYPE_BOOLEAN)
+ {
+ if (c->values[0].value.boolean == 0)
+ children = 0;
+ else
+ children = (unsigned short) -1;
+ }
+ else
+ {
+ ERROR("processes plugin: unreachable statement");
+ }
+ }
+ else
+ {
+ WARNING("processes plugin: unexpected option '%s' -- ignoring", c->key);
+ }
+
+ return children;
}

/* put all pre-defined 'Process' names from config to list_head_g tree */
static int ps_config (oconfig_item_t *ci)
{
int i;
+ unsigned short children = 0;

for (i = 0; i < ci->children_num; ++i) {
oconfig_item_t *c = ci->children + i;
@@ -531,13 +670,18 @@ static int ps_config (oconfig_item_t *ci)
}

if (c->children_num != 0) {
- WARNING ("processes plugin: the `Process' config option "
- "does not expect any child elements -- ignoring "
- "content (%i elements) of the <Process '%s'> block.",
- c->children_num, c->values[0].value.string);
+ if (c->children_num > 1)
+ {
+ WARNING ("processes plugin: the `Process' config option "
+ "expects at maximal one child elements -- use only "
+ "first element, ignoring content (%i elements) of "
+ "the <Process '%s'> block.",
+ c->children_num - 1, c->values[0].value.string);
+ }
+ children = ps_config_tree(c->children);
}

- ps_list_register (c->values[0].value.string, NULL);
+ ps_list_register (c->values[0].value.string, NULL, children);
}
else if (strcasecmp (c->key, "ProcessMatch") == 0)
{
@@ -552,15 +696,22 @@ static int ps_config (oconfig_item_t *ci)
}

if (c->children_num != 0) {
- WARNING ("processes plugin: the `ProcessMatch' config option "
- "does not expect any child elements -- ignoring "
- "content (%i elements) of the <ProcessMatch '%s' '%s'> "
- "block.", c->children_num, c->values[0].value.string,
- c->values[1].value.string);
+ if (c->children_num > 1)
+ {
+ WARNING ("processes plugin: the `ProcessMatch' config "
+ "option expects at maximal one child element "
+ "-- use only first element, ignoring content "
+ "(%i elements) of the <ProcessMatch '%s' '%s'> "
+ "block.", c->children_num - 1,
+ c->values[0].value.string,
+ c->values[1].value.string);
+ }
+
+ children = ps_config_tree(c->children);
}

ps_list_register (c->values[0].value.string,
- c->values[1].value.string);
+ c->values[1].value.string, children);
}
else
{
@@ -712,7 +863,7 @@ static void ps_submit_proc_list (procstat_t *ps)
}

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 = %"PRIi64"; vmem_majflt_counter = %"PRIi64"; "
"cpu_user_counter = %"PRIi64"; cpu_system_counter = %"PRIi64"; "
@@ -728,6 +879,54 @@ static void ps_submit_proc_list (procstat_t *ps)

/* ------- additional functions for KERNEL_LINUX/HAVE_THREAD_INFO
------- */
#if KERNEL_LINUX
+static int ps_read_stat(unsigned int pid, char **fields, int fields_len)
+{
+ char filename[PATH_MAX];
+ char buffer[1024];
+ int i;
+
+ ssnprintf (filename, sizeof (filename), "/proc/%i/stat", pid);
+
+ i = read_file_contents (filename, buffer, sizeof(buffer) - 1);
+ if (i <= 0)
+ return (-1);
+ buffer[i] = 0;
+
+ fields_len = strsplit (buffer, fields, fields_len);
+ if (fields_len < 24)
+ {
+ DEBUG ("processes plugin: ps_read_process (pid = %i):"
+ " `%s' has only %i fields..",
+ (int) pid, filename, fields_len);
+ return (-1);
+ }
+ return fields_len;
+}
+
+static int ps_get_ppid(unsigned int pid)
+{
+ int ppid;
+ char *fields[64];
+ int fields_len;
+
+ if (pid != 1)
+ {
+ fields_len = ps_read_stat(pid, fields, STATIC_ARRAY_SIZE(fields));
+ if (fields_len != -1)
+ {
+ ppid = atoi (fields[3]);
+ if (ppid > 0)
+ return ppid;
+ }
+ else
+ {
+ DEBUG ("processes plugin: ps_get_ppid (pid = %i) can't "
+ "retrieve PPID", pid);
+ }
+ }
+ return (-1);
+}
+
static int ps_read_tasks (int pid)
{
char dirname[64];
@@ -780,7 +979,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;
@@ -875,14 +1074,9 @@ static procstat_t *ps_read_io (int pid, procstat_t
*ps)

int ps_read_process (int pid, procstat_t *ps, char *state)
{
- char filename[64];
- char buffer[1024];
-
char *fields[64];
char fields_len;

- int i;
-
int name_len;

derive_t cpu_user_counter;
@@ -892,22 +1086,10 @@ int ps_read_process (int pid, procstat_t *ps,
char *state)
long long unsigned stack_size;

memset (ps, 0, sizeof (procstat_t));
-
- ssnprintf (filename, sizeof (filename), "/proc/%i/stat", pid);
-
- i = read_file_contents (filename, buffer, sizeof(buffer) - 1);
- if (i <= 0)
- return (-1);
- buffer[i] = 0;
-
- fields_len = strsplit (buffer, fields, STATIC_ARRAY_SIZE (fields));
- if (fields_len < 24)
- {
- DEBUG ("processes plugin: ps_read_process (pid = %i):"
- " `%s' has only %i fields..",
- (int) pid, filename, fields_len);
- return (-1);
- }
+
+ fields_len = ps_read_stat(pid, fields, STATIC_ARRAY_SIZE(fields));
+ if (fields_len == -1)
+ return fields_len;

/* copy the name, strip brackets in the process */
name_len = strlen (fields[1]) - 2;
@@ -1153,8 +1335,38 @@ static void ps_submit_fork_rate (unsigned long
value)

plugin_dispatch_values (&vl);
}
+/* endif KERNEL_LINUX */

-#endif /* KERNEL_LINUX */
+#elif HAVE_LIBKVM_GETPROCS && HAVE_STRUCT_KINFO_PROC_FREEBSD
+static int ps_get_ppid(unsigned int pid)
+{
+ int i;
+ if (pid != 1)
+ {
+ for (i = 0; i < procs_count_g; i++)
+ {
+ if (procs_g[i].ki_pid == pid)
+ return procs_g[i].ki_ppid;
+ }
+ DEBUG ("processes plugin: ps_get_ppid (pid = %i) can't retrieve PPID",
pid);
+ }
+ return (-1);
+}
+#elif HAVE_PROCINFO_H
+static int ps_get_ppid(unsigned int pid)
+{
+ int i;
+ if (pid != 1)
+ {
+ for (i = 0; i < procentry_count_g; i++)
+ {
+ if (procentry[i].pi_pid == pid)
+ return procentry[i].pi_ppid;
+ }
+ DEBUG ("processes plugin: ps_get_ppid (pid = %i) can't retrieve PPID",
pid);
+ }
+}
+#endif

#if HAVE_THREAD_INFO
static int mach_get_task_name (task_t t, int *pid, char *name, size_t
name_max_len)
@@ -1190,6 +1402,22 @@ static int mach_get_task_name (task_t t, int
*pid, char *name, size_t name_max_l

return (0);
}
+
+static int ps_get_ppid(unsigned int pid)
+{
+ struct kinfo_proc kp;
+ int ppid = -1;
+
+ if (pid != 1)
+ {
+ if (get_kinfo_proc (pid, &kp) != -1)
+ ppid = kp.kp_eproc.e_ppid;
+ else
+ DEBUG ("processes plugin: ps_get_ppid (pid = %i) can't retrieve PPID",
pid);
+ }
+ return ppid;
+
+}
#endif /* HAVE_THREAD_INFO */
/* ------- end of additional functions for KERNEL_LINUX/HAVE_THREAD_INFO
------- */

@@ -1468,7 +1696,6 @@ static int ps_read (void)
int stopped = 0;
int paging = 0;
int blocked = 0;
-
struct dirent *ent;
DIR *proc;
int pid;
@@ -1581,9 +1808,9 @@ static int ps_read (void)
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 */
+ //struct kinfo_proc *procs; /* array of processes */
+ char **argv;
+ //int count; /* returns number of processes */
int i;

procstat_t *ps_ptr;
@@ -1601,8 +1828,8 @@ static int ps_read (void)
}

/* Get the list of processes. */
- procs = kvm_getprocs(kd, KERN_PROC_ALL, 0, &count);
- if (procs == NULL)
+ procs_g = kvm_getprocs(kd, KERN_PROC_ALL, 0, &procs_count_g);
+ if (procs_g == NULL)
{
ERROR ("processes plugin: Cannot get kvm processes list: %s",
kvm_geterr(kd));
@@ -1611,13 +1838,13 @@ static int ps_read (void)
}

/* Iterate through the processes in kinfo_proc */
- for (i = 0; i < count; i++)
+ for (i = 0; i < procs_count_g; i++)
{
/* retrieve the arguments */
cmdline[0] = 0;
cmdline_ptr = NULL;

- argv = kvm_getargv (kd, (const struct kinfo_proc *) &(procs[i]), 0);
+ argv = kvm_getargv (kd, (const struct kinfo_proc *) &(procs_g[i]), 0);
if (argv != NULL)
{
int status;
@@ -1641,30 +1868,30 @@ static int ps_read (void)
}
}

- pse.id = procs[i].ki_pid;
+ pse.id = procs_g[i].ki_pid;
pse.age = 0;

pse.num_proc = 1;
- pse.num_lwp = procs[i].ki_numthreads;
+ pse.num_lwp = procs_g[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_size = procs_g[i].ki_size;
+ pse.vmem_rss = procs_g[i].ki_rssize * getpagesize();
+ pse.vmem_data = procs_g[i].ki_dsize * getpagesize();
+ pse.vmem_code = procs_g[i].ki_tsize * getpagesize();
+ pse.stack_size = procs_g[i].ki_ssize * getpagesize();
pse.vmem_minflt = 0;
- pse.vmem_minflt_counter = procs[i].ki_rusage.ru_minflt;
+ pse.vmem_minflt_counter = procs_g[i].ki_rusage.ru_minflt;
pse.vmem_majflt = 0;
- pse.vmem_majflt_counter = procs[i].ki_rusage.ru_majflt;
+ pse.vmem_majflt_counter = procs_g[i].ki_rusage.ru_majflt;

pse.cpu_user = 0;
- pse.cpu_user_counter = procs[i].ki_rusage.ru_utime.tv_sec
+ pse.cpu_user_counter = procs_g[i].ki_rusage.ru_utime.tv_sec
* 1000
- + procs[i].ki_rusage.ru_utime.tv_usec;
+ + procs_g[i].ki_rusage.ru_utime.tv_usec;
pse.cpu_system = 0;
- pse.cpu_system_counter = procs[i].ki_rusage.ru_stime.tv_sec
+ pse.cpu_system_counter = procs_g[i].ki_rusage.ru_stime.tv_sec
* 1000
- + procs[i].ki_rusage.ru_stime.tv_usec;
+ + procs_g[i].ki_rusage.ru_stime.tv_usec;

/* no io data */
pse.io_rchar = -1;
@@ -1672,7 +1899,7 @@ static int ps_read (void)
pse.io_syscr = -1;
pse.io_syscw = -1;

- switch (procs[i].ki_stat)
+ switch (procs_g[i].ki_stat)
{
case SSTOP: stopped++; break;
case SSLEEP: sleeping++; break;
@@ -1683,7 +1910,7 @@ static int ps_read (void)
case SZOMB: zombies++; break;
}

- ps_list_add (procs[i].ki_comm, cmdline_ptr, &pse);
+ ps_list_add (procs_g[i].ki_comm, cmdline_ptr, &pse);
}

kvm_close(kd);
@@ -1710,19 +1937,19 @@ static int ps_read (void)
int blocked = 0;

pid_t pindex = 0;
- int nprocs;
+ //int nprocs;

procstat_t *ps;
procstat_entry_t pse;

ps_list_reset ();
- while ((nprocs = getprocs64 (procentry, sizeof(struct procentry64),
+ while ((procentry_count_g = getprocs64 (procentry, sizeof(struct
procentry64),
/* fdsinfo = */ NULL, sizeof(struct fdsinfo64),
&pindex, MAXPROCENTRY)) > 0)
{
int i;

- for (i = 0; i < nprocs; i++)
+ for (i = 0; i < procentry_count_g; i++)
{
tid64_t thindex;
int nthreads;
@@ -1740,7 +1967,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)
diff --git a/src/rrdtool.c b/src/rrdtool.c
index 56a82d0..06ca934 100644
--- a/src/rrdtool.c
+++ b/src/rrdtool.c
@@ -892,7 +892,8 @@ static int rrd_write (const data_set_t *ds, const
value_list_t *vl,
return (0);

if (0 != strcmp (ds->type, vl->type)) {
- ERROR ("rrdtool plugin: DS type does not match value list type");
+ ERROR ("rrdtool plugin: DS type does not match value list type: "
+ "'%s' != '%s'", ds->type, vl->type);
return -1;
}



_______________________________________________
collectd mailing list
collectd@verplant.org
http://mailman.verplant.org/listinfo/collectd

Reply via email to