From: Florian Forster Date: Mon, 15 Mar 2010 21:43:00 +0000 (+0100) Subject: Merge branch 'collectd-4.9' X-Git-Tag: collectd-4.10.0~55 X-Git-Url: https://git.octo.it/?a=commitdiff_plain;h=070563c1ac6bf489cb13095ed30a6d290b98ac75;hp=9988d61c84dfff5d04ddf48f4b93f6c9449cdc41;p=collectd.git Merge branch 'collectd-4.9' --- diff --git a/AUTHORS b/AUTHORS index 4b133fa0..67c75f0b 100644 --- a/AUTHORS +++ b/AUTHORS @@ -23,6 +23,7 @@ Alvaro Barcellos Amit Gupta - Multiple servers in the apache plugin. + - curl_xml plugin. Anthony Dewhurst - zfs_arc plugin. diff --git a/README b/README index 96fbdf3e..3a56fbed 100644 --- a/README +++ b/README @@ -57,6 +57,10 @@ Features Retrieves JSON data via cURL and parses it according to user configuration. + - curl_xml + Retrieves XML data via cURL and parses it according to user + configuration. + - dbi Executes SQL statements on various databases and interprets the returned data. diff --git a/configure.in b/configure.in index 32805063..11935835 100644 --- a/configure.in +++ b/configure.in @@ -2791,7 +2791,7 @@ fi if test "x$with_python" = "xyes" then AC_MSG_CHECKING([for Python CPPFLAGS]) - python_include_path=`echo "import distutils.sysconfig;print distutils.sysconfig.get_python_inc()" | "$with_python_prog" 2>&1` + python_include_path=`echo "import distutils.sysconfig;import sys;sys.stdout.write(distutils.sysconfig.get_python_inc())" | "$with_python_prog" 2>&1` python_config_status=$? if test "$python_config_status" -ne 0 || test "x$python_include_path" = "x" @@ -2814,7 +2814,7 @@ fi if test "x$with_python" = "xyes" then AC_MSG_CHECKING([for Python LDFLAGS]) - python_library_path=`echo "import distutils.sysconfig;print distutils.sysconfig.get_config_vars(\"LIBDIR\").__getitem__(0)" | "$with_python_prog" 2>&1` + python_library_path=`echo "import distutils.sysconfig;import sys;sys.stdout.write(distutils.sysconfig.get_config_vars(\"LIBDIR\").__getitem__(0))" | "$with_python_prog" 2>&1` python_config_status=$? if test "$python_config_status" -ne 0 || test "x$python_library_path" = "x" @@ -2829,7 +2829,7 @@ fi if test "x$with_python" = "xyes" then AC_MSG_CHECKING([for Python LIBS]) - python_library_flags=`echo "import distutils.sysconfig;print distutils.sysconfig.get_config_vars(\"BLDLIBRARY\").__getitem__(0)" | "$with_python_prog" 2>&1` + python_library_flags=`echo "import distutils.sysconfig;import sys;sys.stdout.write(distutils.sysconfig.get_config_vars(\"BLDLIBRARY\").__getitem__(0))" | "$with_python_prog" 2>&1` python_config_status=$? if test "$python_config_status" -ne 0 || test "x$python_library_flags" = "x" @@ -3842,6 +3842,7 @@ plugin_contextswitch="no" plugin_cpu="no" plugin_cpufreq="no" plugin_curl_json="no" +plugin_curl_xml="no" plugin_df="no" plugin_disk="no" plugin_entropy="no" @@ -3981,6 +3982,11 @@ then plugin_curl_json="yes" fi +if test "x$with_libcurl" = "xyes" && test "x$with_libxml2" = "xyes" +then + plugin_curl_xml="yes" +fi + if test "x$have_processor_info" = "xyes" then plugin_cpu="yes" @@ -4134,6 +4140,7 @@ AC_PLUGIN([cpu], [$plugin_cpu], [CPU usage statistics]) AC_PLUGIN([csv], [yes], [CSV output plugin]) AC_PLUGIN([curl], [$with_libcurl], [CURL generic web statistics]) AC_PLUGIN([curl_json], [$plugin_curl_json], [CouchDB statistics]) +AC_PLUGIN([curl_xml], [$plugin_curl_xml], [CURL generic xml statistics]) AC_PLUGIN([dbi], [$with_libdbi], [General database statistics]) AC_PLUGIN([df], [$plugin_df], [Filesystem usage statistics]) AC_PLUGIN([disk], [$plugin_disk], [Disk usage statistics]) @@ -4444,6 +4451,7 @@ Configuration: csv . . . . . . . . . $enable_csv curl . . . . . . . . $enable_curl curl_json . . . . . . $enable_curl_json + curl_xml . . . . . . $enable_curl_xml dbi . . . . . . . . . $enable_dbi df . . . . . . . . . $enable_df disk . . . . . . . . $enable_disk diff --git a/contrib/collection.cgi b/contrib/collection.cgi index 100c0c73..af64fb1c 100755 --- a/contrib/collection.cgi +++ b/contrib/collection.cgi @@ -976,6 +976,28 @@ sub load_graph_definitions 'GPRINT:avg:LAST:%5.1lf%s Last', 'GPRINT:avg_sum:LAST:(ca. %5.1lf%sB Total)\l' ], + apache_connections => ['DEF:min={file}:count:MIN', + 'DEF:avg={file}:count:AVERAGE', + 'DEF:max={file}:count:MAX', + "AREA:max#$HalfBlue", + "AREA:min#$Canvas", + "LINE1:avg#$FullBlue:Connections", + 'GPRINT:min:MIN:%6.2lf Min,', + 'GPRINT:avg:AVERAGE:%6.2lf Avg,', + 'GPRINT:max:MAX:%6.2lf Max,', + 'GPRINT:avg:LAST:%6.2lf Last' + ], + apache_idle_workers => ['DEF:min={file}:count:MIN', + 'DEF:avg={file}:count:AVERAGE', + 'DEF:max={file}:count:MAX', + "AREA:max#$HalfBlue", + "AREA:min#$Canvas", + "LINE1:avg#$FullBlue:Idle Workers", + 'GPRINT:min:MIN:%6.2lf Min,', + 'GPRINT:avg:AVERAGE:%6.2lf Avg,', + 'GPRINT:max:MAX:%6.2lf Max,', + 'GPRINT:avg:LAST:%6.2lf Last' + ], apache_requests => ['DEF:min={file}:count:MIN', 'DEF:avg={file}:count:AVERAGE', 'DEF:max={file}:count:MAX', diff --git a/src/Makefile.am b/src/Makefile.am index d928311a..02563509 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -257,6 +257,17 @@ collectd_LDADD += "-dlopen" curl_json.la collectd_DEPENDENCIES += curl_json.la endif +if BUILD_PLUGIN_CURL_XML +pkglib_LTLIBRARIES += curl_xml.la +curl_xml_la_SOURCES = curl_xml.c +curl_xml_la_LDFLAGS = -module -avoid-version +curl_xml_la_CFLAGS = $(AM_CFLAGS) \ + $(BUILD_WITH_LIBCURL_CFLAGS) $(BUILD_WITH_LIBXML2_CFLAGS) +curl_xml_la_LIBADD = $(BUILD_WITH_LIBCURL_LIBS) $(BUILD_WITH_LIBXML2_LIBS) +collectd_LDADD += "-dlopen" curl_xml.la +collectd_DEPENDENCIES += curl_xml.la +endif + if BUILD_PLUGIN_DBI pkglib_LTLIBRARIES += dbi.la dbi_la_SOURCES = dbi.c \ diff --git a/src/apache.c b/src/apache.c index ad877b5f..39849e6e 100644 --- a/src/apache.c +++ b/src/apache.c @@ -688,6 +688,9 @@ static int apache_read_host (user_data_t *user_data) /* {{{ */ else if ((strcmp (fields[0], "BusyServers:") == 0) /* Apache 1.* */ || (strcmp (fields[0], "BusyWorkers:") == 0) /* Apache 2.* */) submit_gauge ("apache_connections", NULL, atol (fields[1]), st); + else if ((strcmp (fields[0], "IdleServers:") == 0) /* Apache 1.x */ + || (strcmp (fields[0], "IdleWorkers:") == 0) /* Apache 2.x */) + submit_gauge ("apache_idle_workers", NULL, atol (fields[1]), st); } } diff --git a/src/battery.c b/src/battery.c index b62ad81d..4178d8b5 100644 --- a/src/battery.c +++ b/src/battery.c @@ -514,7 +514,8 @@ static int battery_read (void) if (0 == access (battery_acpi_dir, R_OK)) walk_directory (battery_acpi_dir, battery_read_acpi, - /* user_data = */ NULL); + /* user_data = */ NULL, + /* include hidden */ 0); else { char errbuf[1024]; diff --git a/src/collectd-python.pod b/src/collectd-python.pod index 45a06d1d..335f6a91 100644 --- a/src/collectd-python.pod +++ b/src/collectd-python.pod @@ -27,8 +27,7 @@ for collectd in Python. This is a lot more efficient than executing a Python-script every time you want to read a value with the C (see L) and provides a lot more functionality, too. -Currently only I is supported and at least I is -required. +At least python I is required. =head1 CONFIGURATION @@ -119,6 +118,29 @@ The I identifies the callback. =back +=head1 STRINGS + +There are a lot of places where strings are send from collectd to python and +from python to collectd. How exactly this works depends on wheather byte or +unicode strings or python2 or python3 are used. + +Python2 has I, which is just bytes, and I. Python3 has I, +which is a unicode object, and I. + +When passing strings from python to collectd all of these object are supported +in all places, however I should be used if possible. These strings must +not contain a NUL byte. Ignoring this will result in a I exception. +If a byte string was used it will be used as is by collectd. If a unicode +object was used it will be encoded using the default encoding (see above). If +this is not possible python will raise a I exception. + +Wenn passing strings from collectd to python the behavior depends on the +python version used. Python2 will always receive a I object. Python3 will +usually receive a I object as well, however the original string will be +decoded to unicode using the default encoding. If this fails because the +string is not a valid sequence for this encoding a I object will be +returned instead. + =head1 WRITING YOUR OWN PLUGINS Writing your own plugins is quite simple. collectd manages plugins by means of diff --git a/src/collectd.c b/src/collectd.c index bc69a3b7..abab10f9 100644 --- a/src/collectd.c +++ b/src/collectd.c @@ -259,9 +259,10 @@ static void exit_usage (int status) #endif " -h Display help (this message)\n" "\nBuiltin defaults:\n" - " Config-File "CONFIGFILE"\n" - " PID-File "PIDFILE"\n" - " Data-Directory "PKGLOCALSTATEDIR"\n" + " Config file "CONFIGFILE"\n" + " PID file "PIDFILE"\n" + " Plugin directory "PLUGINDIR"\n" + " Data directory "PKGLOCALSTATEDIR"\n" "\n"PACKAGE" "VERSION", http://collectd.org/\n" "by Florian octo Forster \n" "for contributions see `AUTHORS'\n"); diff --git a/src/collectd.conf.in b/src/collectd.conf.in index 844c83be..888875c2 100644 --- a/src/collectd.conf.in +++ b/src/collectd.conf.in @@ -63,6 +63,7 @@ FQDNLookup true @LOAD_PLUGIN_CSV@LoadPlugin csv #@BUILD_PLUGIN_CURL_TRUE@LoadPlugin curl #@BUILD_PLUGIN_CURL_JSON_TRUE@LoadPlugin curl_json +#@BUILD_PLUGIN_CURL_XML_TRUE@LoadPlugin curl_xml #@BUILD_PLUGIN_DBI_TRUE@LoadPlugin dbi #@BUILD_PLUGIN_DF_TRUE@LoadPlugin df #@BUILD_PLUGIN_DISK_TRUE@LoadPlugin disk @@ -231,6 +232,25 @@ FQDNLookup true # # +# +# +# Host "my_host" +# Instance "some_instance" +# User "collectd" +# Password "thaiNg0I" +# VerifyPeer true +# VerifyHost true +# CACert "/path/to/ca.crt" +# +# +# Type "magic_level" +# #InstancePrefix "prefix-" +# InstanceFrom "td[1]" +# ValuesFrom "td[2]/span[@class=\"level\"]" +# +# +# + # # # Statement "SELECT 'customers' AS c_key, COUNT(*) AS c_value FROM customers_tbl" @@ -665,6 +685,10 @@ FQDNLookup true # Password "dozaiTh4" # CollectInterface true # CollectRegistrationTable true +# CollectCPULoad true +# CollectMemory true +# CollectDF true +# CollectDisk true # # diff --git a/src/collectd.conf.pod b/src/collectd.conf.pod index 33d4f352..09508f70 100644 --- a/src/collectd.conf.pod +++ b/src/collectd.conf.pod @@ -619,6 +619,110 @@ Type-instance to use. Defaults to the current map key or current string array el =back +=head2 Plugin C + +The B uses B (L) and B +(L) to retrieve XML data via cURL. + + + + Host "my_host" + Instance "some_instance" + User "collectd" + Password "thaiNg0I" + VerifyPeer true + VerifyHost true + CACert "/path/to/ca.crt" + + + Type "magic_level" + #InstancePrefix "prefix-" + InstanceFrom "td[1]" + ValuesFrom "td[2]/span[@class=\"level\"]" + + + + +In the B block, there may be one or more B blocks, each defining a +URL to be fetched via HTTP (using libcurl). Within each B block there are +options which specify the connection parameters, for example authentication +information, and one or more B blocks. + +Each B block specifies how to get one type of information. The +string argument must be a valid XPath expression which returns a list +of "base elements". One value is dispatched for each "base element". The +I and values are looked up using further I expressions +that should be relative to the base element. + +Within the B block the following options are accepted: + +=over 4 + +=item B I + +Use I as the host name when submitting values. Defaults to the global +host name setting. + +=item B I + +Use I as the plugin instance when submitting values. Defaults to an +empty string (no plugin instance). + +=item B I +=item B I +=item B B|B +=item B B|B +=item B I + +These options behave exactly equivalent to the appropriate options of the +I and I plugins. Please see there for a detailed description. + +=item EB IE + +Within each B block, there must be one or more B blocks. Each +B block specifies how to get one type of information. The string +argument must be a valid XPath expression which returns a list of "base +elements". One value is dispatched for each "base element". + +Within the B block the following options are accepted: + +=over 4 + +=item B I + +Specifies the I used for submitting patches. This determines the number +of values that are required / expected and whether the strings are parsed as +signed or unsigned integer or as double values. See L for details. +This option is required. + +=item B I + +Prefix the I with I. The values are simply +concatenated together without any separator. +This option is optional. + +=item B I + +Specifies a XPath expression to use for determining the I. The +XPath expression must return exactly one element. The element's value is then +used as I, possibly prefixed with I (see above). + +This value is required. As a special exception, if the "base XPath expression" +(the argument to the B block) returns exactly one argument, then this +option may be omitted. + +=item B I [I ...] + +Specifies one or more XPath expression to use for reading the values. The +number of XPath expressions must match the number of data sources in the +I specified with B (see above). Each XPath expression must return +exactly one element. The element's value is then parsed as a number and used as +value for the appropriate value in the value list dispatched to the daemon. + +=back + +=back + =head2 Plugin C This plugin uses the B library (L) to @@ -1104,6 +1208,12 @@ note that there are 1000 bytes in a kilobyte, not 1024. Controls whether or not to recurse into subdirectories. Enabled by default. +=item B I|I + +Controls whether or not to include "hidden" files and directories in the count. +"Hidden" files and directories are those, whose name begins with a dot. +Defaults to I, i.e. by default hidden files and directories are ignored. + =back =head2 Plugin C @@ -3335,6 +3445,8 @@ multiple routers: User "collectd" Password "secr3t" CollectInterface true + CollectCPULoad true + CollectMemory true Host "router1.example.com" @@ -3342,6 +3454,8 @@ multiple routers: Password "5ecret" CollectInterface true CollectRegistrationTable true + CollectDF true + CollectDisk true @@ -3379,6 +3493,29 @@ present on the device. Defaults to B. When set to B, information about wireless LAN connections will be collected. Defaults to B. +=item B B|B + +When set to B, information about the CPU usage will be collected. The +number is a dimensionless value where zero indicates no CPU usage at all. +Defaults to B. + +=item B B|B + +When enabled, the amount of used and free memory will be collected. How used +memory is calculated is unknown, for example whether or not caches are counted +as used space. +Defaults to B. + +=item B B|B + +When enabled, the amount of used and free disk space will be collected. +Defaults to B. + +=item B B|B + +When enabled, the number of sectors written and bad blocks will be collected. +Defaults to B. + =back =head2 Plugin C @@ -4100,6 +4237,12 @@ create output in the I (JSON). Defaults to B. +=item B B + +If set to B, convert counter values to rates. If set to B (the +default) counter values are stored as is, i.Ee. as an increasing integer +number. + =back =head1 THRESHOLD CONFIGURATION diff --git a/src/common.c b/src/common.c index ae57c433..2598036d 100644 --- a/src/common.c +++ b/src/common.c @@ -1010,7 +1010,7 @@ int notification_init (notification_t *n, int severity, const char *message, } /* int notification_init */ int walk_directory (const char *dir, dirwalk_callback_f callback, - void *user_data) + void *user_data, int include_hidden) { struct dirent *ent; DIR *dh; @@ -1031,9 +1031,18 @@ int walk_directory (const char *dir, dirwalk_callback_f callback, while ((ent = readdir (dh)) != NULL) { int status; - - if (ent->d_name[0] == '.') - continue; + + if (include_hidden) + { + if ((strcmp (".", ent->d_name) == 0) + || (strcmp ("..", ent->d_name) == 0)) + continue; + } + else /* if (!include_hidden) */ + { + if (ent->d_name[0]=='.') + continue; + } status = (*callback) (dir, ent->d_name, user_data); if (status != 0) @@ -1135,3 +1144,21 @@ int service_name_to_port_number (const char *service_name) return (service_number); return (-1); } /* int service_name_to_port_number */ + +int strtoderive (const char *string, derive_t *ret_value) /* {{{ */ +{ + derive_t tmp; + char *endptr; + + if ((string == NULL) || (ret_value == NULL)) + return (EINVAL); + + errno = 0; + endptr = NULL; + tmp = (derive_t) strtoll (string, &endptr, /* base = */ 0); + if ((endptr == string) || (errno != 0)) + return (-1); + + *ret_value = tmp; + return (0); +} /* }}} int strtoderive */ diff --git a/src/common.h b/src/common.h index 7b9fa0ac..c0bea36e 100644 --- a/src/common.h +++ b/src/common.h @@ -282,7 +282,7 @@ int notification_init (notification_t *n, int severity, const char *message, typedef int (*dirwalk_callback_f)(const char *dirname, const char *filename, void *user_data); int walk_directory (const char *dir, dirwalk_callback_f callback, - void *user_data); + void *user_data, int hidden); int read_file_contents (const char *filename, char *buf, int bufsize); counter_t counter_diff (counter_t old_value, counter_t new_value); @@ -291,4 +291,6 @@ counter_t counter_diff (counter_t old_value, counter_t new_value); * (in the range [1-65535]). Returns less than zero on error. */ int service_name_to_port_number (const char *service_name); +int strtoderive (const char *string, derive_t *ret_value); + #endif /* COMMON_H */ diff --git a/src/configfile.c b/src/configfile.c index 2eea2362..fe2bce3c 100644 --- a/src/configfile.c +++ b/src/configfile.c @@ -957,6 +957,45 @@ int cf_util_get_string (const oconfig_item_t *ci, char **ret_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_boolean (const oconfig_item_t *ci, _Bool *ret_bool) /* {{{ */ { if ((ci == NULL) || (ret_bool == NULL)) @@ -965,7 +1004,7 @@ int cf_util_get_boolean (const oconfig_item_t *ci, _Bool *ret_bool) /* {{{ */ if ((ci->values_num != 1) || (ci->values[0].type != OCONFIG_TYPE_BOOLEAN)) { ERROR ("cf_util_get_boolean: The %s option requires " - "exactly one string argument.", ci->key); + "exactly one boolean argument.", ci->key); return (-1); } diff --git a/src/configfile.h b/src/configfile.h index a73def21..432e09f5 100644 --- a/src/configfile.h +++ b/src/configfile.h @@ -91,6 +91,14 @@ const char *global_option_get (const char *option); * success. */ int cf_util_get_string (const oconfig_item_t *ci, char **ret_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); + +/* 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); + /* Assures the config option is a boolean and assignes it to `ret_bool'. * Otherwise, `ret_bool' is not changed and non-zero is returned. */ int cf_util_get_boolean (const oconfig_item_t *ci, _Bool *ret_bool); diff --git a/src/cpython.h b/src/cpython.h index 661bf6a2..3e80cb0c 100644 --- a/src/cpython.h +++ b/src/cpython.h @@ -74,6 +74,89 @@ # define Py_RETURN_NONE return Py_INCREF(Py_None), Py_None #endif +/* This macro is a shortcut for calls like + * x = PyObject_Repr(x); + * This can't be done like this example because this would leak + * a reference the the original x and crash in case of x == NULL. + * This calling syntax is less than elegant but it works, saves + * a lot of lines and avoids potential refcount errors. */ + +#define CPY_SUBSTITUTE(func, a, ...) do {\ + if ((a) != NULL) {\ + PyObject *__tmp = (a);\ + (a) = func(__VA_ARGS__);\ + Py_DECREF(__tmp);\ + }\ +} while(0) + +/* Python3 compatibility layer. To keep the actual code as clean as possible + * do a lot of defines here. */ + +#if PY_MAJOR_VERSION >= 3 +#define IS_PY3K +#endif + +#ifdef IS_PY3K + +#define PyInt_FromLong PyLong_FromLong +#define CPY_INIT_TYPE PyVarObject_HEAD_INIT(NULL, 0) +#define IS_BYTES_OR_UNICODE(o) (PyUnicode_Check(o) || PyBytes_Check(o)) +#define CPY_STRCAT_AND_DEL(a, b) do {\ + CPY_STRCAT((a), (b));\ + Py_XDECREF((b));\ +} while (0) +static inline void CPY_STRCAT(PyObject **a, PyObject *b) { + PyObject *ret; + + if (!a || !*a) + return; + + ret = PyUnicode_Concat(*a, b); + Py_DECREF(*a); + *a = ret; +} + +#else + +#define CPY_INIT_TYPE PyObject_HEAD_INIT(NULL) 0, +#define IS_BYTES_OR_UNICODE(o) (PyUnicode_Check(o) || PyString_Check(o)) +#define CPY_STRCAT_AND_DEL PyString_ConcatAndDel +#define CPY_STRCAT PyString_Concat + +#endif + +static inline const char *cpy_unicode_or_bytes_to_string(PyObject **o) { + if (PyUnicode_Check(*o)) { + PyObject *tmp; + tmp = PyUnicode_AsEncodedString(*o, NULL, NULL); /* New reference. */ + if (tmp == NULL) + return NULL; + Py_DECREF(*o); + *o = tmp; + } +#ifdef IS_PY3K + return PyBytes_AsString(*o); +#else + return PyString_AsString(*o); +#endif +} + +static inline PyObject *cpy_string_to_unicode_or_bytes(const char *buf) { +#ifdef IS_PY3K +/* Python3 preferrs unicode */ + PyObject *ret; + ret = PyUnicode_Decode(buf, strlen(buf), NULL, NULL); + if (ret != NULL) + return ret; + PyErr_Clear(); + return PyBytes_FromString(buf); +#else + return PyString_FromString(buf); +#endif +} + + /* Python object declarations. */ + typedef struct { PyObject_HEAD /* No semicolon! */ PyObject *parent; /* Config */ diff --git a/src/curl_xml.c b/src/curl_xml.c new file mode 100644 index 00000000..240be662 --- /dev/null +++ b/src/curl_xml.c @@ -0,0 +1,930 @@ +/** + * collectd - src/curl_xml.c + * Copyright (C) 2009,2010 Amit Gupta + * + * 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: + * Amit Gupta + **/ + +#include "collectd.h" +#include "common.h" +#include "plugin.h" +#include "configfile.h" +#include "utils_llist.h" + +#include +#include +#include + +#include + +#define CX_DEFAULT_HOST "localhost" + +/* + * Private data structures + */ +struct cx_values_s /* {{{ */ +{ + char path[DATA_MAX_NAME_LEN]; + size_t path_len; +}; +typedef struct cx_values_s cx_values_t; +/* }}} */ + +struct cx_xpath_s /* {{{ */ +{ + char *path; + char *type; + cx_values_t *values; + int values_len; + char *instance_prefix; + char *instance; + int is_table; + unsigned long magic; +}; +typedef struct cx_xpath_s cx_xpath_t; +/* }}} */ + +struct cx_s /* {{{ */ +{ + char *instance; + char *host; + + char *url; + char *user; + char *pass; + char *credentials; + _Bool verify_peer; + _Bool verify_host; + char *cacert; + + CURL *curl; + char curl_errbuf[CURL_ERROR_SIZE]; + char *buffer; + size_t buffer_size; + size_t buffer_fill; + + llist_t *list; /* list of xpath blocks */ +}; +typedef struct cx_s cx_t; /* }}} */ + +/* + * Private functions + */ +static size_t cx_curl_callback (void *buf, /* {{{ */ + size_t size, size_t nmemb, void *user_data) +{ + size_t len = size * nmemb; + cx_t *db; + + db = user_data; + if (db == NULL) + { + ERROR ("curl_xml plugin: cx_curl_callback: " + "user_data pointer is NULL."); + return (0); + } + + if (len <= 0) + return (len); + + if ((db->buffer_fill + len) >= db->buffer_size) + { + char *temp; + + temp = (char *) realloc (db->buffer, + db->buffer_fill + len + 1); + if (temp == NULL) + { + ERROR ("curl_xml plugin: realloc failed."); + return (0); + } + db->buffer = temp; + db->buffer_size = db->buffer_fill + len + 1; + } + + memcpy (db->buffer + db->buffer_fill, (char *) buf, len); + db->buffer_fill += len; + db->buffer[db->buffer_fill] = 0; + + return (len); +} /* }}} size_t cx_curl_callback */ + +static void cx_xpath_free (cx_xpath_t *xpath) /* {{{ */ +{ + if (xpath == NULL) + return; + + sfree (xpath->path); + sfree (xpath->type); + sfree (xpath->instance_prefix); + sfree (xpath->instance); + sfree (xpath->values); + sfree (xpath); +} /* }}} void cx_xpath_free */ + +static void cx_list_free (llist_t *list) /* {{{ */ +{ + llentry_t *le; + + le = llist_head (list); + while (le != NULL) + { + llentry_t *le_next; + + le_next = le->next; + + sfree (le->key); + cx_xpath_free (le->value); + + le = le_next; + } + + llist_destroy (list); + list = NULL; +} /* }}} void cx_list_free */ + +static void cx_free (void *arg) /* {{{ */ +{ + cx_t *db; + + DEBUG ("curl_xml plugin: cx_free (arg = %p);", arg); + + db = (cx_t *) arg; + + if (db == NULL) + return; + + if (db->curl != NULL) + curl_easy_cleanup (db->curl); + db->curl = NULL; + + if (db->list != NULL) + cx_list_free (db->list); + + sfree (db->buffer); + sfree (db->instance); + sfree (db->host); + + sfree (db->url); + sfree (db->user); + sfree (db->pass); + sfree (db->credentials); + sfree (db->cacert); + + sfree (db); +} /* }}} void cx_free */ + +static int cx_check_type (const data_set_t *ds, cx_xpath_t *xpath) /* {{{ */ +{ + if (!ds) + { + WARNING ("curl_xml plugin: DataSet `%s' not defined.", xpath->type); + return (-1); + } + + if (ds->ds_num != xpath->values_len) + { + WARNING ("curl_xml plugin: DataSet `%s' requires %i values, but config talks about %i", + xpath->type, ds->ds_num, xpath->values_len); + return (-1); + } + + return (0); +} /* }}} cx_check_type */ + +static xmlXPathObjectPtr cx_evaluate_xpath (xmlXPathContextPtr xpath_ctx, /* {{{ */ + xmlChar *expr) +{ + xmlXPathObjectPtr xpath_obj; + + /* XXX: When to free this? */ + xpath_obj = xmlXPathEvalExpression(BAD_CAST expr, xpath_ctx); + if (xpath_obj == NULL) + { + WARNING ("curl_xml plugin: " + "Error unable to evaluate xpath expression \"%s\". Skipping...", expr); + return NULL; + } + + return xpath_obj; +} /* }}} cx_evaluate_xpath */ + +static int cx_if_not_text_node (xmlNodePtr node) /* {{{ */ +{ + if (node->type == XML_TEXT_NODE || node->type == XML_ATTRIBUTE_NODE) + return (0); + + WARNING ("curl_xml plugin: " + "Node \"%s\" doesn't seem to be a text node. Skipping...", node->name); + return -1; +} /* }}} cx_if_not_text_node */ + +static int cx_handle_single_value_xpath (xmlXPathContextPtr xpath_ctx, /* {{{ */ + cx_xpath_t *xpath, + const data_set_t *ds, value_list_t *vl, int index) +{ + xmlXPathObjectPtr values_node_obj; + xmlNodeSetPtr values_node; + int tmp_size; + char *node_value; + + values_node_obj = cx_evaluate_xpath (xpath_ctx, BAD_CAST xpath->values[index].path); + if (values_node_obj == NULL) + return (-1); /* Error already logged. */ + + values_node = values_node_obj->nodesetval; + tmp_size = (values_node) ? values_node->nodeNr : 0; + + if (tmp_size == 0) + { + WARNING ("curl_xml plugin: " + "relative xpath expression \"%s\" doesn't match any of the nodes. " + "Skipping...", xpath->values[index].path); + xmlXPathFreeObject (values_node_obj); + return (-1); + } + + if (tmp_size > 1) + { + WARNING ("curl_xml plugin: " + "relative xpath expression \"%s\" is expected to return " + "only one node. Skipping...", xpath->values[index].path); + xmlXPathFreeObject (values_node_obj); + return (-1); + } + + /* ignoring the element if other than textnode/attribute*/ + if (cx_if_not_text_node(values_node->nodeTab[0])) + { + WARNING ("curl_xml plugin: " + "relative xpath expression \"%s\" is expected to return " + "only text/attribute node which is not the case. Skipping...", + xpath->values[index].path); + xmlXPathFreeObject (values_node_obj); + return (-1); + } + + node_value = (char *) xmlNodeGetContent(values_node->nodeTab[0]); + switch (ds->ds[index].type) + { + case DS_TYPE_COUNTER: + vl->values[index].counter = (counter_t) strtoull (node_value, + /* endptr = */ NULL, /* base = */ 0); + break; + case DS_TYPE_DERIVE: + vl->values[index].derive = (derive_t) strtoll (node_value, + /* endptr = */ NULL, /* base = */ 0); + break; + case DS_TYPE_ABSOLUTE: + vl->values[index].absolute = (absolute_t) strtoull (node_value, + /* endptr = */ NULL, /* base = */ 0); + break; + case DS_TYPE_GAUGE: + vl->values[index].gauge = (gauge_t) strtod (node_value, + /* endptr = */ NULL); + } + + /* free up object */ + xmlXPathFreeObject (values_node_obj); + + /* We have reached here which means that + * we have got something to work */ + return (0); +} /* }}} int cx_handle_single_value_xpath */ + +static int cx_handle_all_value_xpaths (xmlXPathContextPtr xpath_ctx, /* {{{ */ + cx_xpath_t *xpath, + const data_set_t *ds, value_list_t *vl) +{ + value_t values[xpath->values_len]; + int status; + int i; + + assert (xpath->values_len > 0); + assert (xpath->values_len == vl->values_len); + assert (xpath->values_len == ds->ds_num); + vl->values = values; + + for (i = 0; i < xpath->values_len; i++) + { + status = cx_handle_single_value_xpath (xpath_ctx, xpath, ds, vl, i); + if (status != 0) + return (-1); /* An error has been printed. */ + } /* for (i = 0; i < xpath->values_len; i++) */ + + plugin_dispatch_values (vl); + vl->values = NULL; + + return (0); +} /* }}} int cx_handle_all_value_xpaths */ + +static int cx_handle_instance_xpath (xmlXPathContextPtr xpath_ctx, /* {{{ */ + cx_xpath_t *xpath, value_list_t *vl, + _Bool is_table) +{ + xmlXPathObjectPtr instance_node_obj = NULL; + xmlNodeSetPtr instance_node = NULL; + + memset (vl->type_instance, 0, sizeof (vl->type_instance)); + + /* If the base xpath returns more than one block, the result is assumed to be + * a table. The `Instnce' option is not optional in this case. Check for the + * condition and inform the user. */ + if (is_table && (vl->type_instance == NULL)) + { + WARNING ("curl_xml plugin: " + "Base-XPath %s is a table (more than one result was returned), " + "but no instance-XPath has been defined.", + xpath->path); + return (-1); + } + + /* instance has to be an xpath expression */ + if (xpath->instance != NULL) + { + int tmp_size; + + instance_node_obj = cx_evaluate_xpath (xpath_ctx, BAD_CAST xpath->instance); + if (instance_node_obj == NULL) + return (-1); /* error is logged already */ + + instance_node = instance_node_obj->nodesetval; + tmp_size = (instance_node) ? instance_node->nodeNr : 0; + + if ( (tmp_size == 0) && (is_table) ) + { + WARNING ("curl_xml plugin: " + "relative xpath expression for 'InstanceFrom' \"%s\" doesn't match " + "any of the nodes. Skipping the node.", xpath->instance); + xmlXPathFreeObject (instance_node_obj); + return (-1); + } + + if (tmp_size > 1) + { + WARNING ("curl_xml plugin: " + "relative xpath expression for 'InstanceFrom' \"%s\" is expected " + "to return only one text node. Skipping the node.", xpath->instance); + xmlXPathFreeObject (instance_node_obj); + return (-1); + } + + /* ignoring the element if other than textnode/attribute */ + if (cx_if_not_text_node(instance_node->nodeTab[0])) + { + WARNING ("curl_xml plugin: " + "relative xpath expression \"%s\" is expected to return only text node " + "which is not the case. Skipping the node.", xpath->instance); + xmlXPathFreeObject (instance_node_obj); + return (-1); + } + } /* if (xpath->instance != NULL) */ + + if (xpath->instance_prefix != NULL) + { + if (instance_node != NULL) + ssnprintf (vl->type_instance, sizeof (vl->type_instance),"%s%s", + xpath->instance_prefix, (char *) xmlNodeGetContent(instance_node->nodeTab[0])); + else + sstrncpy (vl->type_instance, xpath->instance_prefix, + sizeof (vl->type_instance)); + } + else + { + /* If instance_prefix and instance_node are NULL, then + * don't set the type_instance */ + if (instance_node != NULL) + sstrncpy (vl->type_instance, (char *) xmlNodeGetContent(instance_node->nodeTab[0]), + sizeof (vl->type_instance)); + } + + /* Free `instance_node_obj' this late, because `instance_node' points to + * somewhere inside this structure. */ + xmlXPathFreeObject (instance_node_obj); + + return (0); +} /* }}} int cx_handle_instance_xpath */ + +static int cx_handle_base_xpath (char *plugin_instance, /* {{{ */ + xmlXPathContextPtr xpath_ctx, const data_set_t *ds, + char *base_xpath, cx_xpath_t *xpath) +{ + int total_nodes; + int i; + + xmlXPathObjectPtr base_node_obj = NULL; + xmlNodeSetPtr base_nodes = NULL; + + value_list_t vl = VALUE_LIST_INIT; + + base_node_obj = cx_evaluate_xpath (xpath_ctx, BAD_CAST base_xpath); + if (base_node_obj == NULL) + return -1; /* error is logged already */ + + base_nodes = base_node_obj->nodesetval; + total_nodes = (base_nodes) ? base_nodes->nodeNr : 0; + + if (total_nodes == 0) + { + ERROR ("curl_xml plugin: " + "xpath expression \"%s\" doesn't match any of the nodes. " + "Skipping the xpath block...", base_xpath); + xmlXPathFreeObject (base_node_obj); + return -1; + } + + /* If base_xpath returned multiple results, then */ + /* Instance in the xpath block is required */ + if (total_nodes > 1 && xpath->instance == NULL) + { + ERROR ("curl_xml plugin: " + "InstanceFrom is must in xpath block since the base xpath expression \"%s\" " + "returned multiple results. Skipping the xpath block...", base_xpath); + return -1; + } + + /* set the values for the value_list */ + vl.values_len = ds->ds_num; + sstrncpy (vl.type, xpath->type, sizeof (vl.type)); + sstrncpy (vl.plugin, "curl_xml", sizeof (vl.plugin)); + sstrncpy (vl.host, hostname_g, sizeof (vl.host)); + if (plugin_instance != NULL) + sstrncpy (vl.plugin_instance, plugin_instance, sizeof (vl.plugin_instance)); + + for (i = 0; i < total_nodes; i++) + { + int status; + + xpath_ctx->node = base_nodes->nodeTab[i]; + + status = cx_handle_instance_xpath (xpath_ctx, xpath, &vl, + /* is_table = */ (total_nodes > 1)); + if (status != 0) + continue; /* An error has already been reported. */ + + status = cx_handle_all_value_xpaths (xpath_ctx, xpath, ds, &vl); + if (status != 0) + continue; /* An error has been logged. */ + } /* for (i = 0; i < total_nodes; i++) */ + + /* free up the allocated memory */ + xmlXPathFreeObject (base_node_obj); + + return (0); +} /* }}} cx_handle_base_xpath */ + +static int cx_handle_parsed_xml(xmlDocPtr doc, /* {{{ */ + xmlXPathContextPtr xpath_ctx, cx_t *db) +{ + llentry_t *le; + const data_set_t *ds; + cx_xpath_t *xpath; + int status=-1; + + + le = llist_head (db->list); + while (le != NULL) + { + /* get the ds */ + xpath = (cx_xpath_t *) le->value; + ds = plugin_get_ds (xpath->type); + + if ( (cx_check_type(ds, xpath) == 0) && + (cx_handle_base_xpath(db->instance, xpath_ctx, ds, le->key, xpath) == 0) ) + status = 0; /* we got atleast one success */ + + le = le->next; + } /* while (le != NULL) */ + + return status; +} /* }}} cx_handle_parsed_xml */ + +static int cx_parse_stats_xml(xmlChar* xml, cx_t *db) /* {{{ */ +{ + int status; + xmlDocPtr doc; + xmlXPathContextPtr xpath_ctx; + + /* Load the XML */ + doc = xmlParseDoc(xml); + if (doc == NULL) + { + ERROR ("curl_xml plugin: Failed to parse the xml document - %s", xml); + return (-1); + } + + xpath_ctx = xmlXPathNewContext(doc); + if(xpath_ctx == NULL) + { + ERROR ("curl_xml plugin: Failed to create the xml context"); + xmlFreeDoc(doc); + return (-1); + } + + status = cx_handle_parsed_xml (doc, xpath_ctx, db); + /* Cleanup */ + xmlXPathFreeContext(xpath_ctx); + xmlFreeDoc(doc); + return status; +} /* }}} cx_parse_stats_xml */ + +static int cx_curl_perform (cx_t *db, CURL *curl) /* {{{ */ +{ + int status; + long rc; + char *ptr; + char *url; + + db->buffer_fill = 0; + status = curl_easy_perform (curl); + + curl_easy_getinfo(curl, CURLINFO_EFFECTIVE_URL, &url); + curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &rc); + + if (rc != 200) + { + ERROR ("curl_xml plugin: curl_easy_perform failed with response code %ld (%s)", + rc, url); + return (-1); + } + + if (status != 0) + { + ERROR ("curl_xml plugin: curl_easy_perform failed with status %i: %s (%s)", + status, db->curl_errbuf, url); + return (-1); + } + + ptr = db->buffer; + + status = cx_parse_stats_xml(BAD_CAST ptr, db); + db->buffer_fill = 0; + + return status; +} /* }}} int cx_curl_perform */ + +static int cx_read (user_data_t *ud) /* {{{ */ +{ + cx_t *db; + + if ((ud == NULL) || (ud->data == NULL)) + { + ERROR ("curl_xml plugin: cx_read: Invalid user data."); + return (-1); + } + + db = (cx_t *) ud->data; + + return cx_curl_perform (db, db->curl); +} /* }}} int cx_read */ + +/* Configuration handling functions {{{ */ + +static int cx_config_add_values (const char *name, cx_xpath_t *xpath, /* {{{ */ + oconfig_item_t *ci) +{ + int i; + + if (ci->values_num < 1) + { + WARNING ("curl_xml plugin: `ValuesFrom' needs at least one argument."); + return (-1); + } + + for (i = 0; i < ci->values_num; i++) + if (ci->values[i].type != OCONFIG_TYPE_STRING) + { + WARNING ("curl_xml plugin: `ValuesFrom' needs only string argument."); + return (-1); + } + + sfree (xpath->values); + + xpath->values_len = 0; + xpath->values = (cx_values_t *) malloc (sizeof (cx_values_t) * ci->values_num); + if (xpath->values == NULL) + return (-1); + xpath->values_len = ci->values_num; + + /* populate cx_values_t structure */ + for (i = 0; i < ci->values_num; i++) + { + xpath->values[i].path_len = sizeof (ci->values[i].value.string); + sstrncpy (xpath->values[i].path, ci->values[i].value.string, sizeof (xpath->values[i].path)); + } + + return (0); +} /* }}} cx_config_add_values */ + +static int cx_config_add_xpath (cx_t *db, /* {{{ */ + oconfig_item_t *ci) +{ + cx_xpath_t *xpath; + int status; + int i; + + xpath = (cx_xpath_t *) malloc (sizeof (*xpath)); + if (xpath == NULL) + { + ERROR ("curl_xml plugin: malloc failed."); + return (-1); + } + memset (xpath, 0, sizeof (*xpath)); + + status = cf_util_get_string (ci, &xpath->path); + if (status != 0) + { + sfree (xpath); + return (status); + } + + /* error out if xpath->path is an empty string */ + if (*xpath->path == 0) + { + ERROR ("curl_xml plugin: invalid xpath. " + "xpath value can't be an empty string"); + return (-1); + } + + status = 0; + for (i = 0; i < ci->children_num; i++) + { + oconfig_item_t *child = ci->children + i; + + if (strcasecmp ("Type", child->key) == 0) + status = cf_util_get_string (child, &xpath->type); + else if (strcasecmp ("InstancePrefix", child->key) == 0) + status = cf_util_get_string (child, &xpath->instance_prefix); + else if (strcasecmp ("InstanceFrom", child->key) == 0) + status = cf_util_get_string (child, &xpath->instance); + else if (strcasecmp ("ValuesFrom", child->key) == 0) + status = cx_config_add_values ("ValuesFrom", xpath, child); + else + { + WARNING ("curl_xml plugin: Option `%s' not allowed here.", child->key); + status = -1; + } + + if (status != 0) + break; + } /* for (i = 0; i < ci->children_num; i++) */ + + if (status == 0 && xpath->type == NULL) + { + WARNING ("curl_xml plugin: `Type' missing in `xpath' block."); + status = -1; + } + + if (status == 0) + { + char *name; + llentry_t *le; + + if (db->list == NULL) + { + db->list = llist_create(); + if (db->list == NULL) + { + ERROR ("curl_xml plugin: list creation failed."); + return (-1); + } + } + + name = strdup(xpath->path); + if (name == NULL) + { + ERROR ("curl_xml plugin: strdup failed."); + return (-1); + } + + le = llentry_create (name, xpath); + if (le == NULL) + { + ERROR ("curl_xml plugin: llentry_create failed."); + return (-1); + } + + llist_append (db->list, le); + } + + return (status); +} /* }}} int cx_config_add_xpath */ + +/* Initialize db->curl */ +static int cx_init_curl (cx_t *db) /* {{{ */ +{ + db->curl = curl_easy_init (); + if (db->curl == NULL) + { + ERROR ("curl_xml plugin: curl_easy_init failed."); + return (-1); + } + + curl_easy_setopt (db->curl, CURLOPT_WRITEFUNCTION, cx_curl_callback); + curl_easy_setopt (db->curl, CURLOPT_WRITEDATA, db); + curl_easy_setopt (db->curl, CURLOPT_USERAGENT, + PACKAGE_NAME"/"PACKAGE_VERSION); + curl_easy_setopt (db->curl, CURLOPT_ERRORBUFFER, db->curl_errbuf); + curl_easy_setopt (db->curl, CURLOPT_URL, db->url); + + if (db->user != NULL) + { + size_t credentials_size; + + credentials_size = strlen (db->user) + 2; + if (db->pass != NULL) + credentials_size += strlen (db->pass); + + db->credentials = (char *) malloc (credentials_size); + if (db->credentials == NULL) + { + ERROR ("curl_xml plugin: malloc failed."); + return (-1); + } + + ssnprintf (db->credentials, credentials_size, "%s:%s", + db->user, (db->pass == NULL) ? "" : db->pass); + curl_easy_setopt (db->curl, CURLOPT_USERPWD, db->credentials); + } + + curl_easy_setopt (db->curl, CURLOPT_SSL_VERIFYPEER, db->verify_peer ? 1L : 0L); + curl_easy_setopt (db->curl, CURLOPT_SSL_VERIFYHOST, + db->verify_host ? 2L : 0L); + if (db->cacert != NULL) + curl_easy_setopt (db->curl, CURLOPT_CAINFO, db->cacert); + + return (0); +} /* }}} int cx_init_curl */ + +static int cx_config_add_url (oconfig_item_t *ci) /* {{{ */ +{ + cx_t *db; + int status = 0; + int i; + + if ((ci->values_num != 1) + || (ci->values[0].type != OCONFIG_TYPE_STRING)) + { + WARNING ("curl_xml plugin: The `URL' block " + "needs exactly one string argument."); + return (-1); + } + + db = (cx_t *) malloc (sizeof (*db)); + if (db == NULL) + { + ERROR ("curl_xml plugin: malloc failed."); + return (-1); + } + memset (db, 0, sizeof (*db)); + + if (strcasecmp ("URL", ci->key) == 0) + { + status = cf_util_get_string (ci, &db->url); + if (status != 0) + { + sfree (db); + return (status); + } + } + else + { + ERROR ("curl_xml plugin: cx_config: " + "Invalid key: %s", ci->key); + return (-1); + } + + /* Fill the `cx_t' structure.. */ + for (i = 0; i < ci->children_num; i++) + { + oconfig_item_t *child = ci->children + i; + + if (strcasecmp ("Instance", child->key) == 0) + status = cf_util_get_string (child, &db->instance); + else if (strcasecmp ("Host", child->key) == 0) + status = cf_util_get_string (child, &db->host); + else if (strcasecmp ("User", child->key) == 0) + status = cf_util_get_string (child, &db->user); + else if (strcasecmp ("Password", child->key) == 0) + status = cf_util_get_string (child, &db->pass); + else if (strcasecmp ("VerifyPeer", child->key) == 0) + status = cf_util_get_boolean (child, &db->verify_peer); + else if (strcasecmp ("VerifyHost", child->key) == 0) + status = cf_util_get_boolean (child, &db->verify_host); + else if (strcasecmp ("CACert", child->key) == 0) + status = cf_util_get_string (child, &db->cacert); + else if (strcasecmp ("xpath", child->key) == 0) + status = cx_config_add_xpath (db, child); + else + { + WARNING ("curl_xml plugin: Option `%s' not allowed here.", child->key); + status = -1; + } + + if (status != 0) + break; + } + + if (status == 0) + { + if (db->list == NULL) + { + WARNING ("curl_xml plugin: No (valid) `Key' block " + "within `URL' block `%s'.", db->url); + status = -1; + } + if (status == 0) + status = cx_init_curl (db); + } + + /* If all went well, register this database for reading */ + if (status == 0) + { + user_data_t ud; + char cb_name[DATA_MAX_NAME_LEN]; + + if (db->instance == NULL) + db->instance = strdup("default"); + + DEBUG ("curl_xml plugin: Registering new read callback: %s", + db->instance); + + memset (&ud, 0, sizeof (ud)); + ud.data = (void *) db; + ud.free_func = cx_free; + + ssnprintf (cb_name, sizeof (cb_name), "curl_xml-%s-%s", + db->instance, db->url); + + plugin_register_complex_read (cb_name, cx_read, + /* interval = */ NULL, &ud); + } + else + { + cx_free (db); + return (-1); + } + + return (0); +} /* }}} int cx_config_add_url */ + +/* }}} End of configuration handling functions */ + +static int cx_config (oconfig_item_t *ci) /* {{{ */ +{ + int success; + int errors; + int status; + int i; + + success = 0; + errors = 0; + + for (i = 0; i < ci->children_num; i++) + { + oconfig_item_t *child = ci->children + i; + + if (strcasecmp ("URL", child->key) == 0) + { + status = cx_config_add_url (child); + if (status == 0) + success++; + else + errors++; + } + else + { + WARNING ("curl_xml plugin: Option `%s' not allowed here.", child->key); + errors++; + } + } + + if ((success == 0) && (errors > 0)) + { + ERROR ("curl_xml plugin: All statements failed."); + return (-1); + } + + return (0); +} /* }}} int cx_config */ + +void module_register (void) +{ + plugin_register_complex_config ("curl_xml", cx_config); +} /* void module_register */ + +/* vim: set sw=2 sts=2 et fdm=marker : */ diff --git a/src/filecount.c b/src/filecount.c index 05bb4b37..47f99e91 100644 --- a/src/filecount.c +++ b/src/filecount.c @@ -32,6 +32,7 @@ #include #define FC_RECURSIVE 1 +#define FC_HIDDEN 2 struct fc_directory_conf_s { @@ -310,8 +311,8 @@ static int fc_config_add_dir_size (fc_directory_conf_t *dir, return (0); } /* int fc_config_add_dir_size */ -static int fc_config_add_dir_recursive (fc_directory_conf_t *dir, - oconfig_item_t *ci) +static int fc_config_add_dir_option (fc_directory_conf_t *dir, + oconfig_item_t *ci, int bit) { if ((ci->values_num != 1) || (ci->values[0].type != OCONFIG_TYPE_BOOLEAN)) @@ -322,12 +323,12 @@ static int fc_config_add_dir_recursive (fc_directory_conf_t *dir, } if (ci->values[0].value.boolean) - dir->options |= FC_RECURSIVE; + dir->options |= bit; else - dir->options &= ~FC_RECURSIVE; + dir->options &= ~bit; return (0); -} /* int fc_config_add_dir_recursive */ +} /* int fc_config_add_dir_option */ static int fc_config_add_dir (oconfig_item_t *ci) { @@ -380,7 +381,9 @@ static int fc_config_add_dir (oconfig_item_t *ci) else if (strcasecmp ("Size", option->key) == 0) status = fc_config_add_dir_size (dir, option); else if (strcasecmp ("Recursive", option->key) == 0) - status = fc_config_add_dir_recursive (dir, option); + status = fc_config_add_dir_option (dir, option, FC_RECURSIVE); + else if (strcasecmp ("IncludeHidden", option->key) == 0) + status = fc_config_add_dir_option (dir, option, FC_HIDDEN); else { WARNING ("filecount plugin: fc_config_add_dir: " @@ -475,7 +478,8 @@ static int fc_read_dir_callback (const char *dirname, const char *filename, if (S_ISDIR (statbuf.st_mode) && (dir->options & FC_RECURSIVE)) { - status = walk_directory (abs_path, fc_read_dir_callback, dir); + status = walk_directory (abs_path, fc_read_dir_callback, dir, + /* include hidden = */ (dir->options & FC_HIDDEN) ? 1 : 0); return (status); } else if (!S_ISREG (statbuf.st_mode)) @@ -537,8 +541,9 @@ static int fc_read_dir (fc_directory_conf_t *dir) if (dir->mtime != 0) dir->now = time (NULL); - - status = walk_directory (dir->path, fc_read_dir_callback, dir); + + status = walk_directory (dir->path, fc_read_dir_callback, dir, + /* include hidden */ (dir->options & FC_HIDDEN) ? 1 : 0); if (status != 0) { WARNING ("filecount plugin: walk_directory (%s) failed.", dir->path); diff --git a/src/meta_data.c b/src/meta_data.c index 3a3f5e79..6a336c4b 100644 --- a/src/meta_data.c +++ b/src/meta_data.c @@ -26,15 +26,6 @@ #include /* - * Defines - */ -#define MD_TYPE_STRING 1 -#define MD_TYPE_SIGNED_INT 2 -#define MD_TYPE_UNSIGNED_INT 3 -#define MD_TYPE_DOUBLE 4 -#define MD_TYPE_BOOLEAN 5 - -/* * Data types */ union meta_value_u @@ -249,6 +240,49 @@ int meta_data_exists (meta_data_t *md, const char *key) /* {{{ */ return (0); } /* }}} int meta_data_exists */ +int meta_data_type (meta_data_t *md, const char *key) /* {{{ */ +{ + meta_entry_t *e; + + if ((md == NULL) || (key == NULL)) + return -EINVAL; + + pthread_mutex_lock (&md->lock); + + for (e = md->head; e != NULL; e = e->next) + { + if (strcasecmp (key, e->key) == 0) + { + pthread_mutex_unlock (&md->lock); + return e->type; + } + } + + pthread_mutex_unlock (&md->lock); + return 0; +} /* }}} int meta_data_type */ + +int meta_data_toc (meta_data_t *md, char ***toc) /* {{{ */ +{ + int i = 0, count = 0; + meta_entry_t *e; + + if ((md == NULL) || (toc == NULL)) + return -EINVAL; + + pthread_mutex_lock (&md->lock); + + for (e = md->head; e != NULL; e = e->next) + ++count; + + *toc = malloc(count * sizeof(**toc)); + for (e = md->head; e != NULL; e = e->next) + (*toc)[i++] = strdup(e->key); + + pthread_mutex_unlock (&md->lock); + return count; +} /* }}} int meta_data_toc */ + int meta_data_delete (meta_data_t *md, const char *key) /* {{{ */ { meta_entry_t *this; diff --git a/src/meta_data.h b/src/meta_data.h index 8e5a7852..9ef7b0a8 100644 --- a/src/meta_data.h +++ b/src/meta_data.h @@ -24,6 +24,15 @@ #include "collectd.h" +/* + * Defines + */ +#define MD_TYPE_STRING 1 +#define MD_TYPE_SIGNED_INT 2 +#define MD_TYPE_UNSIGNED_INT 3 +#define MD_TYPE_DOUBLE 4 +#define MD_TYPE_BOOLEAN 5 + struct meta_data_s; typedef struct meta_data_s meta_data_t; @@ -31,6 +40,8 @@ meta_data_t *meta_data_create (void); void meta_data_destroy (meta_data_t *md); int meta_data_exists (meta_data_t *md, const char *key); +int meta_data_type (meta_data_t *md, const char *key); +int meta_data_toc (meta_data_t *md, char ***toc); int meta_data_delete (meta_data_t *md, const char *key); int meta_data_add_string (meta_data_t *md, diff --git a/src/pyconfig.c b/src/pyconfig.c index bac39ae9..b5c01aaf 100644 --- a/src/pyconfig.c +++ b/src/pyconfig.c @@ -74,10 +74,17 @@ static int Config_init(PyObject *s, PyObject *args, PyObject *kwds) { Config *self = (Config *) s; static char *kwlist[] = {"key", "parent", "values", "children", NULL}; - if (!PyArg_ParseTupleAndKeywords(args, kwds, "S|OOO", kwlist, + if (!PyArg_ParseTupleAndKeywords(args, kwds, "O|OOO", kwlist, &key, &parent, &values, &children)) return -1; + if (!IS_BYTES_OR_UNICODE(key)) { + PyErr_SetString(PyExc_TypeError, "argument 1 must be str"); + Py_XDECREF(parent); + Py_XDECREF(values); + Py_XDECREF(children); + return -1; + } if (values == NULL) { values = PyTuple_New(0); PyErr_Clear(); @@ -113,8 +120,28 @@ static int Config_init(PyObject *s, PyObject *args, PyObject *kwds) { static PyObject *Config_repr(PyObject *s) { Config *self = (Config *) s; + PyObject *ret = NULL; + static PyObject *node_prefix = NULL, *root_prefix = NULL, *ending = NULL; + + /* This is ok because we have the GIL, so this is thread-save by default. */ + if (node_prefix == NULL) + node_prefix = cpy_string_to_unicode_or_bytes(""); + if (node_prefix == NULL || root_prefix == NULL || ending == NULL) + return NULL; - return PyString_FromFormat("", self->parent == Py_None ? "root " : "", PyString_AsString(PyObject_Str(self->key))); + ret = PyObject_Str(self->key); + CPY_SUBSTITUTE(PyObject_Repr, ret, ret); + if (self->parent == NULL || self->parent == Py_None) + CPY_STRCAT(&ret, root_prefix); + else + CPY_STRCAT(&ret, node_prefix); + CPY_STRCAT(&ret, ending); + + return ret; } static int Config_traverse(PyObject *self, visitproc visit, void *arg) { @@ -123,8 +150,7 @@ static int Config_traverse(PyObject *self, visitproc visit, void *arg) { Py_VISIT(c->key); Py_VISIT(c->values); Py_VISIT(c->children); - return 0; -} + return 0;} static int Config_clear(PyObject *self) { Config *c = (Config *) self; @@ -149,8 +175,7 @@ static PyMemberDef Config_members[] = { }; PyTypeObject ConfigType = { - PyObject_HEAD_INIT(NULL) - 0, /* Always 0 */ + CPY_INIT_TYPE "collectd.Config", /* tp_name */ sizeof(Config), /* tp_basicsize */ 0, /* Will be filled in later */ diff --git a/src/python.c b/src/python.c index d750d95b..5664b0c6 100644 --- a/src/python.c +++ b/src/python.c @@ -245,7 +245,7 @@ static void cpy_build_name(char *buf, size_t size, PyObject *callback, const cha mod = PyObject_GetAttrString(callback, "__module__"); /* New reference. */ if (mod != NULL) - module = PyString_AsString(mod); + module = cpy_unicode_or_bytes_to_string(&mod); if (module != NULL) { snprintf(buf, size, "python.%s", module); @@ -268,11 +268,11 @@ static void cpy_log_exception(const char *context) { PyErr_NormalizeException(&type, &value, &traceback); if (type == NULL) return; tn = PyObject_GetAttrString(type, "__name__"); /* New reference. */ - m = PyObject_GetAttrString(value, "message"); /* New reference. */ + m = PyObject_Str(value); /* New reference. */ if (tn != NULL) - typename = PyString_AsString(tn); + typename = cpy_unicode_or_bytes_to_string(&tn); if (m != NULL) - message = PyString_AsString(m); + message = cpy_unicode_or_bytes_to_string(&m); if (typename == NULL) typename = "NamelessException"; if (message == NULL) @@ -301,7 +301,9 @@ static void cpy_log_exception(const char *context) { PyObject *line; line = PyList_GET_ITEM(list, i); /* Borrowed reference. */ - s = strdup(PyString_AsString(line)); + Py_INCREF(line); + s = strdup(cpy_unicode_or_bytes_to_string(&line)); + Py_DECREF(line); if (s[strlen(s) - 1] == '\n') s[strlen(s) - 1] = 0; Py_BEGIN_ALLOW_THREADS @@ -333,7 +335,8 @@ static int cpy_read_callback(user_data_t *data) { static int cpy_write_callback(const data_set_t *ds, const value_list_t *value_list, user_data_t *data) { int i; cpy_callback_t *c = data->data; - PyObject *ret, *v, *list; + PyObject *ret, *list; + Values *v; CPY_LOCK_THREADS list = PyList_New(value_list->values_len); /* New reference. */ @@ -371,10 +374,15 @@ static int cpy_write_callback(const data_set_t *ds, const value_list_t *value_li CPY_RETURN_FROM_THREADS 0; } } - v = PyObject_CallFunction((void *) &ValuesType, "sOssssdi", value_list->type, list, - value_list->plugin_instance, value_list->type_instance, value_list->plugin, - value_list->host, (double) value_list->time, value_list->interval); - Py_DECREF(list); + v = PyObject_New(Values, (void *) &ValuesType); + sstrncpy(v->data.host, value_list->host, sizeof(v->data.host)); + sstrncpy(v->data.type, value_list->type, sizeof(v->data.type)); + sstrncpy(v->data.type_instance, value_list->type_instance, sizeof(v->data.type_instance)); + sstrncpy(v->data.plugin, value_list->plugin, sizeof(v->data.plugin)); + sstrncpy(v->data.plugin_instance, value_list->plugin_instance, sizeof(v->data.plugin_instance)); + v->data.time = value_list->time; + v->interval = value_list->interval; + v->values = list; ret = PyObject_CallFunctionObjArgs(c->callback, v, c->data, (void *) 0); /* New reference. */ if (ret == NULL) { cpy_log_exception("write callback"); @@ -387,12 +395,19 @@ static int cpy_write_callback(const data_set_t *ds, const value_list_t *value_li static int cpy_notification_callback(const notification_t *notification, user_data_t *data) { cpy_callback_t *c = data->data; - PyObject *ret, *n; + PyObject *ret; + Notification *n; CPY_LOCK_THREADS - n = PyObject_CallFunction((void *) &NotificationType, "ssssssdi", notification->type, notification->message, - notification->plugin_instance, notification->type_instance, notification->plugin, - notification->host, (double) notification->time, notification->severity); + n = PyObject_New(Notification, (void *) &NotificationType); + sstrncpy(n->data.host, notification->host, sizeof(n->data.host)); + sstrncpy(n->data.type, notification->type, sizeof(n->data.type)); + sstrncpy(n->data.type_instance, notification->type_instance, sizeof(n->data.type_instance)); + sstrncpy(n->data.plugin, notification->plugin, sizeof(n->data.plugin)); + sstrncpy(n->data.plugin_instance, notification->plugin_instance, sizeof(n->data.plugin_instance)); + n->data.time = notification->time; + sstrncpy(n->message, notification->message, sizeof(n->message)); + n->severity = notification->severity; ret = PyObject_CallFunctionObjArgs(c->callback, n, c->data, (void *) 0); /* New reference. */ if (ret == NULL) { cpy_log_exception("notification callback"); @@ -405,13 +420,14 @@ static int cpy_notification_callback(const notification_t *notification, user_da static void cpy_log_callback(int severity, const char *message, user_data_t *data) { cpy_callback_t * c = data->data; - PyObject *ret; + PyObject *ret, *text; CPY_LOCK_THREADS + text = cpy_string_to_unicode_or_bytes(message); if (c->data == NULL) - ret = PyObject_CallFunction(c->callback, "is", severity, message); /* New reference. */ + ret = PyObject_CallFunction(c->callback, "iN", severity, text); /* New reference. */ else - ret = PyObject_CallFunction(c->callback, "isO", severity, message, c->data); /* New reference. */ + ret = PyObject_CallFunction(c->callback, "iNO", severity, text, c->data); /* New reference. */ if (ret == NULL) { /* FIXME */ @@ -428,13 +444,14 @@ static void cpy_log_callback(int severity, const char *message, user_data_t *dat static void cpy_flush_callback(int timeout, const char *id, user_data_t *data) { cpy_callback_t * c = data->data; - PyObject *ret; + PyObject *ret, *text; CPY_LOCK_THREADS + text = cpy_string_to_unicode_or_bytes(id); if (c->data == NULL) - ret = PyObject_CallFunction(c->callback, "is", timeout, id); /* New reference. */ + ret = PyObject_CallFunction(c->callback, "iN", timeout, text); /* New reference. */ else - ret = PyObject_CallFunction(c->callback, "isO", timeout, id, c->data); /* New reference. */ + ret = PyObject_CallFunction(c->callback, "iNO", timeout, text, c->data); /* New reference. */ if (ret == NULL) { cpy_log_exception("flush callback"); @@ -451,7 +468,7 @@ static PyObject *cpy_register_generic(cpy_callback_t **list_head, PyObject *args PyObject *callback = NULL, *data = NULL, *mod = NULL; static char *kwlist[] = {"callback", "data", "name", NULL}; - if (PyArg_ParseTupleAndKeywords(args, kwds, "O|Oz", kwlist, &callback, &data, &name) == 0) return NULL; + if (PyArg_ParseTupleAndKeywords(args, kwds, "O|Oet", kwlist, &callback, &data, NULL, &name) == 0) return NULL; if (PyCallable_Check(callback) == 0) { PyErr_SetString(PyExc_TypeError, "callback needs a be a callable object."); return NULL; @@ -467,7 +484,7 @@ static PyObject *cpy_register_generic(cpy_callback_t **list_head, PyObject *args c->next = *list_head; *list_head = c; Py_XDECREF(mod); - return PyString_FromString(buf); + return cpy_string_to_unicode_or_bytes(buf); } static PyObject *cpy_flush(cpy_callback_t **list_head, PyObject *args, PyObject *kwds) { @@ -475,7 +492,7 @@ static PyObject *cpy_flush(cpy_callback_t **list_head, PyObject *args, PyObject const char *plugin = NULL, *identifier = NULL; static char *kwlist[] = {"plugin", "timeout", "identifier", NULL}; - if (PyArg_ParseTupleAndKeywords(args, kwds, "|ziz", kwlist, &plugin, &timeout, &identifier) == 0) return NULL; + if (PyArg_ParseTupleAndKeywords(args, kwds, "|etiet", kwlist, NULL, &plugin, &timeout, NULL, &identifier) == 0) return NULL; Py_BEGIN_ALLOW_THREADS plugin_flush(plugin, timeout, identifier); Py_END_ALLOW_THREADS @@ -501,7 +518,7 @@ static PyObject *cpy_register_generic_userdata(void *reg, void *handler, PyObjec PyObject *callback = NULL, *data = NULL; static char *kwlist[] = {"callback", "data", "name", NULL}; - if (PyArg_ParseTupleAndKeywords(args, kwds, "O|Oz", kwlist, &callback, &data, &name) == 0) return NULL; + if (PyArg_ParseTupleAndKeywords(args, kwds, "O|Oet", kwlist, &callback, &data, NULL, &name) == 0) return NULL; if (PyCallable_Check(callback) == 0) { PyErr_SetString(PyExc_TypeError, "callback needs a be a callable object."); return NULL; @@ -519,7 +536,7 @@ static PyObject *cpy_register_generic_userdata(void *reg, void *handler, PyObjec user_data->free_func = cpy_destroy_user_data; user_data->data = c; register_function(buf, handler, user_data); - return PyString_FromString(buf); + return cpy_string_to_unicode_or_bytes(buf); } static PyObject *cpy_register_read(PyObject *self, PyObject *args, PyObject *kwds) { @@ -532,7 +549,7 @@ static PyObject *cpy_register_read(PyObject *self, PyObject *args, PyObject *kwd struct timespec ts; static char *kwlist[] = {"callback", "interval", "data", "name", NULL}; - if (PyArg_ParseTupleAndKeywords(args, kwds, "O|dOz", kwlist, &callback, &interval, &data, &name) == 0) return NULL; + if (PyArg_ParseTupleAndKeywords(args, kwds, "O|dOet", kwlist, &callback, &interval, &data, NULL, &name) == 0) return NULL; if (PyCallable_Check(callback) == 0) { PyErr_SetString(PyExc_TypeError, "callback needs a be a callable object."); return NULL; @@ -552,7 +569,7 @@ static PyObject *cpy_register_read(PyObject *self, PyObject *args, PyObject *kwd ts.tv_sec = interval; ts.tv_nsec = (interval - ts.tv_sec) * 1000000000; plugin_register_complex_read(buf, cpy_read_callback, &ts, user_data); - return PyString_FromString(buf); + return cpy_string_to_unicode_or_bytes(buf); } static PyObject *cpy_register_log(PyObject *self, PyObject *args, PyObject *kwds) { @@ -581,7 +598,7 @@ static PyObject *cpy_register_shutdown(PyObject *self, PyObject *args, PyObject static PyObject *cpy_error(PyObject *self, PyObject *args) { const char *text; - if (PyArg_ParseTuple(args, "s", &text) == 0) return NULL; + if (PyArg_ParseTuple(args, "et", NULL, &text) == 0) return NULL; Py_BEGIN_ALLOW_THREADS plugin_log(LOG_ERR, "%s", text); Py_END_ALLOW_THREADS @@ -590,7 +607,7 @@ static PyObject *cpy_error(PyObject *self, PyObject *args) { static PyObject *cpy_warning(PyObject *self, PyObject *args) { const char *text; - if (PyArg_ParseTuple(args, "s", &text) == 0) return NULL; + if (PyArg_ParseTuple(args, "et", NULL, &text) == 0) return NULL; Py_BEGIN_ALLOW_THREADS plugin_log(LOG_WARNING, "%s", text); Py_END_ALLOW_THREADS @@ -599,7 +616,7 @@ static PyObject *cpy_warning(PyObject *self, PyObject *args) { static PyObject *cpy_notice(PyObject *self, PyObject *args) { const char *text; - if (PyArg_ParseTuple(args, "s", &text) == 0) return NULL; + if (PyArg_ParseTuple(args, "et", NULL, &text) == 0) return NULL; Py_BEGIN_ALLOW_THREADS plugin_log(LOG_NOTICE, "%s", text); Py_END_ALLOW_THREADS @@ -608,7 +625,7 @@ static PyObject *cpy_notice(PyObject *self, PyObject *args) { static PyObject *cpy_info(PyObject *self, PyObject *args) { const char *text; - if (PyArg_ParseTuple(args, "s", &text) == 0) return NULL; + if (PyArg_ParseTuple(args, "et", NULL, &text) == 0) return NULL; Py_BEGIN_ALLOW_THREADS plugin_log(LOG_INFO, "%s", text); Py_END_ALLOW_THREADS @@ -618,7 +635,7 @@ static PyObject *cpy_info(PyObject *self, PyObject *args) { static PyObject *cpy_debug(PyObject *self, PyObject *args) { #ifdef COLLECT_DEBUG const char *text; - if (PyArg_ParseTuple(args, "s", &text) == 0) return NULL; + if (PyArg_ParseTuple(args, "et", NULL, &text) == 0) return NULL; Py_BEGIN_ALLOW_THREADS plugin_log(LOG_DEBUG, "%s", text); Py_END_ALLOW_THREADS @@ -631,17 +648,13 @@ static PyObject *cpy_unregister_generic(cpy_callback_t **list_head, PyObject *ar const char *name; cpy_callback_t *prev = NULL, *tmp; - if (PyUnicode_Check(arg)) { - arg = PyUnicode_AsEncodedString(arg, NULL, NULL); - if (arg == NULL) - return NULL; - name = PyString_AsString(arg); - Py_DECREF(arg); - } else if (PyString_Check(arg)) { - name = PyString_AsString(arg); - } else { + Py_INCREF(arg); + name = cpy_unicode_or_bytes_to_string(&arg); + if (name == NULL) { + PyErr_Clear(); if (!PyCallable_Check(arg)) { PyErr_SetString(PyExc_TypeError, "This function needs a string or a callable object as its only parameter."); + Py_DECREF(arg); return NULL; } cpy_build_name(buf, sizeof(buf), arg, NULL); @@ -651,6 +664,7 @@ static PyObject *cpy_unregister_generic(cpy_callback_t **list_head, PyObject *ar if (strcmp(name, tmp->name) == 0) break; + Py_DECREF(arg); if (tmp == NULL) { PyErr_Format(PyExc_RuntimeError, "Unable to unregister %s callback '%s'.", desc, name); return NULL; @@ -671,25 +685,24 @@ static PyObject *cpy_unregister_generic_userdata(cpy_unregister_function_t *unre char buf[512]; const char *name; - if (PyUnicode_Check(arg)) { - arg = PyUnicode_AsEncodedString(arg, NULL, NULL); - if (arg == NULL) - return NULL; - name = PyString_AsString(arg); - Py_DECREF(arg); - } else if (PyString_Check(arg)) { - name = PyString_AsString(arg); - } else { + Py_INCREF(arg); + name = cpy_unicode_or_bytes_to_string(&arg); + if (name == NULL) { + PyErr_Clear(); if (!PyCallable_Check(arg)) { PyErr_SetString(PyExc_TypeError, "This function needs a string or a callable object as its only parameter."); + Py_DECREF(arg); return NULL; } cpy_build_name(buf, sizeof(buf), arg, NULL); name = buf; } - if (unreg(name) == 0) + if (unreg(name) == 0) { + Py_DECREF(arg); Py_RETURN_NONE; + } PyErr_Format(PyExc_RuntimeError, "Unable to unregister %s callback '%s'.", desc, name); + Py_DECREF(arg); return NULL; } @@ -860,7 +873,7 @@ static PyObject *cpy_oconfig_to_pyconfig(oconfig_item_t *ci, PyObject *parent) { values = PyTuple_New(ci->values_num); /* New reference. */ for (i = 0; i < ci->values_num; ++i) { if (ci->values[i].type == OCONFIG_TYPE_STRING) { - PyTuple_SET_ITEM(values, i, PyString_FromString(ci->values[i].value.string)); + PyTuple_SET_ITEM(values, i, cpy_string_to_unicode_or_bytes(ci->values[i].value.string)); } else if (ci->values[i].type == OCONFIG_TYPE_NUMBER) { PyTuple_SET_ITEM(values, i, PyFloat_FromDouble(ci->values[i].value.number)); } else if (ci->values[i].type == OCONFIG_TYPE_BOOLEAN) { @@ -868,7 +881,8 @@ static PyObject *cpy_oconfig_to_pyconfig(oconfig_item_t *ci, PyObject *parent) { } } - item = PyObject_CallFunction((void *) &ConfigType, "sONO", ci->key, parent, values, Py_None); + tmp = cpy_string_to_unicode_or_bytes(ci->key); + item = PyObject_CallFunction((void *) &ConfigType, "NONO", tmp, parent, values, Py_None); if (item == NULL) return NULL; children = PyTuple_New(ci->children_num); /* New reference. */ @@ -881,6 +895,20 @@ static PyObject *cpy_oconfig_to_pyconfig(oconfig_item_t *ci, PyObject *parent) { return item; } +#ifdef IS_PY3K +static struct PyModuleDef collectdmodule = { + PyModuleDef_HEAD_INIT, + "collectd", /* name of module */ + "The python interface to collectd", /* module documentation, may be NULL */ + -1, + cpy_methods +}; + +PyMODINIT_FUNC PyInit_collectd(void) { + return PyModule_Create(&collectdmodule); +} +#endif + static int cpy_config(oconfig_item_t *ci) { int i; PyObject *sys, *tb; @@ -893,6 +921,12 @@ static int cpy_config(oconfig_item_t *ci) { * python code during the config callback so we have to start * the interpreter here. */ /* Do *not* use the python "thread" module at this point! */ + +#ifdef IS_PY3K + /* Add a builtin module, before Py_Initialize */ + PyImport_AppendInittab("collectd", PyInit_collectd); +#endif + Py_Initialize(); PyType_Ready(&ConfigType); @@ -912,7 +946,11 @@ static int cpy_config(oconfig_item_t *ci) { cpy_log_exception("python initialization"); return 1; } +#ifdef IS_PY3K + module = PyImport_ImportModule("collectd"); +#else module = Py_InitModule("collectd", cpy_methods); /* Borrowed reference. */ +#endif PyModule_AddObject(module, "Config", (void *) &ConfigType); /* Steals a reference. */ PyModule_AddObject(module, "Values", (void *) &ValuesType); /* Steals a reference. */ PyModule_AddObject(module, "Notification", (void *) &NotificationType); /* Steals a reference. */ @@ -962,7 +1000,7 @@ static int cpy_config(oconfig_item_t *ci) { if (cf_util_get_string(item, &dir) != 0) continue; - dir_object = PyString_FromString(dir); /* New reference. */ + dir_object = cpy_string_to_unicode_or_bytes(dir); /* New reference. */ if (dir_object == NULL) { ERROR("python plugin: Unable to convert \"%s\" to " "a python object.", dir); @@ -987,7 +1025,6 @@ static int cpy_config(oconfig_item_t *ci) { if (module == NULL) { ERROR("python plugin: Error importing module \"%s\".", module_name); cpy_log_exception("importing module"); - PyErr_Print(); } free(module_name); Py_XDECREF(module); diff --git a/src/pyvalues.c b/src/pyvalues.c index d83f541b..a632dc16 100644 --- a/src/pyvalues.c +++ b/src/pyvalues.c @@ -32,6 +32,72 @@ #include "cpython.h" +static PyObject *cpy_common_repr(PyObject *s) { + PyObject *ret, *tmp; + static PyObject *l_type = NULL, *l_type_instance = NULL, *l_plugin = NULL, *l_plugin_instance = NULL; + static PyObject *l_host = NULL, *l_time = NULL; + PluginData *self = (PluginData *) s; + + if (l_type == NULL) + l_type = cpy_string_to_unicode_or_bytes("(type="); + if (l_type_instance == NULL) + l_type_instance = cpy_string_to_unicode_or_bytes(",type_instance="); + if (l_plugin == NULL) + l_plugin = cpy_string_to_unicode_or_bytes(",plugin="); + if (l_plugin_instance == NULL) + l_plugin_instance = cpy_string_to_unicode_or_bytes(",plugin_instance="); + if (l_host == NULL) + l_host = cpy_string_to_unicode_or_bytes(",host="); + if (l_time == NULL) + l_time = cpy_string_to_unicode_or_bytes(",time="); + + if (!l_type || !l_type_instance || !l_plugin || !l_plugin_instance || !l_host || !l_time) + return NULL; + + ret = cpy_string_to_unicode_or_bytes(s->ob_type->tp_name); + + CPY_STRCAT(&ret, l_type); + tmp = cpy_string_to_unicode_or_bytes(self->type); + CPY_SUBSTITUTE(PyObject_Repr, tmp, tmp); + CPY_STRCAT_AND_DEL(&ret, tmp); + + if (self->type_instance[0] != 0) { + CPY_STRCAT(&ret, l_type_instance); + tmp = cpy_string_to_unicode_or_bytes(self->type_instance); + CPY_SUBSTITUTE(PyObject_Repr, tmp, tmp); + CPY_STRCAT_AND_DEL(&ret, tmp); + } + + if (self->plugin[0] != 0) { + CPY_STRCAT(&ret, l_plugin); + tmp = cpy_string_to_unicode_or_bytes(self->plugin); + CPY_SUBSTITUTE(PyObject_Repr, tmp, tmp); + CPY_STRCAT_AND_DEL(&ret, tmp); + } + + if (self->plugin_instance[0] != 0) { + CPY_STRCAT(&ret, l_plugin_instance); + tmp = cpy_string_to_unicode_or_bytes(self->plugin_instance); + CPY_SUBSTITUTE(PyObject_Repr, tmp, tmp); + CPY_STRCAT_AND_DEL(&ret, tmp); + } + + if (self->host[0] != 0) { + CPY_STRCAT(&ret, l_host); + tmp = cpy_string_to_unicode_or_bytes(self->host); + CPY_SUBSTITUTE(PyObject_Repr, tmp, tmp); + CPY_STRCAT_AND_DEL(&ret, tmp); + } + + if (self->time != 0) { + CPY_STRCAT(&ret, l_time); + tmp = PyInt_FromLong(self->time); + CPY_SUBSTITUTE(PyObject_Repr, tmp, tmp); + CPY_STRCAT_AND_DEL(&ret, tmp); + } + return ret; +} + static char time_doc[] = "This is the Unix timestap of the time this value was read.\n" "For dispatching values this can be set to 0 which means \"now\".\n" "This means the time the value is actually dispatched, not the time\n" @@ -81,8 +147,8 @@ static int PluginData_init(PyObject *s, PyObject *args, PyObject *kwds) { static char *kwlist[] = {"type", "plugin_instance", "type_instance", "plugin", "host", "time", NULL}; - if (!PyArg_ParseTupleAndKeywords(args, kwds, "|sssssd", kwlist, &type, - &plugin_instance, &type_instance, &plugin, &host, &time)) + if (!PyArg_ParseTupleAndKeywords(args, kwds, "|etetetetetd", kwlist, NULL, &type, + NULL, &plugin_instance, NULL, &type_instance, NULL, &plugin, NULL, &host, &time)) return -1; if (type[0] != 0 && plugin_get_ds(type) == NULL) { @@ -101,14 +167,18 @@ static int PluginData_init(PyObject *s, PyObject *args, PyObject *kwds) { } static PyObject *PluginData_repr(PyObject *s) { - PluginData *self = (PluginData *) s; + PyObject *ret; + static PyObject *l_closing = NULL; + + if (l_closing == NULL) + l_closing = cpy_string_to_unicode_or_bytes(")"); - return PyString_FromFormat("collectd.Values(type='%s%s%s%s%s%s%s%s%s',time=%lu)", self->type, - *self->type_instance ? "',type_instance='" : "", self->type_instance, - *self->plugin ? "',plugin='" : "", self->plugin, - *self->plugin_instance ? "',plugin_instance='" : "", self->plugin_instance, - *self->host ? "',host='" : "", self->host, - (long unsigned) self->time); + if (l_closing == NULL) + return NULL; + + ret = cpy_common_repr(s); + CPY_STRCAT(&ret, l_closing); + return ret; } static PyMemberDef PluginData_members[] = { @@ -119,7 +189,7 @@ static PyMemberDef PluginData_members[] = { static PyObject *PluginData_getstring(PyObject *self, void *data) { const char *value = ((char *) self) + (intptr_t) data; - return PyString_FromString(value); + return cpy_string_to_unicode_or_bytes(value); } static int PluginData_setstring(PyObject *self, PyObject *value, void *data) { @@ -130,10 +200,15 @@ static int PluginData_setstring(PyObject *self, PyObject *value, void *data) { PyErr_SetString(PyExc_TypeError, "Cannot delete this attribute"); return -1; } - new = PyString_AsString(value); - if (new == NULL) return -1; + Py_INCREF(value); + new = cpy_unicode_or_bytes_to_string(&value); + if (new == NULL) { + Py_DECREF(value); + return -1; + } old = ((char *) self) + (intptr_t) data; sstrncpy(old, new, DATA_MAX_NAME_LEN); + Py_DECREF(value); return 0; } @@ -145,16 +220,22 @@ static int PluginData_settype(PyObject *self, PyObject *value, void *data) { PyErr_SetString(PyExc_TypeError, "Cannot delete this attribute"); return -1; } - new = PyString_AsString(value); - if (new == NULL) return -1; + Py_INCREF(value); + new = cpy_unicode_or_bytes_to_string(&value); + if (new == NULL) { + Py_DECREF(value); + return -1; + } if (plugin_get_ds(new) == NULL) { PyErr_Format(PyExc_TypeError, "Dataset %s not found", new); + Py_DECREF(value); return -1; } old = ((char *) self) + (intptr_t) data; sstrncpy(old, new, DATA_MAX_NAME_LEN); + Py_DECREF(value); return 0; } @@ -168,8 +249,7 @@ static PyGetSetDef PluginData_getseters[] = { }; PyTypeObject PluginDataType = { - PyObject_HEAD_INIT(NULL) - 0, /* Always 0 */ + CPY_INIT_TYPE "collectd.PluginData", /* tp_name */ sizeof(PluginData), /* tp_basicsize */ 0, /* Will be filled in later */ @@ -262,26 +342,30 @@ static PyObject *Values_new(PyTypeObject *type, PyObject *args, PyObject *kwds) static int Values_init(PyObject *s, PyObject *args, PyObject *kwds) { Values *self = (Values *) s; - int interval = 0, ret; + int interval = 0; double time = 0; PyObject *values = NULL, *tmp; const char *type = "", *plugin_instance = "", *type_instance = "", *plugin = "", *host = ""; static char *kwlist[] = {"type", "values", "plugin_instance", "type_instance", "plugin", "host", "time", "interval", NULL}; - if (!PyArg_ParseTupleAndKeywords(args, kwds, "|sOssssdi", kwlist, - &type, &values, &plugin_instance, &type_instance, - &plugin, &host, &time, &interval)) + if (!PyArg_ParseTupleAndKeywords(args, kwds, "|etOetetetetdi", kwlist, + NULL, &type, &values, NULL, &plugin_instance, NULL, &type_instance, + NULL, &plugin, NULL, &host, &time, &interval)) return -1; - tmp = Py_BuildValue("sssssd", type, plugin_instance, type_instance, plugin, host, time); - if (tmp == NULL) - return -1; - ret = PluginDataType.tp_init(s, tmp, NULL); - Py_DECREF(tmp); - if (ret != 0) + if (type[0] != 0 && plugin_get_ds(type) == NULL) { + PyErr_Format(PyExc_TypeError, "Dataset %s not found", type); return -1; - + } + + sstrncpy(self->data.host, host, sizeof(self->data.host)); + sstrncpy(self->data.plugin, plugin, sizeof(self->data.plugin)); + sstrncpy(self->data.plugin_instance, plugin_instance, sizeof(self->data.plugin_instance)); + sstrncpy(self->data.type, type, sizeof(self->data.type)); + sstrncpy(self->data.type_instance, type_instance, sizeof(self->data.type_instance)); + self->data.time = time; + if (values == NULL) { values = PyList_New(0); PyErr_Clear(); @@ -314,9 +398,9 @@ static PyObject *Values_dispatch(Values *self, PyObject *args, PyObject *kwds) { static char *kwlist[] = {"type", "values", "plugin_instance", "type_instance", "plugin", "host", "time", "interval", NULL}; - if (!PyArg_ParseTupleAndKeywords(args, kwds, "|sOssssdi", kwlist, - &type, &values, &plugin_instance, &type_instance, - &plugin, &host, &time, &interval)) + if (!PyArg_ParseTupleAndKeywords(args, kwds, "|etOetetetetdi", kwlist, + NULL, &type, &values, NULL, &plugin_instance, NULL, &type_instance, + NULL, &plugin, NULL, &host, &time, &interval)) return NULL; if (type[0] == 0) { @@ -414,9 +498,9 @@ static PyObject *Values_write(Values *self, PyObject *args, PyObject *kwds) { static char *kwlist[] = {"destination", "type", "values", "plugin_instance", "type_instance", "plugin", "host", "time", "interval", NULL}; - if (!PyArg_ParseTupleAndKeywords(args, kwds, "|sOssssdi", kwlist, - &type, &values, &plugin_instance, &type_instance, - &plugin, &host, &time, &interval)) + if (!PyArg_ParseTupleAndKeywords(args, kwds, "|etOetetetetdi", kwlist, + NULL, &type, &values, NULL, &plugin_instance, NULL, &type_instance, + NULL, &plugin, NULL, &host, &time, &interval)) return NULL; if (type[0] == 0) { @@ -497,22 +581,33 @@ static PyObject *Values_write(Values *self, PyObject *args, PyObject *kwds) { } static PyObject *Values_repr(PyObject *s) { - PyObject *ret, *valuestring = NULL; + PyObject *ret, *tmp; + static PyObject *l_interval = NULL, *l_values = NULL, *l_closing = NULL; Values *self = (Values *) s; - if (self->values != NULL) - valuestring = PyObject_Repr(self->values); - if (valuestring == NULL) + if (l_interval == NULL) + l_interval = cpy_string_to_unicode_or_bytes(",interval="); + if (l_values == NULL) + l_values = cpy_string_to_unicode_or_bytes(",values="); + if (l_closing == NULL) + l_closing = cpy_string_to_unicode_or_bytes(")"); + + if (l_interval == NULL || l_values == NULL || l_closing == NULL) return NULL; - ret = PyString_FromFormat("collectd.Values(type='%s%s%s%s%s%s%s%s%s',time=%lu,interval=%i,values=%s)", self->data.type, - *self->data.type_instance ? "',type_instance='" : "", self->data.type_instance, - *self->data.plugin ? "',plugin='" : "", self->data.plugin, - *self->data.plugin_instance ? "',plugin_instance='" : "", self->data.plugin_instance, - *self->data.host ? "',host='" : "", self->data.host, - (long unsigned) self->data.time, self->interval, - valuestring ? PyString_AsString(valuestring) : "[]"); - Py_XDECREF(valuestring); + ret = cpy_common_repr(s); + if (self->interval != 0) { + CPY_STRCAT(&ret, l_interval); + tmp = PyInt_FromLong(self->interval); + CPY_SUBSTITUTE(PyObject_Repr, tmp, tmp); + CPY_STRCAT_AND_DEL(&ret, tmp); + } + if (self->values != NULL && PySequence_Length(self->values) > 0) { + CPY_STRCAT(&ret, l_values); + tmp = PyObject_Repr(self->values); + CPY_STRCAT_AND_DEL(&ret, tmp); + } + CPY_STRCAT(&ret, l_closing); return ret; } @@ -546,8 +641,7 @@ static PyMethodDef Values_methods[] = { }; PyTypeObject ValuesType = { - PyObject_HEAD_INIT(NULL) - 0, /* Always 0 */ + CPY_INIT_TYPE "collectd.Values", /* tp_name */ sizeof(Values), /* tp_basicsize */ 0, /* Will be filled in later */ @@ -600,27 +694,30 @@ static char Notification_doc[] = "The Notification class is a wrapper around the static int Notification_init(PyObject *s, PyObject *args, PyObject *kwds) { Notification *self = (Notification *) s; - PyObject *tmp; - int severity = 0, ret; + int severity = 0; double time = 0; const char *message = ""; const char *type = "", *plugin_instance = "", *type_instance = "", *plugin = "", *host = ""; static char *kwlist[] = {"type", "message", "plugin_instance", "type_instance", "plugin", "host", "time", "severity", NULL}; - if (!PyArg_ParseTupleAndKeywords(args, kwds, "|ssssssdi", kwlist, - &type, &message, &plugin_instance, &type_instance, - &plugin, &host, &time, &severity)) + if (!PyArg_ParseTupleAndKeywords(args, kwds, "|etetetetetetdi", kwlist, + NULL, &type, NULL, &message, NULL, &plugin_instance, NULL, &type_instance, + NULL, &plugin, NULL, &host, &time, &severity)) return -1; - tmp = Py_BuildValue("sssssd", type, plugin_instance, type_instance, plugin, host, time); - if (tmp == NULL) - return -1; - ret = PluginDataType.tp_init(s, tmp, NULL); - Py_DECREF(tmp); - if (ret != 0) + if (type[0] != 0 && plugin_get_ds(type) == NULL) { + PyErr_Format(PyExc_TypeError, "Dataset %s not found", type); return -1; - + } + + sstrncpy(self->data.host, host, sizeof(self->data.host)); + sstrncpy(self->data.plugin, plugin, sizeof(self->data.plugin)); + sstrncpy(self->data.plugin_instance, plugin_instance, sizeof(self->data.plugin_instance)); + sstrncpy(self->data.type, type, sizeof(self->data.type)); + sstrncpy(self->data.type_instance, type_instance, sizeof(self->data.type_instance)); + self->data.time = time; + sstrncpy(self->message, message, sizeof(self->message)); self->severity = severity; return 0; @@ -641,9 +738,9 @@ static PyObject *Notification_dispatch(Notification *self, PyObject *args, PyObj static char *kwlist[] = {"type", "message", "plugin_instance", "type_instance", "plugin", "host", "time", "severity", NULL}; - if (!PyArg_ParseTupleAndKeywords(args, kwds, "|ssssssdi", kwlist, - &type, &message, &plugin_instance, &type_instance, - &plugin, &host, &t, &severity)) + if (!PyArg_ParseTupleAndKeywords(args, kwds, "|etetetetetetdi", kwlist, + NULL, &type, NULL, &message, NULL, &plugin_instance, NULL, &type_instance, + NULL, &plugin, NULL, &host, &t, &severity)) return NULL; if (type[0] == 0) { @@ -701,24 +798,47 @@ static int Notification_setstring(PyObject *self, PyObject *value, void *data) { PyErr_SetString(PyExc_TypeError, "Cannot delete this attribute"); return -1; } - new = PyString_AsString(value); - if (new == NULL) return -1; + Py_INCREF(value); + new = cpy_unicode_or_bytes_to_string(&value); + if (new == NULL) { + Py_DECREF(value); + return -1; + } old = ((char *) self) + (intptr_t) data; sstrncpy(old, new, NOTIF_MAX_MSG_LEN); + Py_DECREF(value); return 0; } static PyObject *Notification_repr(PyObject *s) { - PyObject *ret; + PyObject *ret, *tmp; + static PyObject *l_severity = NULL, *l_message = NULL, *l_closing = NULL; Notification *self = (Notification *) s; - ret = PyString_FromFormat("collectd.Values(type='%s%s%s%s%s%s%s%s%s%s%s',time=%lu,interval=%i)", self->data.type, - *self->data.type_instance ? "',type_instance='" : "", self->data.type_instance, - *self->data.plugin ? "',plugin='" : "", self->data.plugin, - *self->data.plugin_instance ? "',plugin_instance='" : "", self->data.plugin_instance, - *self->data.host ? "',host='" : "", self->data.host, - *self->message ? "',message='" : "", self->message, - (long unsigned) self->data.time, self->severity); + if (l_severity == NULL) + l_severity = cpy_string_to_unicode_or_bytes(",severity="); + if (l_message == NULL) + l_message = cpy_string_to_unicode_or_bytes(",message="); + if (l_closing == NULL) + l_closing = cpy_string_to_unicode_or_bytes(")"); + + if (l_severity == NULL || l_message == NULL || l_closing == NULL) + return NULL; + + ret = cpy_common_repr(s); + if (self->severity != 0) { + CPY_STRCAT(&ret, l_severity); + tmp = PyInt_FromLong(self->severity); + CPY_SUBSTITUTE(PyObject_Repr, tmp, tmp); + CPY_STRCAT_AND_DEL(&ret, tmp); + } + if (self->message[0] != 0) { + CPY_STRCAT(&ret, l_message); + tmp = cpy_string_to_unicode_or_bytes(self->message); + CPY_SUBSTITUTE(PyObject_Repr, tmp, tmp); + CPY_STRCAT_AND_DEL(&ret, tmp); + } + CPY_STRCAT(&ret, l_closing); return ret; } @@ -738,8 +858,7 @@ static PyGetSetDef Notification_getseters[] = { }; PyTypeObject NotificationType = { - PyObject_HEAD_INIT(NULL) - 0, /* Always 0 */ + CPY_INIT_TYPE "collectd.Notification", /* tp_name */ sizeof(Notification), /* tp_basicsize */ 0, /* Will be filled in later */ diff --git a/src/routeros.c b/src/routeros.c index 4fe33fa9..2512613c 100644 --- a/src/routeros.c +++ b/src/routeros.c @@ -36,6 +36,10 @@ struct cr_data_s _Bool collect_interface; _Bool collect_regtable; + _Bool collect_cpu_load; + _Bool collect_memory; + _Bool collect_df; + _Bool collect_disk; }; typedef struct cr_data_s cr_data_t; @@ -110,6 +114,26 @@ static void cr_submit_gauge (cr_data_t *rd, const char *type, /* {{{ */ plugin_dispatch_values (&vl); } /* }}} void cr_submit_gauge */ +#if ROS_VERSION >= ROS_VERSION_ENCODE(1, 1, 0) +static void cr_submit_counter (cr_data_t *rd, const char *type, /* {{{ */ + const char *type_instance, counter_t value) +{ + value_t values[1]; + value_list_t vl = VALUE_LIST_INIT; + + values[0].counter = value; + + vl.values = values; + vl.values_len = STATIC_ARRAY_SIZE (values); + sstrncpy (vl.host, rd->node, sizeof (vl.host)); /* FIXME */ + sstrncpy (vl.plugin, "routeros", sizeof (vl.plugin)); + sstrncpy (vl.type, type, sizeof (vl.type)); + sstrncpy (vl.type_instance, type_instance, sizeof (vl.type_instance)); + + plugin_dispatch_values (&vl); +} /* }}} void cr_submit_gauge */ +#endif + static void submit_regtable (cr_data_t *rd, /* {{{ */ const ros_registration_table_t *r) { @@ -158,6 +182,44 @@ static int handle_regtable (__attribute__((unused)) ros_connection_t *c, /* {{{ return (0); } /* }}} int handle_regtable */ +#if ROS_VERSION >= ROS_VERSION_ENCODE(1, 1, 0) /* FIXME */ +static int handle_system_resource (__attribute__((unused)) ros_connection_t *c, /* {{{ */ + const ros_system_resource_t *r, + __attribute__((unused)) void *user_data) +{ + cr_data_t *rd; + + if ((r == NULL) || (user_data == NULL)) + return (EINVAL); + rd = user_data; + + if (rd->collect_cpu_load) + cr_submit_gauge (rd, "gauge", "cpu_load", (gauge_t) r->cpu_load); + + if (rd->collect_memory) + { + cr_submit_gauge (rd, "memory", "used", + (gauge_t) (r->total_memory - r->free_memory)); + cr_submit_gauge (rd, "memory", "free", (gauge_t) r->free_memory); + } + + if (rd->collect_df) + { + cr_submit_gauge (rd, "df_complex", "used", + (gauge_t) (r->total_memory - r->free_memory)); + cr_submit_gauge (rd, "df_complex", "free", (gauge_t) r->free_memory); + } + + if (rd->collect_disk) + { + cr_submit_counter (rd, "counter", "secors_written", (counter_t) r->write_sect_total); + cr_submit_gauge (rd, "gauge", "bad_blocks", (gauge_t) r->bad_blocks); + } + + return (0); +} /* }}} int handle_system_resource */ +#endif + static int cr_read (user_data_t *user_data) /* {{{ */ { int status; @@ -214,6 +276,26 @@ static int cr_read (user_data_t *user_data) /* {{{ */ } } +#if ROS_VERSION >= ROS_VERSION_ENCODE(1, 1, 0) /* FIXME */ + if (rd->collect_cpu_load + || rd->collect_memory + || rd->collect_df + || rd->collect_disk) + { + status = ros_system_resource (rd->connection, handle_system_resource, + /* user data = */ rd); + if (status != 0) + { + char errbuf[128]; + ERROR ("routeros plugin: ros_system_resource failed: %s", + sstrerror (status, errbuf, sizeof (errbuf))); + ros_disconnect (rd->connection); + rd->connection = NULL; + return (-1); + } + } +#endif + return (0); } /* }}} int cr_read */ @@ -250,8 +332,6 @@ static int cr_config_router (oconfig_item_t *ci) /* {{{ */ router_data->service = NULL; router_data->username = NULL; router_data->password = NULL; - router_data->collect_interface = false; - router_data->collect_regtable = false; status = 0; for (i = 0; i < ci->children_num; i++) @@ -270,6 +350,16 @@ static int cr_config_router (oconfig_item_t *ci) /* {{{ */ cf_util_get_boolean (child, &router_data->collect_interface); else if (strcasecmp ("CollectRegistrationTable", child->key) == 0) cf_util_get_boolean (child, &router_data->collect_regtable); +#if ROS_VERSION >= ROS_VERSION_ENCODE(1, 1, 0) /* FIXME */ + else if (strcasecmp ("CollectCPULoad", child->key) == 0) + cf_util_get_boolean (child, &router_data->collect_cpu_load); + else if (strcasecmp ("CollectMemory", child->key) == 0) + cf_util_get_boolean (child, &router_data->collect_memory); + else if (strcasecmp ("CollectDF", child->key) == 0) + cf_util_get_boolean (child, &router_data->collect_df); + else if (strcasecmp ("CollectDisk", child->key) == 0) + cf_util_get_boolean (child, &router_data->collect_disk); +#endif else { WARNING ("routeros plugin: Unknown config option `%s'.", child->key); diff --git a/src/swap.c b/src/swap.c index 7f41c9ea..e467818d 100644 --- a/src/swap.c +++ b/src/swap.c @@ -96,7 +96,7 @@ int kvm_pagesize; #elif HAVE_PERFSTAT static int pagesize; static perfstat_memory_total_t pmemory; -/*# endif HAVE_PERFSTAT */ +/*# endif HAVE_PERFSTAT */ #else # error "No applicable input method." @@ -193,6 +193,8 @@ static int swap_read (void) char *fields[8]; int numfields; + _Bool old_kernel=0; + derive_t swap_used = 0; derive_t swap_cached = 0; derive_t swap_free = 0; @@ -243,30 +245,44 @@ static int swap_read (void) if ((fh = fopen ("/proc/vmstat", "r")) == NULL) { - char errbuf[1024]; - WARNING ("swap: fopen: %s", - sstrerror (errno, errbuf, sizeof (errbuf))); - return (-1); + // /proc/vmstat does not exist in kernels <2.6 + if ((fh = fopen ("/proc/stat", "r")) == NULL ) + { + char errbuf[1024]; + WARNING ("swap: fopen: %s", + sstrerror (errno, errbuf, sizeof (errbuf))); + return (-1); + } + else + old_kernel = 1; } while (fgets (buffer, 1024, fh) != NULL) { - derive_t *val = NULL; - - if (strncasecmp (buffer, "pswpin", 6) == 0) - val = &swap_in; - else if (strncasecmp (buffer, "pswpout", 7) == 0) - val = &swap_out; - else - continue; - - numfields = strsplit (buffer, fields, 8); - - if (numfields < 2) - continue; - - *val = (derive_t) atoll (fields[1]); - } + numfields = strsplit (buffer, fields, STATIC_ARRAY_SIZE (fields)); + + if (!old_kernel) + { + if (numfields != 2) + continue; + + if (strcasecmp ("pswpin", fields[0]) != 0) + strtoderive (fields[1], &swap_in); + else if (strcasecmp ("pswpout", fields[0]) == 0) + strtoderive (fields[1], &swap_out); + } + else /* if (old_kernel) */ + { + if (numfields != 3) + continue; + + if (strcasecmp ("page", fields[0]) == 0) + { + strtoderive (fields[1], &swap_in); + strtoderive (fields[2], &swap_out); + } + } + } /* while (fgets) */ if (fclose (fh)) { @@ -280,7 +296,6 @@ static int swap_read (void) swap_submit ("cached", swap_cached, DS_TYPE_GAUGE); swap_submit ("in", swap_in, DS_TYPE_DERIVE); swap_submit ("out", swap_out, DS_TYPE_DERIVE); - /* #endif KERNEL_LINUX */ #elif HAVE_LIBKSTAT @@ -310,7 +325,7 @@ static int swap_read (void) * However, Solaris does not allow to allocated/reserved more than the * available swap (physical memory + disk swap), so the pedant may * prefer: allocated + unallocated = reserved, available - * + * * We map the above to: used + resv = n/a, free * * Does your brain hurt yet? - Christophe Kalt diff --git a/src/thermal.c b/src/thermal.c index 2b708052..b9d07bf5 100644 --- a/src/thermal.c +++ b/src/thermal.c @@ -218,13 +218,13 @@ static int thermal_config (const char *key, const char *value) static int thermal_sysfs_read (void) { return walk_directory (dirname_sysfs, thermal_sysfs_device_read, - /* user_data = */ NULL); + /* user_data = */ NULL, /* include hidden */ 0); } static int thermal_procfs_read (void) { return walk_directory (dirname_procfs, thermal_procfs_device_read, - /* user_data = */ NULL); + /* user_data = */ NULL, /* include hidden */ 0); } static int thermal_init (void) diff --git a/src/types.db b/src/types.db index a5872ebf..dffb10a7 100644 --- a/src/types.db +++ b/src/types.db @@ -1,6 +1,7 @@ absolute count:ABSOLUTE:0:U apache_bytes count:COUNTER:0:134217728 apache_connections count:GAUGE:0:65535 +apache_idle_workers count:GAUGE:0:65535 apache_requests count:COUNTER:0:134217728 apache_scoreboard count:GAUGE:0:65535 arc_counts demand_data:COUNTER:0:U, demand_metadata:COUNTER:0:U, prefetch_data:COUNTER:0:U, prefetch_metadata:COUNTER:0:U diff --git a/src/write_http.c b/src/write_http.c index 1a490399..8c6a1b56 100644 --- a/src/write_http.c +++ b/src/write_http.c @@ -49,6 +49,7 @@ struct wh_callback_s int verify_peer; int verify_host; char *cacert; + int store_rates; #define WH_FORMAT_COMMAND 0 #define WH_FORMAT_JSON 1 @@ -271,11 +272,13 @@ static void wh_callback_free (void *data) /* {{{ */ static int wh_value_list_to_string (char *buffer, /* {{{ */ size_t buffer_size, - const data_set_t *ds, const value_list_t *vl) + const data_set_t *ds, const value_list_t *vl, + wh_callback_t *cb) { size_t offset = 0; int status; int i; + gauge_t *rates = NULL; assert (0 == strcmp (ds->type, vl->type)); @@ -285,9 +288,15 @@ static int wh_value_list_to_string (char *buffer, /* {{{ */ status = ssnprintf (buffer + offset, buffer_size - offset, \ __VA_ARGS__); \ if (status < 1) \ + { \ + sfree (rates); \ return (-1); \ + } \ else if (((size_t) status) >= (buffer_size - offset)) \ + { \ + sfree (rates); \ return (-1); \ + } \ else \ offset += ((size_t) status); \ } while (0) @@ -299,7 +308,22 @@ static int wh_value_list_to_string (char *buffer, /* {{{ */ if (ds->ds[i].type == DS_TYPE_GAUGE) BUFFER_ADD (":%f", vl->values[i].gauge); else if (ds->ds[i].type == DS_TYPE_COUNTER) - BUFFER_ADD (":%llu", vl->values[i].counter); + { + if (cb->store_rates != 0) + { + if (rates == NULL) + rates = uc_get_rate (ds, vl); + if (rates == NULL) + { + WARNING ("write_http plugin: " + "uc_get_rate failed."); + return (-1); + } + BUFFER_ADD (":%lf", rates[i]); + } + else + BUFFER_ADD (":%llu", vl->values[i].counter); + } else if (ds->ds[i].type == DS_TYPE_DERIVE) BUFFER_ADD (":%"PRIi64, vl->values[i].derive); else if (ds->ds[i].type == DS_TYPE_ABSOLUTE) @@ -314,6 +338,7 @@ static int wh_value_list_to_string (char *buffer, /* {{{ */ #undef BUFFER_ADD +sfree (rates); return (0); } /* }}} int wh_value_list_to_string */ @@ -343,7 +368,7 @@ static int wh_write_command (const data_set_t *ds, const value_list_t *vl, /* {{ /* Convert the values to an ASCII representation and put that into * `values'. */ - status = wh_value_list_to_string (values, sizeof (values), ds, vl); + status = wh_value_list_to_string (values, sizeof (values), ds, vl, cb); if (status != 0) { ERROR ("write_http plugin: error with " "wh_value_list_to_string"); @@ -589,6 +614,8 @@ static int wh_config_url (oconfig_item_t *ci) /* {{{ */ config_set_string (&cb->cacert, child); else if (strcasecmp ("Format", child->key) == 0) config_set_format (cb, child); + else if (strcasecmp ("StoreRates", child->key) == 0) + config_set_boolean (&cb->store_rates, child); else { ERROR ("write_http plugin: Invalid configuration "