From: Florian Forster Date: Thu, 18 Jun 2015 14:52:09 +0000 (+0200) Subject: Merge branch 'collectd-5.4' into collectd-5.5 X-Git-Tag: collectd-5.5.1~98 X-Git-Url: https://git.verplant.org/?a=commitdiff_plain;h=0003c4d3c184f0f437499d6073cd023dc7b659c2;p=collectd.git Merge branch 'collectd-5.4' into collectd-5.5 --- 0003c4d3c184f0f437499d6073cd023dc7b659c2 diff --cc src/Makefile.am index 9839c03a,62f98751..c718621e --- a/src/Makefile.am +++ b/src/Makefile.am @@@ -953,8 -1094,10 +953,8 @@@ if BUILD_PLUGIN_STATS pkglib_LTLIBRARIES += statsd.la statsd_la_SOURCES = statsd.c \ utils_latency.h utils_latency.c -statsd_la_LDFLAGS = -module -avoid-version +statsd_la_LDFLAGS = $(PLUGIN_LDFLAGS) - statsd_la_LIBADD = -lpthread + statsd_la_LIBADD = -lpthread -lm -collectd_LDADD += "-dlopen" statsd.la -collectd_DEPENDENCIES += statsd.la endif if BUILD_PLUGIN_SWAP diff --cc src/daemon/collectd.c index a708665d,00000000..46e13b3f mode 100644,000000..100644 --- a/src/daemon/collectd.c +++ b/src/daemon/collectd.c @@@ -1,718 -1,0 +1,719 @@@ +/** + * collectd - src/collectd.c + * Copyright (C) 2005-2007 Florian octo Forster + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + * + * Authors: + * Florian octo Forster + * Alvaro Barcellos + **/ + +#include "collectd.h" +#include "common.h" + +#include "plugin.h" +#include "configfile.h" + +#include +#include +#include +#include + +#include + +#if HAVE_LOCALE_H +# include +#endif + +#if HAVE_STATGRAB_H +# include +#endif + +#ifndef COLLECTD_LOCALE +# define COLLECTD_LOCALE "C" +#endif + +/* + * Global variables + */ +char hostname_g[DATA_MAX_NAME_LEN]; +cdtime_t interval_g; +int pidfile_from_cli = 0; +int timeout_g; +#if HAVE_LIBKSTAT +kstat_ctl_t *kc; +#endif /* HAVE_LIBKSTAT */ + +static int loop = 0; + +static void *do_flush (void __attribute__((unused)) *arg) +{ + INFO ("Flushing all data."); + plugin_flush (/* plugin = */ NULL, + /* timeout = */ 0, + /* ident = */ NULL); + INFO ("Finished flushing all data."); + pthread_exit (NULL); + return NULL; +} + +static void sig_int_handler (int __attribute__((unused)) signal) +{ + loop++; +} + +static void sig_term_handler (int __attribute__((unused)) signal) +{ + loop++; +} + +static void sig_usr1_handler (int __attribute__((unused)) signal) +{ + pthread_t thread; + pthread_attr_t attr; + + /* flushing the data might take a while, + * so it should be done asynchronously */ + pthread_attr_init (&attr); + pthread_attr_setdetachstate (&attr, PTHREAD_CREATE_DETACHED); + pthread_create (&thread, &attr, do_flush, NULL); + pthread_attr_destroy (&attr); +} + +static int init_hostname (void) +{ + const char *str; + + struct addrinfo ai_hints; + struct addrinfo *ai_list; + struct addrinfo *ai_ptr; + int status; + + str = global_option_get ("Hostname"); + if (str != NULL) + { + sstrncpy (hostname_g, str, sizeof (hostname_g)); + return (0); + } + + if (gethostname (hostname_g, sizeof (hostname_g)) != 0) + { + fprintf (stderr, "`gethostname' failed and no " + "hostname was configured.\n"); + return (-1); + } + + str = global_option_get ("FQDNLookup"); + if (IS_FALSE (str)) + return (0); + + memset (&ai_hints, '\0', sizeof (ai_hints)); + ai_hints.ai_flags = AI_CANONNAME; + + status = getaddrinfo (hostname_g, NULL, &ai_hints, &ai_list); + if (status != 0) + { + ERROR ("Looking up \"%s\" failed. You have set the " + "\"FQDNLookup\" option, but I cannot resolve " + "my hostname to a fully qualified domain " + "name. Please fix the network " + "configuration.", hostname_g); + return (-1); + } + + for (ai_ptr = ai_list; ai_ptr != NULL; ai_ptr = ai_ptr->ai_next) + { + if (ai_ptr->ai_canonname == NULL) + continue; + + sstrncpy (hostname_g, ai_ptr->ai_canonname, sizeof (hostname_g)); + break; + } + + freeaddrinfo (ai_list); + return (0); +} /* int init_hostname */ + +static int init_global_variables (void) +{ + char const *str; + + interval_g = cf_get_default_interval (); + assert (interval_g > 0); + DEBUG ("interval_g = %.3f;", CDTIME_T_TO_DOUBLE (interval_g)); + + str = global_option_get ("Timeout"); + if (str == NULL) + str = "2"; + timeout_g = atoi (str); + if (timeout_g <= 1) + { + fprintf (stderr, "Cannot set the timeout to a correct value.\n" + "Please check your settings.\n"); + return (-1); + } + DEBUG ("timeout_g = %i;", timeout_g); + + if (init_hostname () != 0) + return (-1); + DEBUG ("hostname_g = %s;", hostname_g); + + return (0); +} /* int init_global_variables */ + +static int change_basedir (const char *orig_dir) +{ + char *dir; + size_t dirlen; + int status; + + dir = strdup (orig_dir); + if (dir == NULL) + { + char errbuf[1024]; + ERROR ("strdup failed: %s", + sstrerror (errno, errbuf, sizeof (errbuf))); + return (-1); + } + + dirlen = strlen (dir); + while ((dirlen > 0) && (dir[dirlen - 1] == '/')) + dir[--dirlen] = '\0'; + + if (dirlen <= 0) + return (-1); + + status = chdir (dir); + if (status == 0) + { + free (dir); + return (0); + } + else if (errno != ENOENT) + { + char errbuf[1024]; + ERROR ("change_basedir: chdir (%s): %s", dir, + sstrerror (errno, errbuf, sizeof (errbuf))); + free (dir); + return (-1); + } + + status = mkdir (dir, S_IRWXU | S_IRWXG | S_IRWXO); + if (status != 0) + { + char errbuf[1024]; + ERROR ("change_basedir: mkdir (%s): %s", dir, + sstrerror (errno, errbuf, sizeof (errbuf))); + free (dir); + return (-1); + } + + status = chdir (dir); + if (status != 0) + { + char errbuf[1024]; + ERROR ("change_basedir: chdir (%s): %s", dir, + sstrerror (errno, errbuf, sizeof (errbuf))); + free (dir); + return (-1); + } + + free (dir); + return (0); +} /* static int change_basedir (char *dir) */ + +#if HAVE_LIBKSTAT +static void update_kstat (void) +{ + if (kc == NULL) + { + if ((kc = kstat_open ()) == NULL) + ERROR ("Unable to open kstat control structure"); + } + else + { + kid_t kid; + kid = kstat_chain_update (kc); + if (kid > 0) + { + INFO ("kstat chain has been updated"); + plugin_init_all (); + } + else if (kid < 0) + ERROR ("kstat chain update failed"); + /* else: everything works as expected */ + } + + return; +} /* static void update_kstat (void) */ +#endif /* HAVE_LIBKSTAT */ + +/* TODO + * Remove all settings but `-f' and `-C' + */ +static void exit_usage (int status) +{ + printf ("Usage: "PACKAGE_NAME" [OPTIONS]\n\n" + + "Available options:\n" + " General:\n" + " -C Configuration file.\n" + " Default: "CONFIGFILE"\n" + " -t Test config and exit.\n" + " -T Test plugin read and exit.\n" + " -P PID-file.\n" + " Default: "PIDFILE"\n" +#if COLLECT_DAEMON + " -f Don't fork to the background.\n" +#endif + " -h Display help (this message)\n" + "\nBuiltin defaults:\n" + " Config file "CONFIGFILE"\n" + " PID file "PIDFILE"\n" + " Plugin directory "PLUGINDIR"\n" + " Data directory "PKGLOCALSTATEDIR"\n" + "\n"PACKAGE_NAME" "PACKAGE_VERSION", http://collectd.org/\n" + "by Florian octo Forster \n" + "for contributions see `AUTHORS'\n"); + exit (status); +} /* static void exit_usage (int status) */ + +static int do_init (void) +{ +#if HAVE_SETLOCALE + if (setlocale (LC_NUMERIC, COLLECTD_LOCALE) == NULL) + WARNING ("setlocale (\"%s\") failed.", COLLECTD_LOCALE); +#endif + +#if HAVE_LIBKSTAT + kc = NULL; + update_kstat (); +#endif + +#if HAVE_LIBSTATGRAB + if (sg_init ( +# if HAVE_LIBSTATGRAB_0_90 + 0 +# endif + )) + { + ERROR ("sg_init: %s", sg_str_error (sg_get_error ())); + return (-1); + } + + if (sg_drop_privileges ()) + { + ERROR ("sg_drop_privileges: %s", sg_str_error (sg_get_error ())); + return (-1); + } +#endif + + plugin_init_all (); + + return (0); +} /* int do_init () */ + + +static int do_loop (void) +{ + cdtime_t interval = cf_get_default_interval (); + cdtime_t wait_until; + + wait_until = cdtime () + interval; + + while (loop == 0) + { + struct timespec ts_wait = { 0, 0 }; + cdtime_t now; + +#if HAVE_LIBKSTAT + update_kstat (); +#endif + + /* Issue all plugins */ + plugin_read_all (); + + now = cdtime (); + if (now >= wait_until) + { + WARNING ("Not sleeping because the next interval is " + "%.3f seconds in the past!", + CDTIME_T_TO_DOUBLE (now - wait_until)); + wait_until = now + interval; + continue; + } + + CDTIME_T_TO_TIMESPEC (wait_until - now, &ts_wait); + wait_until = wait_until + interval; + + while ((loop == 0) && (nanosleep (&ts_wait, &ts_wait) != 0)) + { + if (errno != EINTR) + { + char errbuf[1024]; + ERROR ("nanosleep failed: %s", + sstrerror (errno, errbuf, + sizeof (errbuf))); + return (-1); + } + } + } /* while (loop == 0) */ + + return (0); +} /* int do_loop */ + +static int do_shutdown (void) +{ + plugin_shutdown_all (); + return (0); +} /* int do_shutdown */ + +#if COLLECT_DAEMON +static int pidfile_create (void) +{ + FILE *fh; + const char *file = global_option_get ("PIDFile"); + + if ((fh = fopen (file, "w")) == NULL) + { + char errbuf[1024]; + ERROR ("fopen (%s): %s", file, + sstrerror (errno, errbuf, sizeof (errbuf))); + return (1); + } + + fprintf (fh, "%i\n", (int) getpid ()); + fclose(fh); + + return (0); +} /* static int pidfile_create (const char *file) */ + +static int pidfile_remove (void) +{ + const char *file = global_option_get ("PIDFile"); ++ if (file == NULL) ++ return 0; + - DEBUG ("unlink (%s)", (file != NULL) ? file : ""); + return (unlink (file)); +} /* static int pidfile_remove (const char *file) */ +#endif /* COLLECT_DAEMON */ + +#ifdef KERNEL_LINUX +int notify_upstart (void) +{ + const char *upstart_job = getenv("UPSTART_JOB"); + + if (upstart_job == NULL) + return 0; + + if (strcmp(upstart_job, "collectd") != 0) + return 0; + + WARNING ("supervised by upstart, will stop to signal readyness"); + raise(SIGSTOP); + unsetenv("UPSTART_JOB"); + + return 1; +} + +int notify_systemd (void) +{ + int fd = -1; + const char *notifysocket = getenv("NOTIFY_SOCKET"); + struct sockaddr_un su; + struct iovec iov; + struct msghdr hdr; + + if (notifysocket == NULL) + return 0; + + if ((strchr("@/", notifysocket[0])) == NULL || + strlen(notifysocket) < 2) + return 0; + + WARNING ("supervised by systemd, will signal readyness"); + if ((fd = socket(AF_UNIX, SOCK_DGRAM, 0)) < 0) { + WARNING ("cannot contact systemd socket %s", notifysocket); + return 0; + } + + bzero(&su, sizeof(su)); + su.sun_family = AF_UNIX; + sstrncpy (su.sun_path, notifysocket, sizeof(su.sun_path)); + + if (notifysocket[0] == '@') + su.sun_path[0] = 0; + + bzero(&iov, sizeof(iov)); + iov.iov_base = "READY=1"; + iov.iov_len = strlen("READY=1"); + + bzero(&hdr, sizeof(hdr)); + hdr.msg_name = &su; + hdr.msg_namelen = offsetof(struct sockaddr_un, sun_path) + + strlen(notifysocket); + hdr.msg_iov = &iov; + hdr.msg_iovlen = 1; + + unsetenv("NOTIFY_SOCKET"); + if (sendmsg(fd, &hdr, MSG_NOSIGNAL) < 0) { + WARNING ("cannot send notification to systemd"); + close(fd); + return 0; + } + close(fd); + return 1; +} +#endif /* KERNEL_LINUX */ + +int main (int argc, char **argv) +{ + struct sigaction sig_int_action; + struct sigaction sig_term_action; + struct sigaction sig_usr1_action; + struct sigaction sig_pipe_action; + char *configfile = CONFIGFILE; + int test_config = 0; + int test_readall = 0; + const char *basedir; +#if COLLECT_DAEMON + struct sigaction sig_chld_action; + pid_t pid; + int daemonize = 1; +#endif + int exit_status = 0; + + /* read options */ + while (1) + { + int c; + + c = getopt (argc, argv, "htTC:" +#if COLLECT_DAEMON + "fP:" +#endif + ); + + if (c == -1) + break; + + switch (c) + { + case 'C': + configfile = optarg; + break; + case 't': + test_config = 1; + break; + case 'T': + test_readall = 1; + global_option_set ("ReadThreads", "-1"); +#if COLLECT_DAEMON + daemonize = 0; +#endif /* COLLECT_DAEMON */ + break; +#if COLLECT_DAEMON + case 'P': + global_option_set ("PIDFile", optarg); + pidfile_from_cli = 1; + break; + case 'f': + daemonize = 0; + break; +#endif /* COLLECT_DAEMON */ + case 'h': + exit_usage (0); + break; + default: + exit_usage (1); + } /* switch (c) */ + } /* while (1) */ + + if (optind < argc) + exit_usage (1); + + plugin_init_ctx (); + + /* + * Read options from the config file, the environment and the command + * line (in that order, with later options overwriting previous ones in + * general). + * Also, this will automatically load modules. + */ + if (cf_read (configfile)) + { + fprintf (stderr, "Error: Reading the config file failed!\n" + "Read the syslog for details.\n"); + return (1); + } + + /* + * Change directory. We do this _after_ reading the config and loading + * modules to relative paths work as expected. + */ + if ((basedir = global_option_get ("BaseDir")) == NULL) + { + fprintf (stderr, "Don't have a basedir to use. This should not happen. Ever."); + return (1); + } + else if (change_basedir (basedir)) + { + fprintf (stderr, "Error: Unable to change to directory `%s'.\n", basedir); + return (1); + } + + /* + * Set global variables or, if that failes, exit. We cannot run with + * them being uninitialized. If nothing is configured, then defaults + * are being used. So this means that the user has actually done + * something wrong. + */ + if (init_global_variables () != 0) + return (1); + + if (test_config) + return (0); + +#if COLLECT_DAEMON + /* + * fork off child + */ + memset (&sig_chld_action, '\0', sizeof (sig_chld_action)); + sig_chld_action.sa_handler = SIG_IGN; + sigaction (SIGCHLD, &sig_chld_action, NULL); + + /* + * Only daemonize if we're not being supervised + * by upstart or systemd (when using Linux). + */ + if (daemonize +#ifdef KERNEL_LINUX + && notify_upstart() == 0 && notify_systemd() == 0 +#endif + ) + { + if ((pid = fork ()) == -1) + { + /* error */ + char errbuf[1024]; + fprintf (stderr, "fork: %s", + sstrerror (errno, errbuf, + sizeof (errbuf))); + return (1); + } + else if (pid != 0) + { + /* parent */ + /* printf ("Running (PID %i)\n", pid); */ + return (0); + } + + /* Detach from session */ + setsid (); + + /* Write pidfile */ + if (pidfile_create ()) + exit (2); + + /* close standard descriptors */ + close (2); + close (1); + close (0); + + if (open ("/dev/null", O_RDWR) != 0) + { + ERROR ("Error: Could not connect `STDIN' to `/dev/null'"); + return (1); + } + if (dup (0) != 1) + { + ERROR ("Error: Could not connect `STDOUT' to `/dev/null'"); + return (1); + } + if (dup (0) != 2) + { + ERROR ("Error: Could not connect `STDERR' to `/dev/null'"); + return (1); + } + } /* if (daemonize) */ +#endif /* COLLECT_DAEMON */ + + memset (&sig_pipe_action, '\0', sizeof (sig_pipe_action)); + sig_pipe_action.sa_handler = SIG_IGN; + sigaction (SIGPIPE, &sig_pipe_action, NULL); + + /* + * install signal handlers + */ + memset (&sig_int_action, '\0', sizeof (sig_int_action)); + sig_int_action.sa_handler = sig_int_handler; + if (0 != sigaction (SIGINT, &sig_int_action, NULL)) { + char errbuf[1024]; + ERROR ("Error: Failed to install a signal handler for signal INT: %s", + sstrerror (errno, errbuf, sizeof (errbuf))); + return (1); + } + + memset (&sig_term_action, '\0', sizeof (sig_term_action)); + sig_term_action.sa_handler = sig_term_handler; + if (0 != sigaction (SIGTERM, &sig_term_action, NULL)) { + char errbuf[1024]; + ERROR ("Error: Failed to install a signal handler for signal TERM: %s", + sstrerror (errno, errbuf, sizeof (errbuf))); + return (1); + } + + memset (&sig_usr1_action, '\0', sizeof (sig_usr1_action)); + sig_usr1_action.sa_handler = sig_usr1_handler; + if (0 != sigaction (SIGUSR1, &sig_usr1_action, NULL)) { + char errbuf[1024]; + ERROR ("Error: Failed to install a signal handler for signal USR1: %s", + sstrerror (errno, errbuf, sizeof (errbuf))); + return (1); + } + + /* + * run the actual loops + */ + do_init (); + + if (test_readall) + { + if (plugin_read_all_once () != 0) + exit_status = 1; + } + else + { + INFO ("Initialization complete, entering read-loop."); + do_loop (); + } + + /* close syslog */ + INFO ("Exiting normally."); + + do_shutdown (); + +#if COLLECT_DAEMON + if (daemonize) + pidfile_remove (); +#endif /* COLLECT_DAEMON */ + + return (exit_status); +} /* int main */ diff --cc src/daemon/configfile.c index 765a23c4,00000000..8fc6f7d7 mode 100644,000000..100644 --- a/src/daemon/configfile.c +++ b/src/daemon/configfile.c @@@ -1,1365 -1,0 +1,1369 @@@ +/** + * collectd - src/configfile.c + * Copyright (C) 2005-2011 Florian octo Forster + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + * + * Authors: + * Florian octo Forster + * Sebastian tokkee Harl + **/ + +#include "collectd.h" + +#include "liboconfig/oconfig.h" + +#include "common.h" +#include "plugin.h" +#include "configfile.h" +#include "types_list.h" +#include "filter_chain.h" + +#if HAVE_WORDEXP_H +# include +#endif /* HAVE_WORDEXP_H */ + +#if HAVE_FNMATCH_H +# include +#endif /* HAVE_FNMATCH_H */ + +#if HAVE_LIBGEN_H +# include +#endif /* HAVE_LIBGEN_H */ + +#define ESCAPE_NULL(str) ((str) == NULL ? "(null)" : (str)) + +/* + * Private types + */ +typedef struct cf_callback +{ + const char *type; + int (*callback) (const char *, const char *); + const char **keys; + int keys_num; + plugin_ctx_t ctx; + struct cf_callback *next; +} cf_callback_t; + +typedef struct cf_complex_callback_s +{ + char *type; + int (*callback) (oconfig_item_t *); + plugin_ctx_t ctx; + struct cf_complex_callback_s *next; +} cf_complex_callback_t; + +typedef struct cf_value_map_s +{ + char *key; + int (*func) (oconfig_item_t *); +} cf_value_map_t; + +typedef struct cf_global_option_s +{ + char *key; + char *value; + char *def; +} cf_global_option_t; + +/* + * Prototypes of callback functions + */ +static int dispatch_value_typesdb (oconfig_item_t *ci); +static int dispatch_value_plugindir (oconfig_item_t *ci); +static int dispatch_loadplugin (oconfig_item_t *ci); +static int dispatch_block_plugin (oconfig_item_t *ci); + +/* + * Private variables + */ +static cf_callback_t *first_callback = NULL; +static cf_complex_callback_t *complex_callback_head = NULL; + +static cf_value_map_t cf_value_map[] = +{ + {"TypesDB", dispatch_value_typesdb}, + {"PluginDir", dispatch_value_plugindir}, + {"LoadPlugin", dispatch_loadplugin}, + {"Plugin", dispatch_block_plugin} +}; +static int cf_value_map_num = STATIC_ARRAY_SIZE (cf_value_map); + +static cf_global_option_t cf_global_options[] = +{ + {"BaseDir", NULL, PKGLOCALSTATEDIR}, + {"PIDFile", NULL, PIDFILE}, + {"Hostname", NULL, NULL}, + {"FQDNLookup", NULL, "true"}, + {"Interval", NULL, NULL}, + {"ReadThreads", NULL, "5"}, + {"WriteThreads", NULL, "5"}, + {"WriteQueueLimitHigh", NULL, NULL}, + {"WriteQueueLimitLow", NULL, NULL}, + {"Timeout", NULL, "2"}, + {"AutoLoadPlugin", NULL, "false"}, + {"CollectInternalStats", NULL, "false"}, + {"PreCacheChain", NULL, "PreCache"}, + {"PostCacheChain", NULL, "PostCache"}, + {"MaxReadInterval", NULL, "86400"} +}; +static int cf_global_options_num = STATIC_ARRAY_SIZE (cf_global_options); + +static int cf_default_typesdb = 1; + +/* + * Functions to handle register/unregister, search, and other plugin related + * stuff + */ +static cf_callback_t *cf_search (const char *type) +{ + cf_callback_t *cf_cb; + + if (type == NULL) + return (NULL); + + for (cf_cb = first_callback; cf_cb != NULL; cf_cb = cf_cb->next) + if (strcasecmp (cf_cb->type, type) == 0) + break; + + return (cf_cb); +} + +static int cf_dispatch (const char *type, const char *orig_key, + const char *orig_value) +{ + cf_callback_t *cf_cb; + plugin_ctx_t old_ctx; + char *key; + char *value; + int ret; + int i; + ++ if (orig_key == NULL) ++ return (EINVAL); ++ + DEBUG ("type = %s, key = %s, value = %s", + ESCAPE_NULL(type), - ESCAPE_NULL(orig_key), ++ orig_key, + ESCAPE_NULL(orig_value)); + + if ((cf_cb = cf_search (type)) == NULL) + { + WARNING ("Found a configuration for the `%s' plugin, but " + "the plugin isn't loaded or didn't register " + "a configuration callback.", type); + return (-1); + } + + if ((key = strdup (orig_key)) == NULL) + return (1); + if ((value = strdup (orig_value)) == NULL) + { + free (key); + return (2); + } + + ret = -1; + + old_ctx = plugin_set_ctx (cf_cb->ctx); + + for (i = 0; i < cf_cb->keys_num; i++) + { + if ((cf_cb->keys[i] != NULL) + && (strcasecmp (cf_cb->keys[i], key) == 0)) + { + ret = (*cf_cb->callback) (key, value); + break; + } + } + + plugin_set_ctx (old_ctx); + + if (i >= cf_cb->keys_num) + WARNING ("Plugin `%s' did not register for value `%s'.", type, key); + + free (key); + free (value); + - DEBUG ("cf_dispatch: return (%i)", ret); - + return (ret); +} /* int cf_dispatch */ + +static int dispatch_global_option (const oconfig_item_t *ci) +{ + if (ci->values_num != 1) + return (-1); + if (ci->values[0].type == OCONFIG_TYPE_STRING) + return (global_option_set (ci->key, ci->values[0].value.string)); + else if (ci->values[0].type == OCONFIG_TYPE_NUMBER) + { + char tmp[128]; + ssnprintf (tmp, sizeof (tmp), "%lf", ci->values[0].value.number); + return (global_option_set (ci->key, tmp)); + } + else if (ci->values[0].type == OCONFIG_TYPE_BOOLEAN) + { + if (ci->values[0].value.boolean) + return (global_option_set (ci->key, "true")); + else + return (global_option_set (ci->key, "false")); + } + + return (-1); +} /* int dispatch_global_option */ + +static int dispatch_value_typesdb (oconfig_item_t *ci) +{ + int i = 0; + + assert (strcasecmp (ci->key, "TypesDB") == 0); + + cf_default_typesdb = 0; + + if (ci->values_num < 1) { + ERROR ("configfile: `TypesDB' needs at least one argument."); + return (-1); + } + + for (i = 0; i < ci->values_num; ++i) + { + if (OCONFIG_TYPE_STRING != ci->values[i].type) { + WARNING ("configfile: TypesDB: Skipping %i. argument which " + "is not a string.", i + 1); + continue; + } + + read_types_list (ci->values[i].value.string); + } + return (0); +} /* int dispatch_value_typesdb */ + +static int dispatch_value_plugindir (oconfig_item_t *ci) +{ + assert (strcasecmp (ci->key, "PluginDir") == 0); + + if (ci->values_num != 1) + return (-1); + if (ci->values[0].type != OCONFIG_TYPE_STRING) + return (-1); + + plugin_set_dir (ci->values[0].value.string); + return (0); +} + +static int dispatch_loadplugin (oconfig_item_t *ci) +{ + int i; + const char *name; + unsigned int flags = 0; + plugin_ctx_t ctx; + plugin_ctx_t old_ctx; + int ret_val; + + assert (strcasecmp (ci->key, "LoadPlugin") == 0); + + if (ci->values_num != 1) + return (-1); + if (ci->values[0].type != OCONFIG_TYPE_STRING) + return (-1); + + name = ci->values[0].value.string; + if (strcmp ("libvirt", name) == 0) + name = "virt"; + + /* default to the global interval set before loading this plugin */ + memset (&ctx, 0, sizeof (ctx)); + ctx.interval = cf_get_default_interval (); + + for (i = 0; i < ci->children_num; ++i) { + if (strcasecmp("Globals", ci->children[i].key) == 0) + cf_util_get_flag (ci->children + i, &flags, PLUGIN_FLAGS_GLOBAL); + else if (strcasecmp ("Interval", ci->children[i].key) == 0) { + if (cf_util_get_cdtime (ci->children + i, &ctx.interval) != 0) { + /* cf_util_get_cdtime will log an error */ + continue; + } + } + else { + WARNING("Ignoring unknown LoadPlugin option \"%s\" " + "for plugin \"%s\"", + ci->children[i].key, ci->values[0].value.string); + } + } + + old_ctx = plugin_set_ctx (ctx); + ret_val = plugin_load (name, (uint32_t) flags); + /* reset to the "global" context */ + plugin_set_ctx (old_ctx); + + return (ret_val); +} /* int dispatch_value_loadplugin */ + +static int dispatch_value_plugin (const char *plugin, oconfig_item_t *ci) +{ + char buffer[4096]; + char *buffer_ptr; + int buffer_free; + int i; + + buffer_ptr = buffer; + buffer_free = sizeof (buffer); + + for (i = 0; i < ci->values_num; i++) + { + int status = -1; + + if (ci->values[i].type == OCONFIG_TYPE_STRING) + status = ssnprintf (buffer_ptr, buffer_free, " %s", + ci->values[i].value.string); + else if (ci->values[i].type == OCONFIG_TYPE_NUMBER) + status = ssnprintf (buffer_ptr, buffer_free, " %lf", + ci->values[i].value.number); + else if (ci->values[i].type == OCONFIG_TYPE_BOOLEAN) + status = ssnprintf (buffer_ptr, buffer_free, " %s", + ci->values[i].value.boolean + ? "true" : "false"); + + if ((status < 0) || (status >= buffer_free)) + return (-1); + buffer_free -= status; + buffer_ptr += status; + } + /* skip the initial space */ + buffer_ptr = buffer + 1; + + return (cf_dispatch (plugin, ci->key, buffer_ptr)); +} /* int dispatch_value_plugin */ + +static int dispatch_value (oconfig_item_t *ci) +{ + int ret = -2; + int i; + + for (i = 0; i < cf_value_map_num; i++) + if (strcasecmp (cf_value_map[i].key, ci->key) == 0) + { + ret = cf_value_map[i].func (ci); + break; + } + + for (i = 0; i < cf_global_options_num; i++) + if (strcasecmp (cf_global_options[i].key, ci->key) == 0) + { + ret = dispatch_global_option (ci); + break; + } + + return (ret); +} /* int dispatch_value */ + +static int dispatch_block_plugin (oconfig_item_t *ci) +{ + int i; + char *name; + + cf_complex_callback_t *cb; + + if (strcasecmp (ci->key, "Plugin") != 0) + return (-1); + if (ci->values_num < 1) + return (-1); + if (ci->values[0].type != OCONFIG_TYPE_STRING) + return (-1); + + name = ci->values[0].value.string; + if (strcmp ("libvirt", name) == 0) + { + /* TODO(octo): Remove this legacy. */ + WARNING ("The \"libvirt\" plugin has been renamed to \"virt\" to avoid problems with the build system. " + "Your configuration is still using the old name. " + "Please change it to use \"virt\" as soon as possible. " + "This compatibility code will go away eventually."); + name = "virt"; + } + + if (IS_TRUE (global_option_get ("AutoLoadPlugin"))) + { + plugin_ctx_t ctx; + plugin_ctx_t old_ctx; + int status; + + /* default to the global interval set before loading this plugin */ + memset (&ctx, 0, sizeof (ctx)); + ctx.interval = cf_get_default_interval (); + + old_ctx = plugin_set_ctx (ctx); + status = plugin_load (name, /* flags = */ 0); + /* reset to the "global" context */ + plugin_set_ctx (old_ctx); + + if (status != 0) + { + ERROR ("Automatically loading plugin \"%s\" failed " + "with status %i.", name, status); + return (status); + } + } + + /* Check for a complex callback first */ + for (cb = complex_callback_head; cb != NULL; cb = cb->next) + { + if (strcasecmp (name, cb->type) == 0) + { + plugin_ctx_t old_ctx; + int ret_val; + + old_ctx = plugin_set_ctx (cb->ctx); + ret_val = (cb->callback (ci)); + plugin_set_ctx (old_ctx); + return (ret_val); + } + } + + /* Hm, no complex plugin found. Dispatch the values one by one */ + for (i = 0; i < ci->children_num; i++) + { + if (ci->children[i].children == NULL) + dispatch_value_plugin (name, ci->children + i); + else + { + WARNING ("There is a `%s' block within the " + "configuration for the %s plugin. " + "The plugin either only expects " + "\"simple\" configuration statements " + "or wasn't loaded using `LoadPlugin'." + " Please check your configuration.", + ci->children[i].key, name); + } + } + + return (0); +} + + +static int dispatch_block (oconfig_item_t *ci) +{ + if (strcasecmp (ci->key, "LoadPlugin") == 0) + return (dispatch_loadplugin (ci)); + else if (strcasecmp (ci->key, "Plugin") == 0) + return (dispatch_block_plugin (ci)); + else if (strcasecmp (ci->key, "Chain") == 0) + return (fc_configure (ci)); + + return (0); +} + +static int cf_ci_replace_child (oconfig_item_t *dst, oconfig_item_t *src, + int offset) +{ + oconfig_item_t *temp; + int i; + + assert (offset >= 0); + assert (dst->children_num > offset); + + /* Free the memory used by the replaced child. Usually that's the + * `Include "blah"' statement. */ + temp = dst->children + offset; + for (i = 0; i < temp->values_num; i++) + { + if (temp->values[i].type == OCONFIG_TYPE_STRING) + { + sfree (temp->values[i].value.string); + } + } + sfree (temp->values); + temp = NULL; + + /* If (src->children_num == 0) the array size is decreased. If offset + * is _not_ the last element, (offset < (dst->children_num - 1)), then + * we need to move the trailing elements before resizing the array. */ + if ((src->children_num == 0) && (offset < (dst->children_num - 1))) + { + int nmemb = dst->children_num - (offset + 1); + memmove (dst->children + offset, dst->children + offset + 1, + sizeof (oconfig_item_t) * nmemb); + } + + /* Resize the memory containing the children to be big enough to hold + * all children. */ + if (dst->children_num + src->children_num - 1 == 0) + { + dst->children_num = 0; + return (0); + } + + temp = (oconfig_item_t *) realloc (dst->children, + sizeof (oconfig_item_t) + * (dst->children_num + src->children_num - 1)); + if (temp == NULL) + { + ERROR ("configfile: realloc failed."); + return (-1); + } + dst->children = temp; + + /* If there are children behind the include statement, and they have + * not yet been moved because (src->children_num == 0), then move them + * to the end of the list, so that the new children have room before + * them. */ + if ((src->children_num > 0) + && ((dst->children_num - (offset + 1)) > 0)) + { + int nmemb = dst->children_num - (offset + 1); + int old_offset = offset + 1; + int new_offset = offset + src->children_num; + + memmove (dst->children + new_offset, + dst->children + old_offset, + sizeof (oconfig_item_t) * nmemb); + } + + /* Last but not least: If there are new children, copy them to the + * memory reserved for them. */ + if (src->children_num > 0) + { + memcpy (dst->children + offset, + src->children, + sizeof (oconfig_item_t) * src->children_num); + } + + /* Update the number of children. */ + dst->children_num += (src->children_num - 1); + + return (0); +} /* int cf_ci_replace_child */ + +static int cf_ci_append_children (oconfig_item_t *dst, oconfig_item_t *src) +{ + oconfig_item_t *temp; + + if ((src == NULL) || (src->children_num == 0)) + return (0); + + temp = (oconfig_item_t *) realloc (dst->children, + sizeof (oconfig_item_t) + * (dst->children_num + src->children_num)); + if (temp == NULL) + { + ERROR ("configfile: realloc failed."); + return (-1); + } + dst->children = temp; + + memcpy (dst->children + dst->children_num, + src->children, + sizeof (oconfig_item_t) + * src->children_num); + dst->children_num += src->children_num; + + return (0); +} /* int cf_ci_append_children */ + +#define CF_MAX_DEPTH 8 +static oconfig_item_t *cf_read_generic (const char *path, + const char *pattern, int depth); + +static int cf_include_all (oconfig_item_t *root, int depth) +{ + int i; + + for (i = 0; i < root->children_num; i++) + { + oconfig_item_t *new; + oconfig_item_t *old; + + char *pattern = NULL; + + int j; + + if (strcasecmp (root->children[i].key, "Include") != 0) + continue; + + old = root->children + i; + + if ((old->values_num != 1) + || (old->values[0].type != OCONFIG_TYPE_STRING)) + { + ERROR ("configfile: `Include' needs exactly one string argument."); + continue; + } + + for (j = 0; j < old->children_num; ++j) + { + oconfig_item_t *child = old->children + j; + + if (strcasecmp (child->key, "Filter") == 0) + cf_util_get_string (child, &pattern); + else + ERROR ("configfile: Option `%s' not allowed in block.", + child->key); + } + + new = cf_read_generic (old->values[0].value.string, pattern, depth + 1); + sfree (pattern); + + if (new == NULL) + return (-1); + + /* Now replace the i'th child in `root' with `new'. */ + if (cf_ci_replace_child (root, new, i) < 0) + return (-1); + + /* ... and go back to the new i'th child. */ + --i; + + sfree (new->values); + sfree (new); + } /* for (i = 0; i < root->children_num; i++) */ + + return (0); +} /* int cf_include_all */ + +static oconfig_item_t *cf_read_file (const char *file, + const char *pattern, int depth) +{ + oconfig_item_t *root; + int status; + + assert (depth < CF_MAX_DEPTH); + + if (pattern != NULL) { +#if HAVE_FNMATCH_H && HAVE_LIBGEN_H + char *tmp = sstrdup (file); + char *filename = basename (tmp); + + if ((filename != NULL) && (fnmatch (pattern, filename, 0) != 0)) { + DEBUG ("configfile: Not including `%s' because it " + "does not match pattern `%s'.", + filename, pattern); + free (tmp); + return (NULL); + } + + free (tmp); +#else + ERROR ("configfile: Cannot apply pattern filter '%s' " + "to file '%s': functions basename() and / or " + "fnmatch() not available.", pattern, file); +#endif /* HAVE_FNMATCH_H && HAVE_LIBGEN_H */ + } + + root = oconfig_parse_file (file); + if (root == NULL) + { + ERROR ("configfile: Cannot read file `%s'.", file); + return (NULL); + } + + status = cf_include_all (root, depth); + if (status != 0) + { + oconfig_free (root); + return (NULL); + } + + return (root); +} /* oconfig_item_t *cf_read_file */ + +static int cf_compare_string (const void *p1, const void *p2) +{ + return strcmp (*(const char **) p1, *(const char **) p2); +} + +static oconfig_item_t *cf_read_dir (const char *dir, + const char *pattern, int depth) +{ + oconfig_item_t *root = NULL; + DIR *dh; + struct dirent *de; + char **filenames = NULL; + int filenames_num = 0; + int status; + int i; + + assert (depth < CF_MAX_DEPTH); + + dh = opendir (dir); + if (dh == NULL) + { + char errbuf[1024]; + ERROR ("configfile: opendir failed: %s", + sstrerror (errno, errbuf, sizeof (errbuf))); + return (NULL); + } + + root = (oconfig_item_t *) malloc (sizeof (oconfig_item_t)); + if (root == NULL) + { + ERROR ("configfile: malloc failed."); + return (NULL); + } + memset (root, 0, sizeof (oconfig_item_t)); + + while ((de = readdir (dh)) != NULL) + { + char name[1024]; + char **tmp; + + if ((de->d_name[0] == '.') || (de->d_name[0] == 0)) + continue; + + status = ssnprintf (name, sizeof (name), "%s/%s", + dir, de->d_name); + if ((status < 0) || ((size_t) status >= sizeof (name))) + { + ERROR ("configfile: Not including `%s/%s' because its" + " name is too long.", + dir, de->d_name); + for (i = 0; i < filenames_num; ++i) + free (filenames[i]); + free (filenames); + free (root); + return (NULL); + } + + ++filenames_num; + tmp = (char **) realloc (filenames, + filenames_num * sizeof (*filenames)); + if (tmp == NULL) { + ERROR ("configfile: realloc failed."); + for (i = 0; i < filenames_num - 1; ++i) + free (filenames[i]); + free (filenames); + free (root); + return (NULL); + } + filenames = tmp; + + filenames[filenames_num - 1] = sstrdup (name); + } + ++ if (filenames == NULL) ++ return (root); ++ + qsort ((void *) filenames, filenames_num, sizeof (*filenames), + cf_compare_string); + + for (i = 0; i < filenames_num; ++i) + { + oconfig_item_t *temp; + char *name = filenames[i]; + + temp = cf_read_generic (name, pattern, depth); + if (temp == NULL) + { + /* An error should already have been reported. */ + sfree (name); + continue; + } + + cf_ci_append_children (root, temp); + sfree (temp->children); + sfree (temp); + + free (name); + } + + free(filenames); + return (root); +} /* oconfig_item_t *cf_read_dir */ + +/* + * cf_read_generic + * + * Path is stat'ed and either cf_read_file or cf_read_dir is called + * accordingly. + * + * There are two versions of this function: If `wordexp' exists shell wildcards + * will be expanded and the function will include all matches found. If + * `wordexp' (or, more precisely, it's header file) is not available the + * simpler function is used which does not do any such expansion. + */ +#if HAVE_WORDEXP_H +static oconfig_item_t *cf_read_generic (const char *path, + const char *pattern, int depth) +{ + oconfig_item_t *root = NULL; + int status; + const char *path_ptr; + wordexp_t we; + size_t i; + + if (depth >= CF_MAX_DEPTH) + { + ERROR ("configfile: Not including `%s' because the maximum " + "nesting depth has been reached.", path); + return (NULL); + } + + status = wordexp (path, &we, WRDE_NOCMD); + if (status != 0) + { + ERROR ("configfile: wordexp (%s) failed.", path); + return (NULL); + } + + root = (oconfig_item_t *) malloc (sizeof (oconfig_item_t)); + if (root == NULL) + { + ERROR ("configfile: malloc failed."); + return (NULL); + } + memset (root, '\0', sizeof (oconfig_item_t)); + + /* wordexp() might return a sorted list already. That's not + * documented though, so let's make sure we get what we want. */ + qsort ((void *) we.we_wordv, we.we_wordc, sizeof (*we.we_wordv), + cf_compare_string); + + for (i = 0; i < we.we_wordc; i++) + { + oconfig_item_t *temp; + struct stat statbuf; + + path_ptr = we.we_wordv[i]; + + status = stat (path_ptr, &statbuf); + if (status != 0) + { + char errbuf[1024]; + WARNING ("configfile: stat (%s) failed: %s", + path_ptr, + sstrerror (errno, errbuf, sizeof (errbuf))); + continue; + } + + if (S_ISREG (statbuf.st_mode)) + temp = cf_read_file (path_ptr, pattern, depth); + else if (S_ISDIR (statbuf.st_mode)) + temp = cf_read_dir (path_ptr, pattern, depth); + else + { + WARNING ("configfile: %s is neither a file nor a " + "directory.", path); + continue; + } + + if (temp == NULL) { + oconfig_free (root); + return (NULL); + } + + cf_ci_append_children (root, temp); + sfree (temp->children); + sfree (temp); + } + + wordfree (&we); + + return (root); +} /* oconfig_item_t *cf_read_generic */ +/* #endif HAVE_WORDEXP_H */ + +#else /* if !HAVE_WORDEXP_H */ +static oconfig_item_t *cf_read_generic (const char *path, + const char *pattern, int depth) +{ + struct stat statbuf; + int status; + + if (depth >= CF_MAX_DEPTH) + { + ERROR ("configfile: Not including `%s' because the maximum " + "nesting depth has been reached.", path); + return (NULL); + } + + status = stat (path, &statbuf); + if (status != 0) + { + char errbuf[1024]; + ERROR ("configfile: stat (%s) failed: %s", + path, + sstrerror (errno, errbuf, sizeof (errbuf))); + return (NULL); + } + + if (S_ISREG (statbuf.st_mode)) + return (cf_read_file (path, pattern, depth)); + else if (S_ISDIR (statbuf.st_mode)) + return (cf_read_dir (path, pattern, depth)); + + ERROR ("configfile: %s is neither a file nor a directory.", path); + return (NULL); +} /* oconfig_item_t *cf_read_generic */ +#endif /* !HAVE_WORDEXP_H */ + +/* + * Public functions + */ +int global_option_set (const char *option, const char *value) +{ + int i; + + DEBUG ("option = %s; value = %s;", option, value); + + for (i = 0; i < cf_global_options_num; i++) + if (strcasecmp (cf_global_options[i].key, option) == 0) + break; + + if (i >= cf_global_options_num) + return (-1); + + if (strcasecmp (option, "PIDFile") == 0 && pidfile_from_cli == 1) + { + DEBUG ("Configfile: Ignoring `PIDFILE' option because " + "command-line option `-P' take precedence."); + return (0); + } + + sfree (cf_global_options[i].value); + + if (value != NULL) + cf_global_options[i].value = strdup (value); + else + cf_global_options[i].value = NULL; + + return (0); +} + +const char *global_option_get (const char *option) +{ + int i; + + for (i = 0; i < cf_global_options_num; i++) + if (strcasecmp (cf_global_options[i].key, option) == 0) + break; + + if (i >= cf_global_options_num) + return (NULL); + + return ((cf_global_options[i].value != NULL) + ? cf_global_options[i].value + : cf_global_options[i].def); +} /* char *global_option_get */ + +long global_option_get_long (const char *option, long default_value) +{ + const char *str; + long value; + + str = global_option_get (option); + if (NULL == str) + return (default_value); + + errno = 0; + value = strtol (str, /* endptr = */ NULL, /* base = */ 0); + if (errno != 0) + return (default_value); + + return (value); +} /* char *global_option_get_long */ + +cdtime_t global_option_get_time (const char *name, cdtime_t def) /* {{{ */ +{ + char const *optstr; + char *endptr = NULL; + double v; + + optstr = global_option_get (name); + if (optstr == NULL) + return (def); + + errno = 0; + v = strtod (optstr, &endptr); + if ((endptr == NULL) || (*endptr != 0) || (errno != 0)) + return (def); + else if (v <= 0.0) + return (def); + + return (DOUBLE_TO_CDTIME_T (v)); +} /* }}} cdtime_t global_option_get_time */ + +cdtime_t cf_get_default_interval (void) +{ + return (global_option_get_time ("Interval", + DOUBLE_TO_CDTIME_T (COLLECTD_DEFAULT_INTERVAL))); +} + +void cf_unregister (const char *type) +{ + cf_callback_t *this, *prev; + + for (prev = NULL, this = first_callback; + this != NULL; + prev = this, this = this->next) + if (strcasecmp (this->type, type) == 0) + { + if (prev == NULL) + first_callback = this->next; + else + prev->next = this->next; + + free (this); + break; + } +} /* void cf_unregister */ + +void cf_unregister_complex (const char *type) +{ + cf_complex_callback_t *this, *prev; + + for (prev = NULL, this = complex_callback_head; + this != NULL; + prev = this, this = this->next) + if (strcasecmp (this->type, type) == 0) + { + if (prev == NULL) + complex_callback_head = this->next; + else + prev->next = this->next; + + sfree (this->type); + sfree (this); + break; + } +} /* void cf_unregister */ + +void cf_register (const char *type, + int (*callback) (const char *, const char *), + const char **keys, int keys_num) +{ + cf_callback_t *cf_cb; + + /* Remove this module from the list, if it already exists */ + cf_unregister (type); + + /* This pointer will be free'd in `cf_unregister' */ + if ((cf_cb = (cf_callback_t *) malloc (sizeof (cf_callback_t))) == NULL) + return; + + cf_cb->type = type; + cf_cb->callback = callback; + cf_cb->keys = keys; + cf_cb->keys_num = keys_num; + cf_cb->ctx = plugin_get_ctx (); + + cf_cb->next = first_callback; + first_callback = cf_cb; +} /* void cf_register */ + +int cf_register_complex (const char *type, int (*callback) (oconfig_item_t *)) +{ + cf_complex_callback_t *new; + + new = (cf_complex_callback_t *) malloc (sizeof (cf_complex_callback_t)); + if (new == NULL) + return (-1); + + new->type = strdup (type); + if (new->type == NULL) + { + sfree (new); + return (-1); + } + + new->callback = callback; + new->next = NULL; + + new->ctx = plugin_get_ctx (); + + if (complex_callback_head == NULL) + { + complex_callback_head = new; + } + else + { + cf_complex_callback_t *last = complex_callback_head; + while (last->next != NULL) + last = last->next; + last->next = new; + } + + return (0); +} /* int cf_register_complex */ + +int cf_read (char *filename) +{ + oconfig_item_t *conf; + int i; + + conf = cf_read_generic (filename, /* pattern = */ NULL, /* depth = */ 0); + if (conf == NULL) + { + ERROR ("Unable to read config file %s.", filename); + return (-1); + } + else if (conf->children_num == 0) + { + ERROR ("Configuration file %s is empty.", filename); + oconfig_free (conf); + return (-1); + } + + for (i = 0; i < conf->children_num; i++) + { + if (conf->children[i].children == NULL) + dispatch_value (conf->children + i); + else + dispatch_block (conf->children + i); + } + + oconfig_free (conf); + + /* Read the default types.db if no `TypesDB' option was given. */ + if (cf_default_typesdb) + read_types_list (PKGDATADIR"/types.db"); + + return (0); +} /* int cf_read */ + +/* Assures the config option is a string, duplicates it and returns the copy in + * "ret_string". If necessary "*ret_string" is freed first. Returns zero upon + * success. */ +int cf_util_get_string (const oconfig_item_t *ci, char **ret_string) /* {{{ */ +{ + char *string; + + if ((ci->values_num != 1) || (ci->values[0].type != OCONFIG_TYPE_STRING)) + { + ERROR ("cf_util_get_string: The %s option requires " + "exactly one string argument.", ci->key); + return (-1); + } + + string = strdup (ci->values[0].value.string); + if (string == NULL) + return (-1); + + if (*ret_string != NULL) + sfree (*ret_string); + *ret_string = string; + + return (0); +} /* }}} int cf_util_get_string */ + +/* Assures the config option is a string and copies it to the provided buffer. + * Assures null-termination. */ +int cf_util_get_string_buffer (const oconfig_item_t *ci, char *buffer, /* {{{ */ + size_t buffer_size) +{ + if ((ci == NULL) || (buffer == NULL) || (buffer_size < 1)) + return (EINVAL); + + if ((ci->values_num != 1) || (ci->values[0].type != OCONFIG_TYPE_STRING)) + { + ERROR ("cf_util_get_string_buffer: The %s option requires " + "exactly one string argument.", ci->key); + return (-1); + } + + strncpy (buffer, ci->values[0].value.string, buffer_size); + buffer[buffer_size - 1] = 0; + + return (0); +} /* }}} int cf_util_get_string_buffer */ + +/* Assures the config option is a number and returns it as an int. */ +int cf_util_get_int (const oconfig_item_t *ci, int *ret_value) /* {{{ */ +{ + if ((ci == NULL) || (ret_value == NULL)) + return (EINVAL); + + if ((ci->values_num != 1) || (ci->values[0].type != OCONFIG_TYPE_NUMBER)) + { + ERROR ("cf_util_get_int: The %s option requires " + "exactly one numeric argument.", ci->key); + return (-1); + } + + *ret_value = (int) ci->values[0].value.number; + + return (0); +} /* }}} int cf_util_get_int */ + +int cf_util_get_double (const oconfig_item_t *ci, double *ret_value) /* {{{ */ +{ + if ((ci == NULL) || (ret_value == NULL)) + return (EINVAL); + + if ((ci->values_num != 1) || (ci->values[0].type != OCONFIG_TYPE_NUMBER)) + { + ERROR ("cf_util_get_double: The %s option requires " + "exactly one numeric argument.", ci->key); + return (-1); + } + + *ret_value = ci->values[0].value.number; + + return (0); +} /* }}} int cf_util_get_double */ + +int cf_util_get_boolean (const oconfig_item_t *ci, _Bool *ret_bool) /* {{{ */ +{ + if ((ci == NULL) || (ret_bool == NULL)) + return (EINVAL); + + if ((ci->values_num != 1) || (ci->values[0].type != OCONFIG_TYPE_BOOLEAN)) + { + ERROR ("cf_util_get_boolean: The %s option requires " + "exactly one boolean argument.", ci->key); + return (-1); + } + + *ret_bool = ci->values[0].value.boolean ? 1 : 0; + + return (0); +} /* }}} int cf_util_get_boolean */ + +int cf_util_get_flag (const oconfig_item_t *ci, /* {{{ */ + unsigned int *ret_value, unsigned int flag) +{ + int status; + _Bool b; + + if (ret_value == NULL) + return (EINVAL); + + b = 0; + status = cf_util_get_boolean (ci, &b); + if (status != 0) + return (status); + + if (b) + { + *ret_value |= flag; + } + else + { + *ret_value &= ~flag; + } + + return (0); +} /* }}} int cf_util_get_flag */ + +/* Assures that the config option is a string or a number if the correct range + * of 1-65535. The string is then converted to a port number using + * `service_name_to_port_number' and returned. + * Returns the port number in the range [1-65535] or less than zero upon + * failure. */ +int cf_util_get_port_number (const oconfig_item_t *ci) /* {{{ */ +{ + int tmp; + + if ((ci->values_num != 1) + || ((ci->values[0].type != OCONFIG_TYPE_STRING) + && (ci->values[0].type != OCONFIG_TYPE_NUMBER))) + { + ERROR ("cf_util_get_port_number: The \"%s\" option requires " + "exactly one string argument.", ci->key); + return (-1); + } + + if (ci->values[0].type == OCONFIG_TYPE_STRING) + return (service_name_to_port_number (ci->values[0].value.string)); + + assert (ci->values[0].type == OCONFIG_TYPE_NUMBER); + tmp = (int) (ci->values[0].value.number + 0.5); + if ((tmp < 1) || (tmp > 65535)) + { + ERROR ("cf_util_get_port_number: The \"%s\" option requires " + "a service name or a port number. The number " + "you specified, %i, is not in the valid " + "range of 1-65535.", + ci->key, tmp); + return (-1); + } + + return (tmp); +} /* }}} int cf_util_get_port_number */ + +int cf_util_get_service (const oconfig_item_t *ci, char **ret_string) /* {{{ */ +{ + int port; + char *service; + int status; + + if (ci->values_num != 1) + { + ERROR ("cf_util_get_service: The %s option requires exactly " + "one argument.", ci->key); + return (-1); + } + + if (ci->values[0].type == OCONFIG_TYPE_STRING) + return (cf_util_get_string (ci, ret_string)); + if (ci->values[0].type != OCONFIG_TYPE_NUMBER) + { + ERROR ("cf_util_get_service: The %s option requires " + "exactly one string or numeric argument.", + ci->key); + } + + port = 0; + status = cf_util_get_int (ci, &port); + if (status != 0) + return (status); + else if ((port < 1) || (port > 65535)) + { + ERROR ("cf_util_get_service: The port number given " + "for the %s option is out of " + "range (%i).", ci->key, port); + return (-1); + } + + service = malloc (6); + if (service == NULL) + { + ERROR ("cf_util_get_service: Out of memory."); + return (-1); + } + ssnprintf (service, 6, "%i", port); + + sfree (*ret_string); + *ret_string = service; + + return (0); +} /* }}} int cf_util_get_service */ + +int cf_util_get_cdtime (const oconfig_item_t *ci, cdtime_t *ret_value) /* {{{ */ +{ + if ((ci == NULL) || (ret_value == NULL)) + return (EINVAL); + + if ((ci->values_num != 1) || (ci->values[0].type != OCONFIG_TYPE_NUMBER)) + { + ERROR ("cf_util_get_cdtime: The %s option requires " + "exactly one numeric argument.", ci->key); + return (-1); + } + + if (ci->values[0].value.number < 0.0) + { + ERROR ("cf_util_get_cdtime: The numeric argument of the %s " + "option must not be negative.", ci->key); + return (-1); + } + + *ret_value = DOUBLE_TO_CDTIME_T (ci->values[0].value.number); + + return (0); +} /* }}} int cf_util_get_cdtime */ + diff --cc src/threshold.c index 882f9553,4a840bb9..a8900db7 --- a/src/threshold.c +++ b/src/threshold.c @@@ -516,11 -626,7 +516,9 @@@ static int ut_report_state (const data_ ": Value is no longer missing."); else status = ssnprintf (buf, bufsize, - ": All data sources are within range again."); + ": All data sources are within range again. " + "Current value of \"%s\" is %f.", + ds->ds[ds_index].name, values[ds_index]); - buf += status; - bufsize -= status; } else { diff --cc src/virt.c index b6fedf55,00000000..dff8f71d mode 100644,000000..100644 --- a/src/virt.c +++ b/src/virt.c @@@ -1,1010 -1,0 +1,1016 @@@ +/** + * collectd - src/virt.c + * Copyright (C) 2006-2008 Red Hat Inc. + * + * 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 + * Free Software Foundation; only version 2 of the license is applicable. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + * Authors: + * Richard W.M. Jones + **/ + +#include "collectd.h" +#include "common.h" +#include "plugin.h" +#include "configfile.h" +#include "utils_ignorelist.h" +#include "utils_complain.h" + +#include +#include +#include +#include +#include + +/* Plugin name */ +#define PLUGIN_NAME "virt" + +static const char *config_keys[] = { + "Connection", + + "RefreshInterval", + + "Domain", + "BlockDevice", + "InterfaceDevice", + "IgnoreSelected", + + "HostnameFormat", + "InterfaceFormat", + + "PluginInstanceFormat", + + NULL +}; +#define NR_CONFIG_KEYS ((sizeof config_keys / sizeof config_keys[0]) - 1) + +/* Connection. */ +static virConnectPtr conn = 0; +static char *conn_string = NULL; +static c_complain_t conn_complain = C_COMPLAIN_INIT_STATIC; + +/* Seconds between list refreshes, 0 disables completely. */ +static int interval = 60; + +/* List of domains, if specified. */ +static ignorelist_t *il_domains = NULL; +/* List of block devices, if specified. */ +static ignorelist_t *il_block_devices = NULL; +/* List of network interface devices, if specified. */ +static ignorelist_t *il_interface_devices = NULL; + +static int ignore_device_match (ignorelist_t *, + const char *domname, const char *devpath); + +/* Actual list of domains found on last refresh. */ +static virDomainPtr *domains = NULL; +static int nr_domains = 0; + +static void free_domains (void); +static int add_domain (virDomainPtr dom); + +/* Actual list of block devices found on last refresh. */ +struct block_device { + virDomainPtr dom; /* domain */ + char *path; /* name of block device */ +}; + +static struct block_device *block_devices = NULL; +static int nr_block_devices = 0; + +static void free_block_devices (void); +static int add_block_device (virDomainPtr dom, const char *path); + +/* Actual list of network interfaces found on last refresh. */ +struct interface_device { + virDomainPtr dom; /* domain */ + char *path; /* name of interface device */ + char *address; /* mac address of interface device */ + char *number; /* interface device number */ +}; + +static struct interface_device *interface_devices = NULL; +static int nr_interface_devices = 0; + +static void free_interface_devices (void); +static int add_interface_device (virDomainPtr dom, const char *path, const char *address, unsigned int number); + +/* HostnameFormat. */ +#define HF_MAX_FIELDS 3 + +enum hf_field { + hf_none = 0, + hf_hostname, + hf_name, + hf_uuid +}; + +static enum hf_field hostname_format[HF_MAX_FIELDS] = + { hf_name }; + +/* PluginInstanceFormat */ +#define PLGINST_MAX_FIELDS 2 + +enum plginst_field { + plginst_none = 0, + plginst_name, + plginst_uuid +}; + +static enum plginst_field plugin_instance_format[PLGINST_MAX_FIELDS] = + { plginst_name }; + +/* InterfaceFormat. */ +enum if_field { + if_address, + if_name, + if_number +}; + +static enum if_field interface_format = if_name; + +/* Time that we last refreshed. */ +static time_t last_refresh = (time_t) 0; + +static int refresh_lists (void); + +/* ERROR(...) macro for virterrors. */ +#define VIRT_ERROR(conn,s) do { \ + virErrorPtr err; \ + err = (conn) ? virConnGetLastError ((conn)) : virGetLastError (); \ + if (err) ERROR ("%s: %s", (s), err->message); \ + } while(0) + +static void +init_value_list (value_list_t *vl, virDomainPtr dom) +{ + int i, n; + const char *name; + char uuid[VIR_UUID_STRING_BUFLEN]; + + sstrncpy (vl->plugin, PLUGIN_NAME, sizeof (vl->plugin)); + + vl->host[0] = '\0'; + + /* Construct the hostname field according to HostnameFormat. */ + for (i = 0; i < HF_MAX_FIELDS; ++i) { + if (hostname_format[i] == hf_none) + continue; + + n = DATA_MAX_NAME_LEN - strlen (vl->host) - 2; + + if (i > 0 && n >= 1) { + strncat (vl->host, ":", 1); + n--; + } + + switch (hostname_format[i]) { + case hf_none: break; + case hf_hostname: + strncat (vl->host, hostname_g, n); + break; + case hf_name: + name = virDomainGetName (dom); + if (name) + strncat (vl->host, name, n); + break; + case hf_uuid: + if (virDomainGetUUIDString (dom, uuid) == 0) + strncat (vl->host, uuid, n); + break; + } + } + + vl->host[sizeof (vl->host) - 1] = '\0'; + + /* Construct the plugin instance field according to PluginInstanceFormat. */ + for (i = 0; i < PLGINST_MAX_FIELDS; ++i) { + if (plugin_instance_format[i] == plginst_none) + continue; + + n = sizeof(vl->plugin_instance) - strlen (vl->plugin_instance) - 2; + + if (i > 0 && n >= 1) { + strncat (vl->plugin_instance, ":", 1); + n--; + } + + switch (plugin_instance_format[i]) { + case plginst_none: break; + case plginst_name: + name = virDomainGetName (dom); + if (name) + strncat (vl->plugin_instance, name, n); + break; + case plginst_uuid: + if (virDomainGetUUIDString (dom, uuid) == 0) + strncat (vl->plugin_instance, uuid, n); + break; + } + } + + vl->plugin_instance[sizeof (vl->plugin_instance) - 1] = '\0'; + +} /* void init_value_list */ + +static void +memory_submit (gauge_t memory, virDomainPtr dom) +{ + value_t values[1]; + value_list_t vl = VALUE_LIST_INIT; + + init_value_list (&vl, dom); + + values[0].gauge = memory; + + vl.values = values; + vl.values_len = 1; + + sstrncpy (vl.type, "memory", sizeof (vl.type)); + sstrncpy (vl.type_instance, "total", sizeof (vl.type_instance)); + + plugin_dispatch_values (&vl); +} + +static void +memory_stats_submit (gauge_t memory, virDomainPtr dom, int tag_index) +{ + static const char *tags[] = { "swap_in", "swap_out", "major_fault", "minor_fault", + "unused", "available", "actual_balloon", "rss"}; + + value_t values[1]; + value_list_t vl = VALUE_LIST_INIT; + + init_value_list (&vl, dom); + + values[0].gauge = memory; + + vl.values = values; + vl.values_len = 1; + + sstrncpy (vl.type, "memory", sizeof (vl.type)); + sstrncpy (vl.type_instance, tags[tag_index], sizeof (vl.type_instance)); + + plugin_dispatch_values (&vl); +} + +static void +cpu_submit (unsigned long long cpu_time, + virDomainPtr dom, const char *type) +{ + value_t values[1]; + value_list_t vl = VALUE_LIST_INIT; + + init_value_list (&vl, dom); + + values[0].derive = cpu_time; + + vl.values = values; + vl.values_len = 1; + + sstrncpy (vl.type, type, sizeof (vl.type)); + + plugin_dispatch_values (&vl); +} + +static void +vcpu_submit (derive_t cpu_time, + virDomainPtr dom, int vcpu_nr, const char *type) +{ + value_t values[1]; + value_list_t vl = VALUE_LIST_INIT; + + init_value_list (&vl, dom); + + values[0].derive = cpu_time; + vl.values = values; + vl.values_len = 1; + + sstrncpy (vl.type, type, sizeof (vl.type)); + ssnprintf (vl.type_instance, sizeof (vl.type_instance), "%d", vcpu_nr); + + plugin_dispatch_values (&vl); +} + +static void +submit_derive2 (const char *type, derive_t v0, derive_t v1, + virDomainPtr dom, const char *devname) +{ + value_t values[2]; + value_list_t vl = VALUE_LIST_INIT; + + init_value_list (&vl, dom); + + values[0].derive = v0; + values[1].derive = v1; + vl.values = values; + vl.values_len = 2; + + sstrncpy (vl.type, type, sizeof (vl.type)); + sstrncpy (vl.type_instance, devname, sizeof (vl.type_instance)); + + plugin_dispatch_values (&vl); +} /* void submit_derive2 */ + +static int +lv_init (void) +{ + if (virInitialize () != 0) + return -1; + + return 0; +} + +static int +lv_config (const char *key, const char *value) +{ + if (virInitialize () != 0) + return 1; + + if (il_domains == NULL) + il_domains = ignorelist_create (1); + if (il_block_devices == NULL) + il_block_devices = ignorelist_create (1); + if (il_interface_devices == NULL) + il_interface_devices = ignorelist_create (1); + + if (strcasecmp (key, "Connection") == 0) { + char *tmp = strdup (value); + if (tmp == NULL) { + ERROR (PLUGIN_NAME " plugin: Connection strdup failed."); + return 1; + } + sfree (conn_string); + conn_string = tmp; + return 0; + } + + if (strcasecmp (key, "RefreshInterval") == 0) { + char *eptr = NULL; + interval = strtol (value, &eptr, 10); + if (eptr == NULL || *eptr != '\0') return 1; + return 0; + } + + if (strcasecmp (key, "Domain") == 0) { + if (ignorelist_add (il_domains, value)) return 1; + return 0; + } + if (strcasecmp (key, "BlockDevice") == 0) { + if (ignorelist_add (il_block_devices, value)) return 1; + return 0; + } + if (strcasecmp (key, "InterfaceDevice") == 0) { + if (ignorelist_add (il_interface_devices, value)) return 1; + return 0; + } + + if (strcasecmp (key, "IgnoreSelected") == 0) { + if (IS_TRUE (value)) + { + ignorelist_set_invert (il_domains, 0); + ignorelist_set_invert (il_block_devices, 0); + ignorelist_set_invert (il_interface_devices, 0); + } + else + { + ignorelist_set_invert (il_domains, 1); + ignorelist_set_invert (il_block_devices, 1); + ignorelist_set_invert (il_interface_devices, 1); + } + return 0; + } + + if (strcasecmp (key, "HostnameFormat") == 0) { + char *value_copy; + char *fields[HF_MAX_FIELDS]; + int i, n; + + value_copy = strdup (value); + if (value_copy == NULL) { + ERROR (PLUGIN_NAME " plugin: strdup failed."); + return -1; + } + + n = strsplit (value_copy, fields, HF_MAX_FIELDS); + if (n < 1) { + sfree (value_copy); + ERROR (PLUGIN_NAME " plugin: HostnameFormat: no fields"); + return -1; + } + + for (i = 0; i < n; ++i) { + if (strcasecmp (fields[i], "hostname") == 0) + hostname_format[i] = hf_hostname; + else if (strcasecmp (fields[i], "name") == 0) + hostname_format[i] = hf_name; + else if (strcasecmp (fields[i], "uuid") == 0) + hostname_format[i] = hf_uuid; + else { + sfree (value_copy); + ERROR (PLUGIN_NAME " plugin: unknown HostnameFormat field: %s", fields[i]); + return -1; + } + } + sfree (value_copy); + + for (i = n; i < HF_MAX_FIELDS; ++i) + hostname_format[i] = hf_none; + + return 0; + } + + if (strcasecmp (key, "PluginInstanceFormat") == 0) { + char *value_copy; + char *fields[PLGINST_MAX_FIELDS]; + int i, n; + + value_copy = strdup (value); + if (value_copy == NULL) { + ERROR (PLUGIN_NAME " plugin: strdup failed."); + return -1; + } + + n = strsplit (value_copy, fields, PLGINST_MAX_FIELDS); + if (n < 1) { + sfree (value_copy); + ERROR (PLUGIN_NAME " plugin: PluginInstanceFormat: no fields"); + return -1; + } + + for (i = 0; i < n; ++i) { + if (strcasecmp (fields[i], "name") == 0) + plugin_instance_format[i] = plginst_name; + else if (strcasecmp (fields[i], "uuid") == 0) + plugin_instance_format[i] = plginst_uuid; + else { + sfree (value_copy); + ERROR (PLUGIN_NAME " plugin: unknown HostnameFormat field: %s", fields[i]); + return -1; + } + } + sfree (value_copy); + + for (i = n; i < PLGINST_MAX_FIELDS; ++i) + plugin_instance_format[i] = plginst_none; + + return 0; + } + + if (strcasecmp (key, "InterfaceFormat") == 0) { + if (strcasecmp (value, "name") == 0) + interface_format = if_name; + else if (strcasecmp (value, "address") == 0) + interface_format = if_address; + else if (strcasecmp (value, "number") == 0) + interface_format = if_number; + else { + ERROR (PLUGIN_NAME " plugin: unknown InterfaceFormat: %s", value); + return -1; + } + return 0; + } + + /* Unrecognised option. */ + return -1; +} + +static int +lv_read (void) +{ + time_t t; + int i; + + if (conn == NULL) { + /* `conn_string == NULL' is acceptable. */ + conn = virConnectOpenReadOnly (conn_string); + if (conn == NULL) { + c_complain (LOG_ERR, &conn_complain, + PLUGIN_NAME " plugin: Unable to connect: " + "virConnectOpenReadOnly failed."); + return -1; + } + } + c_release (LOG_NOTICE, &conn_complain, + PLUGIN_NAME " plugin: Connection established."); + + time (&t); + + /* Need to refresh domain or device lists? */ + if ((last_refresh == (time_t) 0) || + ((interval > 0) && ((last_refresh + interval) <= t))) { + if (refresh_lists () != 0) { + if (conn != NULL) + virConnectClose (conn); + conn = NULL; + return -1; + } + last_refresh = t; + } + +#if 0 + for (i = 0; i < nr_domains; ++i) + fprintf (stderr, "domain %s\n", virDomainGetName (domains[i])); + for (i = 0; i < nr_block_devices; ++i) + fprintf (stderr, "block device %d %s:%s\n", + i, virDomainGetName (block_devices[i].dom), + block_devices[i].path); + for (i = 0; i < nr_interface_devices; ++i) + fprintf (stderr, "interface device %d %s:%s\n", + i, virDomainGetName (interface_devices[i].dom), + interface_devices[i].path); +#endif + + /* Get CPU usage, memory, VCPU usage for each domain. */ + for (i = 0; i < nr_domains; ++i) { + virDomainInfo info; + virVcpuInfoPtr vinfo = NULL; + virDomainMemoryStatPtr minfo = NULL; + int status; + int j; + + status = virDomainGetInfo (domains[i], &info); + if (status != 0) + { + ERROR (PLUGIN_NAME " plugin: virDomainGetInfo failed with status %i.", + status); + continue; + } + + if (info.state != VIR_DOMAIN_RUNNING) + { + /* only gather stats for running domains */ + continue; + } + + cpu_submit (info.cpuTime, domains[i], "virt_cpu_total"); + memory_submit ((gauge_t) info.memory * 1024, domains[i]); + + vinfo = malloc (info.nrVirtCpu * sizeof (vinfo[0])); + if (vinfo == NULL) { + ERROR (PLUGIN_NAME " plugin: malloc failed."); + continue; + } + + status = virDomainGetVcpus (domains[i], vinfo, info.nrVirtCpu, + /* cpu map = */ NULL, /* cpu map length = */ 0); + if (status < 0) + { + ERROR (PLUGIN_NAME " plugin: virDomainGetVcpus failed with status %i.", + status); + sfree (vinfo); + continue; + } + + for (j = 0; j < info.nrVirtCpu; ++j) + vcpu_submit (vinfo[j].cpuTime, + domains[i], vinfo[j].number, "virt_vcpu"); + + sfree (vinfo); + + minfo = malloc (VIR_DOMAIN_MEMORY_STAT_NR * sizeof (virDomainMemoryStatStruct)); + if (minfo == NULL) { + ERROR ("virt plugin: malloc failed."); + continue; + } + + status = virDomainMemoryStats (domains[i], minfo, VIR_DOMAIN_MEMORY_STAT_NR, 0); + + if (status < 0) { + ERROR ("virt plugin: virDomainMemoryStats failed with status %i.", + status); + sfree (minfo); + continue; + } + + for (j = 0; j < status; j++) { + memory_stats_submit ((gauge_t) minfo[j].val * 1024, domains[i], minfo[j].tag); + } + + sfree (minfo); + } + + + /* Get block device stats for each domain. */ + for (i = 0; i < nr_block_devices; ++i) { + struct _virDomainBlockStats stats; + + if (virDomainBlockStats (block_devices[i].dom, block_devices[i].path, + &stats, sizeof stats) != 0) + continue; + + if ((stats.rd_req != -1) && (stats.wr_req != -1)) + submit_derive2 ("disk_ops", + (derive_t) stats.rd_req, (derive_t) stats.wr_req, + block_devices[i].dom, block_devices[i].path); + + if ((stats.rd_bytes != -1) && (stats.wr_bytes != -1)) + submit_derive2 ("disk_octets", + (derive_t) stats.rd_bytes, (derive_t) stats.wr_bytes, + block_devices[i].dom, block_devices[i].path); + } /* for (nr_block_devices) */ + + /* Get interface stats for each domain. */ + for (i = 0; i < nr_interface_devices; ++i) { + struct _virDomainInterfaceStats stats; + char *display_name = NULL; + + + switch (interface_format) { + case if_address: + display_name = interface_devices[i].address; + break; + case if_number: + display_name = interface_devices[i].number; + break; + case if_name: + default: + display_name = interface_devices[i].path; + } + + if (virDomainInterfaceStats (interface_devices[i].dom, + interface_devices[i].path, + &stats, sizeof stats) != 0) + continue; + + if ((stats.rx_bytes != -1) && (stats.tx_bytes != -1)) + submit_derive2 ("if_octets", + (derive_t) stats.rx_bytes, (derive_t) stats.tx_bytes, + interface_devices[i].dom, display_name); + + if ((stats.rx_packets != -1) && (stats.tx_packets != -1)) + submit_derive2 ("if_packets", + (derive_t) stats.rx_packets, (derive_t) stats.tx_packets, + interface_devices[i].dom, display_name); + + if ((stats.rx_errs != -1) && (stats.tx_errs != -1)) + submit_derive2 ("if_errors", + (derive_t) stats.rx_errs, (derive_t) stats.tx_errs, + interface_devices[i].dom, display_name); + + if ((stats.rx_drop != -1) && (stats.tx_drop != -1)) + submit_derive2 ("if_dropped", + (derive_t) stats.rx_drop, (derive_t) stats.tx_drop, + interface_devices[i].dom, display_name); + } /* for (nr_interface_devices) */ + + return 0; +} + +static int +refresh_lists (void) +{ + int n; + + n = virConnectNumOfDomains (conn); + if (n < 0) { + VIRT_ERROR (conn, "reading number of domains"); + return -1; + } + + if (n > 0) { + int i; + int *domids; + + /* Get list of domains. */ + domids = malloc (sizeof (int) * n); + if (domids == 0) { + ERROR (PLUGIN_NAME " plugin: malloc failed."); + return -1; + } + + n = virConnectListDomains (conn, domids, n); + if (n < 0) { + VIRT_ERROR (conn, "reading list of domains"); + sfree (domids); + return -1; + } + + free_block_devices (); + free_interface_devices (); + free_domains (); + + /* Fetch each domain and add it to the list, unless ignore. */ + for (i = 0; i < n; ++i) { + virDomainPtr dom = NULL; + const char *name; + char *xml = NULL; + xmlDocPtr xml_doc = NULL; + xmlXPathContextPtr xpath_ctx = NULL; + xmlXPathObjectPtr xpath_obj = NULL; + int j; + + dom = virDomainLookupByID (conn, domids[i]); + if (dom == NULL) { + VIRT_ERROR (conn, "virDomainLookupByID"); + /* Could be that the domain went away -- ignore it anyway. */ + continue; + } + + name = virDomainGetName (dom); + if (name == NULL) { + VIRT_ERROR (conn, "virDomainGetName"); + goto cont; + } + + if (il_domains && ignorelist_match (il_domains, name) != 0) + goto cont; + + if (add_domain (dom) < 0) { + ERROR (PLUGIN_NAME " plugin: malloc failed."); + goto cont; + } + + /* Get a list of devices for this domain. */ + xml = virDomainGetXMLDesc (dom, 0); + if (!xml) { + VIRT_ERROR (conn, "virDomainGetXMLDesc"); + goto cont; + } + + /* Yuck, XML. Parse out the devices. */ + xml_doc = xmlReadDoc ((xmlChar *) xml, NULL, NULL, XML_PARSE_NONET); + if (xml_doc == NULL) { + VIRT_ERROR (conn, "xmlReadDoc"); + goto cont; + } + + xpath_ctx = xmlXPathNewContext (xml_doc); + + /* Block devices. */ + xpath_obj = xmlXPathEval + ((xmlChar *) "/domain/devices/disk/target[@dev]", + xpath_ctx); + if (xpath_obj == NULL || xpath_obj->type != XPATH_NODESET || + xpath_obj->nodesetval == NULL) + goto cont; + + for (j = 0; j < xpath_obj->nodesetval->nodeNr; ++j) { + xmlNodePtr node; + char *path = NULL; + + node = xpath_obj->nodesetval->nodeTab[j]; + if (!node) continue; + path = (char *) xmlGetProp (node, (xmlChar *) "dev"); + if (!path) continue; + + if (il_block_devices && + ignore_device_match (il_block_devices, name, path) != 0) + goto cont2; + + add_block_device (dom, path); + cont2: + if (path) xmlFree (path); + } + xmlXPathFreeObject (xpath_obj); + + /* Network interfaces. */ + xpath_obj = xmlXPathEval + ((xmlChar *) "/domain/devices/interface[target[@dev]]", + xpath_ctx); + if (xpath_obj == NULL || xpath_obj->type != XPATH_NODESET || + xpath_obj->nodesetval == NULL) + goto cont; + + xmlNodeSetPtr xml_interfaces = xpath_obj->nodesetval; + + for (j = 0; j < xml_interfaces->nodeNr; ++j) { + char *path = NULL; + char *address = NULL; + xmlNodePtr xml_interface; + + xml_interface = xml_interfaces->nodeTab[j]; + if (!xml_interface) continue; + xmlNodePtr child = NULL; + + for (child = xml_interface->children; child; child = child->next) { + if (child->type != XML_ELEMENT_NODE) continue; + + if (xmlStrEqual(child->name, (const xmlChar *) "target")) { + path = (char *) xmlGetProp (child, (const xmlChar *) "dev"); + if (!path) continue; + } else if (xmlStrEqual(child->name, (const xmlChar *) "mac")) { + address = (char *) xmlGetProp (child, (const xmlChar *) "address"); + if (!address) continue; + } + } + + if (il_interface_devices && + (ignore_device_match (il_interface_devices, name, path) != 0 || + ignore_device_match (il_interface_devices, name, address) != 0)) + goto cont3; + + add_interface_device (dom, path, address, j+1); + cont3: + if (path) xmlFree (path); + if (address) xmlFree (address); + } + + cont: + if (xpath_obj) xmlXPathFreeObject (xpath_obj); + if (xpath_ctx) xmlXPathFreeContext (xpath_ctx); + if (xml_doc) xmlFreeDoc (xml_doc); + sfree (xml); + } + + sfree (domids); + } + + return 0; +} + +static void +free_domains () +{ + int i; + + if (domains) { + for (i = 0; i < nr_domains; ++i) + virDomainFree (domains[i]); + sfree (domains); + } + domains = NULL; + nr_domains = 0; +} + +static int +add_domain (virDomainPtr dom) +{ + virDomainPtr *new_ptr; + int new_size = sizeof (domains[0]) * (nr_domains+1); + + if (domains) + new_ptr = realloc (domains, new_size); + else + new_ptr = malloc (new_size); + + if (new_ptr == NULL) + return -1; + + domains = new_ptr; + domains[nr_domains] = dom; + return nr_domains++; +} + +static void +free_block_devices () +{ + int i; + + if (block_devices) { + for (i = 0; i < nr_block_devices; ++i) + sfree (block_devices[i].path); + sfree (block_devices); + } + block_devices = NULL; + nr_block_devices = 0; +} + +static int +add_block_device (virDomainPtr dom, const char *path) +{ + struct block_device *new_ptr; + int new_size = sizeof (block_devices[0]) * (nr_block_devices+1); + char *path_copy; + + path_copy = strdup (path); + if (!path_copy) + return -1; + + if (block_devices) + new_ptr = realloc (block_devices, new_size); + else + new_ptr = malloc (new_size); + + if (new_ptr == NULL) { + sfree (path_copy); + return -1; + } + block_devices = new_ptr; + block_devices[nr_block_devices].dom = dom; + block_devices[nr_block_devices].path = path_copy; + return nr_block_devices++; +} + +static void +free_interface_devices () +{ + int i; + + if (interface_devices) { + for (i = 0; i < nr_interface_devices; ++i) { + sfree (interface_devices[i].path); + sfree (interface_devices[i].address); + sfree (interface_devices[i].number); + } + sfree (interface_devices); + } + interface_devices = NULL; + nr_interface_devices = 0; +} + +static int +add_interface_device (virDomainPtr dom, const char *path, const char *address, unsigned int number) +{ + struct interface_device *new_ptr; + int new_size = sizeof (interface_devices[0]) * (nr_interface_devices+1); + char *path_copy, *address_copy, number_string[15]; + ++ if ((path == NULL) || (address == NULL)) ++ return EINVAL; ++ + path_copy = strdup (path); + if (!path_copy) return -1; + + address_copy = strdup (address); + if (!address_copy) { + sfree(path_copy); + return -1; + } + + snprintf(number_string, sizeof (number_string), "interface-%u", number); + + if (interface_devices) + new_ptr = realloc (interface_devices, new_size); + else + new_ptr = malloc (new_size); + + if (new_ptr == NULL) { + sfree (path_copy); + sfree (address_copy); + return -1; + } + interface_devices = new_ptr; + interface_devices[nr_interface_devices].dom = dom; + interface_devices[nr_interface_devices].path = path_copy; + interface_devices[nr_interface_devices].address = address_copy; + interface_devices[nr_interface_devices].number = strdup(number_string); + return nr_interface_devices++; +} + +static int +ignore_device_match (ignorelist_t *il, const char *domname, const char *devpath) +{ + char *name; + int n, r; + ++ if ((domname == NULL) || (devpath == NULL)) ++ return 0; ++ + n = sizeof (char) * (strlen (domname) + strlen (devpath) + 2); + name = malloc (n); + if (name == NULL) { + ERROR (PLUGIN_NAME " plugin: malloc failed."); + return 0; + } + ssnprintf (name, n, "%s:%s", domname, devpath); + r = ignorelist_match (il, name); + sfree (name); + return r; +} + +static int +lv_shutdown (void) +{ + free_block_devices (); + free_interface_devices (); + free_domains (); + + if (conn != NULL) + virConnectClose (conn); + conn = NULL; + + ignorelist_free (il_domains); + il_domains = NULL; + ignorelist_free (il_block_devices); + il_block_devices = NULL; + ignorelist_free (il_interface_devices); + il_interface_devices = NULL; + + return 0; +} + +void +module_register (void) +{ + plugin_register_config (PLUGIN_NAME, + lv_config, + config_keys, NR_CONFIG_KEYS); + plugin_register_init (PLUGIN_NAME, lv_init); + plugin_register_read (PLUGIN_NAME, lv_read); + plugin_register_shutdown (PLUGIN_NAME, lv_shutdown); +} + +/* + * vim: shiftwidth=4 tabstop=8 softtabstop=4 expandtab fdm=marker + */