From: Pavel Rochnyak Date: Fri, 20 Oct 2017 13:15:57 +0000 (+0700) Subject: Merge pull request #2346 from trenkel/master X-Git-Tag: collectd-5.8.0~33 X-Git-Url: https://git.octo.it/?a=commitdiff_plain;h=71fb7a5299f307a9261ae69819318f1eda1dd3c7;hp=b6bbd0e4785b771b9e70777828dcb3a91fbdad50;p=collectd.git Merge pull request #2346 from trenkel/master Add CollectdError exception which can be thrown without causing a stacktrace to be logged. --- diff --git a/AUTHORS b/AUTHORS index d866c700..4df743c5 100644 --- a/AUTHORS +++ b/AUTHORS @@ -116,6 +116,9 @@ Dan Ryder David Bacher - serial plugin. +Denis Pompilio + - Improvements to the write_http plugin. + Doug MacEachern - The `-T' option (config testing mode). - OpenVPN plugin. @@ -285,6 +288,11 @@ Scott Sanders Sebastien Pahl - AMQP plugin. +Serhiy Pshyk + - intel_pmu plugin + - intel_rdt plugin + - snmp_agent plugin + Simon Kuhnle - OpenBSD code for the cpu and memory plugins. diff --git a/ChangeLog b/ChangeLog index b8755d99..f990dd46 100644 --- a/ChangeLog +++ b/ChangeLog @@ -134,6 +134,77 @@ embedded HTTP server, in a format compatible with Prometheus' collectd_exporter. Thanks to Florian Forster. #1967 +2017-10-06, Version 5.6.3 + * collectd: support for boolean string config values has been + reintroduced. Thanks to Sebastian Harl. #2083, #2098 + * collectd: The capability checking has been changed to use + "cap_get_proc()". Thanks to Marc Fournier. #2151 + * Documentation: A section documenting ignore lists has been added to + collectd.conf(5). Thanks to Florian Forster. + * AMQP plugin: The "ExchangeType" option is now also valid for + publishers. Thanks to Florian Forster. #2286 + * Apache, Ascent, BIND, cURL, cURL-JSON, cURL-XML, nginx, Write HTTP + plugins: Handling of URLs that redirect elsewhere has been fixed. + Thanks to Pavel Rochnyack. #2328 + * BIND plugin: Fix parsing of the sample time provided by BIND. + Previously, the time was assumed to be in the local time zone when in + fact it was in UTC. Thanks to Ed Ravin. #1268 + * BIND plugin: Memory leaks have been fixed. Thanks to Ruben Kerkhof. + #2303 + * Chrony plugin: Build flags have been fixed. Thanks to Thomas Jost and + Marc Fournier. #2133 + * cURL-JSON plugin: The timeout value has been changed to default to the + collection interval. This fixes a regression. Thanks to Marc Fournier. + * cURL-JSON plugin: Handling of arrays has been fixed. Thanks to Florian + Forster. #2266 + * DBI plugin: Memory leaks at shutdown have been fixes. Thanks to Pavel + Rochnyack and Florian Forster. + * E-Mail, Exec, UnixSock plugins: Group ID lookup on systems with many + groups has been fixed. Thanks to Ruben Kerkhof and Florian Forster. + #2208 + * IPC plugin: A compilation error on AIX has been fixed. Thanks to Pavel + Rochnyack. #2305 + * LogFile plugin: If writing to the file fails, print log messages on + "STDERR" instead. Thanks to Marc Fournier. + * Log Logstash plugin: If writing the log entry fails, print it to + "STDERR" instead. Thanks to Marc Fournier. + * memcachec, Tail plugins: A resource leak in the matching + infrastructure has been fixed. Thanks to Krzysztof Matczak. #2192 + * MQTT plugin: Invalid symbols in topic names are now replaced and a + resource leak has been fixed. Thanks to Denys Fedoryshchenko. #2123 + * Network plugin: A potential endless-loop has been fixed. This can be + triggered remotely by sending a signed network packet to a server + which is not set up to check signatures. Thanks to Marcin Kozlowski + and Pavel Rochnyack. #2174, #2233, CVE-2017-7401 + * Network plugin: A use-after-free has been fixed. Thanks to Pavel + Rochnyack. #2375 + * Notify Email plugin: The plugin is no longer explicitly linked against + libssl and libcrypto, relies on libesmtp being linked correctly. + Thanks to Marc Fournier. Debian#852924 + * NTPd plugin: Calculation of loop offset and error has been fixed. + Thanks to Neil Wilson. #2188 + * OpenLDAP plugin: An incorrect use of the ldap library, leading to a + crash, has been fixed. Thanks to Marc Fournier. #2331 + * Perl plugin: A potential double-free has been fixed. Thanks to Florian + Forster. #2278 + * Perl plugin: Print an error when an incorrect configuration is + encountered. Thanks to Pavel Rochnyack. #927 + * RRDtool plugin: Incorrect handling of the flushes timeout option has + been fixed. Handling of the "RandomTimeout" has been fixed. Thanks to + Pavel Rochnyack. #2363 + * SMART plugin: Some warning messages have been removed and the code has + been cleaned up. Thanks to Florian Forster. #2062 + * SMART plugin: A check for the "CAP_SYS_RAWIO" capability has been + added. Thanks to Marc Fournier. + * SNMP plugin: A double free has been fixed. Thanks to Pavel Rochnyack. + #2291 + * Write Graphite plugin: Error handling in the case that calculating a + metric's rate fails has been improved. Previously, the raw counter + values were sent to Graphite. Thanks to Iain Buclaw. #2209 + * Write Kafka plugin: A 32 bit random number is now used when formatting + a random key. Thanks to Florian Forster. #2074 + + 2016-11-30, Version 5.6.2 * collectd: A compile error on AIX has been fixed: "MSG_DONTWAIT" is not available on AIX. Thanks to Chao Yang. diff --git a/Makefile.am b/Makefile.am index f9273990..ae027a36 100644 --- a/Makefile.am +++ b/Makefile.am @@ -89,9 +89,12 @@ nodist_pkgconfig_DATA = \ pkginclude_HEADERS = \ src/libcollectdclient/collectd/client.h \ - src/libcollectdclient/collectd/network.h \ + src/libcollectdclient/collectd/lcc_features.h \ src/libcollectdclient/collectd/network_buffer.h \ - src/libcollectdclient/collectd/lcc_features.h + src/libcollectdclient/collectd/network.h \ + src/libcollectdclient/collectd/network_parse.h \ + src/libcollectdclient/collectd/server.h \ + src/libcollectdclient/collectd/types.h lib_LTLIBRARIES = libcollectdclient.la @@ -137,7 +140,8 @@ check_PROGRAMS = \ test_utils_mount \ test_utils_subst \ test_utils_time \ - test_utils_vl_lookup + test_utils_vl_lookup \ + test_libcollectd_network_parse TESTS = $(check_PROGRAMS) @@ -195,6 +199,8 @@ collectd_SOURCES = \ src/daemon/configfile.h \ src/daemon/filter_chain.c \ src/daemon/filter_chain.h \ + src/daemon/globals.c \ + src/daemon/globals.h \ src/daemon/meta_data.c \ src/daemon/meta_data.h \ src/daemon/plugin.c \ @@ -246,8 +252,8 @@ collectdmon_SOURCES = src/collectdmon.c collectd_nagios_SOURCES = src/collectd-nagios.c collectd_nagios_CPPFLAGS = $(AM_CPPFLAGS) \ - -I$(srcdir)/src/libcollectdclient/collectd \ - -I$(top_builddir)/src/libcollectdclient/collectd + -I$(srcdir)/src/libcollectdclient \ + -I$(top_builddir)/src/libcollectdclient collectd_nagios_LDADD = libcollectdclient.la if BUILD_WITH_LIBSOCKET collectd_nagios_LDADD += -lsocket @@ -259,8 +265,8 @@ endif collectdctl_SOURCES = src/collectdctl.c collectdctl_CPPFLAGS = $(AM_CPPFLAGS) \ - -I$(srcdir)/src/libcollectdclient/collectd \ - -I$(top_builddir)/src/libcollectdclient/collectd + -I$(srcdir)/src/libcollectdclient \ + -I$(top_builddir)/src/libcollectdclient collectdctl_LDADD = libcollectdclient.la if BUILD_WITH_LIBSOCKET collectdctl_LDADD += -lsocket @@ -272,8 +278,8 @@ endif collectd_tg_SOURCES = src/collectd-tg.c collectd_tg_CPPFLAGS = $(AM_CPPFLAGS) \ - -I$(srcdir)/src/libcollectdclient/collectd \ - -I$(top_builddir)/src/libcollectdclient/collectd + -I$(srcdir)/src/libcollectdclient \ + -I$(top_builddir)/src/libcollectdclient collectd_tg_LDADD = \ $(PTHREAD_LIBS) \ libheap.la \ @@ -475,20 +481,34 @@ endif libcollectdclient_la_SOURCES = \ src/libcollectdclient/client.c \ src/libcollectdclient/network.c \ - src/libcollectdclient/network_buffer.c + src/libcollectdclient/network_buffer.c \ + src/libcollectdclient/network_parse.c \ + src/libcollectdclient/server.c libcollectdclient_la_CPPFLAGS = \ $(AM_CPPFLAGS) \ - -I$(srcdir)/src/libcollectdclient/collectd \ - -I$(top_builddir)/src/libcollectdclient/collectd \ + -I$(srcdir)/src/libcollectdclient \ + -I$(top_builddir)/src/libcollectdclient \ -I$(srcdir)/src/daemon -libcollectdclient_la_LDFLAGS = -version-info 1:0:0 -libcollectdclient_la_LIBADD = +libcollectdclient_la_LDFLAGS = -version-info 2:0:1 +libcollectdclient_la_LIBADD = -lm if BUILD_WITH_LIBGCRYPT libcollectdclient_la_CPPFLAGS += $(GCRYPT_CPPFLAGS) libcollectdclient_la_LDFLAGS += $(GCRYPT_LDFLAGS) libcollectdclient_la_LIBADD += $(GCRYPT_LIBS) endif +# network_parse_test.c includes network_parse.c, so no need to link with +# libcollectdclient.so. +test_libcollectd_network_parse_SOURCES = src/libcollectdclient/network_parse_test.c +test_libcollectd_network_parse_CPPFLAGS = \ + $(AM_CPPFLAGS) \ + -I$(srcdir)/src/libcollectdclient \ + -I$(top_builddir)/src/libcollectdclient +if BUILD_WITH_LIBGCRYPT +test_libcollectd_network_parse_CPPFLAGS += $(GCRYPT_CPPFLAGS) +test_libcollectd_network_parse_LDFLAGS = $(GCRYPT_LDFLAGS) +test_libcollectd_network_parse_LDADD = $(GCRYPT_LIBS) +endif liboconfig_la_SOURCES = \ src/liboconfig/oconfig.c \ @@ -783,16 +803,18 @@ if BUILD_PLUGIN_DPDKEVENTS pkglib_LTLIBRARIES += dpdkevents.la dpdkevents_la_SOURCES = src/dpdkevents.c src/utils_dpdk.c src/utils_dpdk.h dpdkevents_la_CPPFLAGS = $(AM_CPPFLAGS) $(LIBDPDK_CPPFLAGS) +dpdkevents_la_CFLAGS = $(AM_CFLAGS) $(LIBDPDK_CFLAGS) dpdkevents_la_LDFLAGS = $(PLUGIN_LDFLAGS) $(LIBDPDK_LDFLAGS) -dpdkevents_la_LIBADD = -ldpdk +dpdkevents_la_LIBADD = $(LIBDPDK_LIBS) endif if BUILD_PLUGIN_DPDKSTAT pkglib_LTLIBRARIES += dpdkstat.la dpdkstat_la_SOURCES = src/dpdkstat.c src/utils_dpdk.c src/utils_dpdk.h dpdkstat_la_CPPFLAGS = $(AM_CPPFLAGS) $(LIBDPDK_CPPFLAGS) +dpdkstat_la_CFLAGS = $(AM_CFLAGS) $(LIBDPDK_CFLAGS) dpdkstat_la_LDFLAGS = $(PLUGIN_LDFLAGS) $(LIBDPDK_LDFLAGS) -dpdkstat_la_LIBADD = -ldpdk +dpdkstat_la_LIBADD = $(LIBDPDK_LIBS) endif if BUILD_PLUGIN_DRBD @@ -888,6 +910,14 @@ hugepages_la_SOURCES = src/hugepages.c hugepages_la_LDFLAGS = $(PLUGIN_LDFLAGS) endif +if BUILD_PLUGIN_INTEL_PMU +pkglib_LTLIBRARIES += intel_pmu.la +intel_pmu_la_SOURCES = src/intel_pmu.c +intel_pmu_la_CPPFLAGS = $(AM_CPPFLAGS) $(BUILD_WITH_LIBJEVENTS_CPPFLAGS) +intel_pmu_la_LDFLAGS = $(PLUGIN_LDFLAGS) $(BUILD_WITH_LIBJEVENTS_LDFLAGS) +intel_pmu_la_LIBADD = $(BUILD_WITH_LIBJEVENTS_LIBS) +endif + if BUILD_PLUGIN_INTEL_RDT pkglib_LTLIBRARIES += intel_rdt.la intel_rdt_la_SOURCES = src/intel_rdt.c @@ -1526,6 +1556,12 @@ endif endif +if BUILD_PLUGIN_SYNPROXY +pkglib_LTLIBRARIES += synproxy.la +synproxy_la_SOURCES = src/synproxy.c +synproxy_la_LDFLAGS = $(PLUGIN_LDFLAGS) +endif + if BUILD_PLUGIN_SYSLOG pkglib_LTLIBRARIES += syslog.la syslog_la_SOURCES = src/syslog.c @@ -1646,7 +1682,9 @@ endif if BUILD_PLUGIN_TURBOSTAT pkglib_LTLIBRARIES += turbostat.la -turbostat_la_SOURCES = src/turbostat.c +turbostat_la_SOURCES = \ + src/turbostat.c \ + src/msr-index.h turbostat_la_LDFLAGS = $(PLUGIN_LDFLAGS) endif diff --git a/README b/README index b0b59214..ca86c84d 100644 --- a/README +++ b/README @@ -140,6 +140,11 @@ Features hugepages can be found here: https://www.kernel.org/doc/Documentation/vm/hugetlbpage.txt. + - intel_pmu + The intel_pmu plugin reads performance counters provided by the Linux + kernel perf interface. The plugin uses jevents library to resolve named + events to perf events and access perf interface. + - intel_rdt The intel_rdt plugin collects information provided by monitoring features of Intel Resource Director Technology (Intel(R) RDT) like Cache Monitoring @@ -240,7 +245,7 @@ Features - netapp Plugin to query performance values from a NetApp storage system using the - “Manage ONTAP” SDK provided by NetApp. + “Manage ONTAP” SDK provided by NetApp. - netlink Very detailed Linux network interface and routing statistics. You can get @@ -253,8 +258,7 @@ Features plugin of choice for that. - nfs - NFS Procedures: Which NFS command were called how often. Only NFSv2 and - NFSv3 right now. + NFS Procedures: Which NFS command were called how often. - nginx Collects statistics from `nginx' (speak: engine X), a HTTP and mail @@ -786,6 +790,13 @@ Prerequisites For querying iptables counters. + * libjevents (optional) + The jevents library is used by the `intel_pmu' plugin to access the Linux + kernel perf interface. + Note: the library should be build with -fPIC flag to be linked with + intel_pmu shared object correctly. + + * libjvm (optional) Library that encapsulates the `Java Virtual Machine' (JVM). This library is used by the `java' plugin to execute Java bytecode. diff --git a/configure.ac b/configure.ac index 35b139c4..55f78d8a 100644 --- a/configure.ac +++ b/configure.ac @@ -168,6 +168,7 @@ AC_CHECK_HEADERS_ONCE([ \ pthread_np.h \ pwd.h \ regex.h \ + sys/endian.h \ sys/fs_types.h \ sys/fstyp.h \ sys/ioctl.h \ @@ -520,32 +521,6 @@ if test "x$ac_system" = "xLinux"; then ]] ) # For the turbostat plugin - AC_CHECK_HEADERS([asm/msr-index.h], - [have_asm_msrindex_h="yes"], - [have_asm_msrindex_h="no"] - ) - - if test "x$have_asm_msrindex_h" = "xyes"; then - AC_CACHE_CHECK([whether asm/msr-index.h has MSR_PKG_C10_RESIDENCY], - [c_cv_have_usable_asm_msrindex_h], - [ - AC_COMPILE_IFELSE( - [ - AC_LANG_PROGRAM( - [[#include]], - [[ - int y = MSR_PKG_C10_RESIDENCY; - return(y); - ]] - ) - ], - [c_cv_have_usable_asm_msrindex_h="yes"], - [c_cv_have_usable_asm_msrindex_h="no"], - ) - ] - ) - fi - AC_CHECK_HEADERS([cpuid.h], [have_cpuid_h="yes"], [have_cpuid_h="no (cpuid.h not found)"] @@ -2349,7 +2324,9 @@ AC_SUBST(BUILD_WITH_LIBDBI_LIBS) # --with-libdpdk {{{ AC_ARG_VAR([LIBDPDK_CPPFLAGS], [Preprocessor flags for libdpdk]) +AC_ARG_VAR([LIBDPDK_CFLAGS], [Compiler flags for libdpdk]) AC_ARG_VAR([LIBDPDK_LDFLAGS], [Linker flags for libdpdk]) +AC_ARG_VAR([LIBDPDK_LIBS], [Libraries to link for libdpdk]) AC_ARG_WITH([libdpdk], [AS_HELP_STRING([--without-libdpdk], [Disable libdpdk.])], @@ -2358,11 +2335,26 @@ AC_ARG_WITH([libdpdk], ) if test "x$with_libdpdk" != "xno"; then + PKG_CHECK_MODULES([DPDK], [libdpdk], [], + [AC_MSG_NOTICE([no DPDK pkg-config, using defaults])]) if test "x$LIBDPDK_CPPFLAGS" = "x"; then LIBDPDK_CPPFLAGS="-I/usr/include/dpdk" fi + if test "x$LIBDPDK_CFLAGS" = "x"; then + LIBDPDK_CFLAGS="$DPDK_CFLAGS" + LIBDPDK_CPPFLAGS="$LIBDPDK_CPPFLAGS $DPDK_CFLAGS" + fi + if test "x$LIBDPDK_LIBS" = "x"; then + if test "x$DPDK_LIBS" != "x"; then + LIBDPDK_LIBS="$DPDK_LIBS" + else + LIBDPDK_LIBS="-ldpdk" + fi + fi SAVE_CPPFLAGS="$CPPFLAGS" CPPFLAGS="$LIBDPDK_CPPFLAGS $CPPFLAGS" + SAVE_CFLAGS="$CFLAGS" + CFLAGS="$LIBDPDK_CFLAGS $CFLAGS" AC_CHECK_HEADERS([rte_config.h], [ with_libdpdk="yes" @@ -2384,6 +2376,7 @@ if test "x$with_libdpdk" != "xno"; then [with_libdpdk="no (rte_config.h not found)"] ) CPPFLAGS="$SAVE_CPPFLAGS" + CFLAGS="$SAVE_CFLAGS" fi if test "x$with_libdpdk" = "xyes"; then @@ -4429,6 +4422,49 @@ AC_SUBST([BUILD_WITH_LIBPQOS_LDFLAGS]) AC_SUBST([BUILD_WITH_LIBPQOS_LIBS]) # }}} +# --with-libjevents {{{ +with_libjevents_cppflags="" +with_libjevents_ldflags="" +AC_ARG_WITH([libjevents], + [AS_HELP_STRING([--with-libjevents@<:@=PREFIX@:>@], [Path to libjevents.])], + [ + if test "x$withval" != "xno" && test "x$withval" != "xyes"; then + with_libjevents_cppflags="-I$withval/include" + with_libjevents_ldflags="-L$withval/lib" + with_libjevents="yes" + else + with_libjevents="$withval" + fi + ], + [with_libjevents="yes"] +) + +if test "x$with_libjevents" = "xyes"; then + SAVE_CPPFLAGS="$CPPFLAGS" + CPPFLAGS="$CPPFLAGS $with_libjevents_cppflags" + + AC_CHECK_HEADERS([jevents.h], [with_libjevents="yes"], [with_libjevents="no (jevents.h not found)"]) + + CPPFLAGS="$SAVE_CPPFLAGS" +fi +if test "x$with_libjevents" = "xyes"; then + SAVE_LDFLAGS="$LDFLAGS" + LDFLAGS="$LDFLAGS $with_libjevents_ldflags" + + AC_CHECK_LIB([jevents], [json_events], [with_libjevents="yes"], [with_libjevents="no (Can't find libjevents)"]) + + LDFLAGS="$SAVE_LDFLAGS" +fi +if test "x$with_libjevents" = "xyes"; then + BUILD_WITH_LIBJEVENTS_CPPFLAGS="$with_libjevents_cppflags" + BUILD_WITH_LIBJEVENTS_LDFLAGS="$with_libjevents_ldflags" + BUILD_WITH_LIBJEVENTS_LIBS="-ljevents" +fi +AC_SUBST([BUILD_WITH_LIBJEVENTS_CPPFLAGS]) +AC_SUBST([BUILD_WITH_LIBJEVENTS_LDFLAGS]) +AC_SUBST([BUILD_WITH_LIBJEVENTS_LIBS]) +# }}} + # --with-libprotobuf {{{ with_libprotobuf_cppflags="" with_libprotobuf_ldflags="" @@ -6083,6 +6119,7 @@ plugin_fscache="no" plugin_gps="no" plugin_grpc="no" plugin_hugepages="no" +plugin_intel_pmu="no" plugin_intel_rdt="no" plugin_interface="no" plugin_ipc="no" @@ -6106,6 +6143,7 @@ plugin_python="no" plugin_serial="no" plugin_smart="no" plugin_swap="no" +plugin_synproxy="no" plugin_tape="no" plugin_tcpconns="no" plugin_ted="no" @@ -6150,6 +6188,7 @@ if test "x$ac_system" = "xLinux"; then plugin_protocols="yes" plugin_serial="yes" plugin_swap="yes" + plugin_synproxy="yes" plugin_tcpconns="yes" plugin_thermal="yes" plugin_uptime="yes" @@ -6162,7 +6201,7 @@ if test "x$ac_system" = "xLinux"; then plugin_ipvs="yes" fi - if test "x$c_cv_have_usable_asm_msrindex_h" = "xyes" && test "x$have_cpuid_h" = "xyes"; then + if test "x$have_cpuid_h" = "xyes"; then plugin_turbostat="yes" fi @@ -6425,8 +6464,7 @@ if test "x$with_libxenctrl" = "xyes"; then plugin_xencpu="yes" fi -if test "x$with_libdpdk" = "xyes" -then +if test "x$with_libdpdk" = "xyes"; then plugin_dpdkevents="$dpdk_keepalive" plugin_dpdkstat="yes" fi @@ -6491,6 +6529,7 @@ AC_PLUGIN([gps], [$plugin_gps], [GPS plugin]) AC_PLUGIN([grpc], [$plugin_grpc], [gRPC plugin]) AC_PLUGIN([hddtemp], [yes], [Query hddtempd]) AC_PLUGIN([hugepages], [$plugin_hugepages], [Hugepages statistics]) +AC_PLUGIN([intel_pmu], [$with_libjevents], [Intel performance monitor plugin]) AC_PLUGIN([intel_rdt], [$with_libpqos], [Intel RDT monitor plugin]) AC_PLUGIN([interface], [$plugin_interface], [Interface traffic statistics]) AC_PLUGIN([ipc], [$plugin_ipc], [IPC statistics]) @@ -6562,6 +6601,7 @@ AC_PLUGIN([snmp], [$with_libnetsnmp], [SNMP querying plugi AC_PLUGIN([snmp_agent], [$with_libnetsnmpagent], [SNMP agent plugin]) AC_PLUGIN([statsd], [yes], [StatsD plugin]) AC_PLUGIN([swap], [$plugin_swap], [Swap usage statistics]) +AC_PLUGIN([synproxy], [$plugin_synproxy], [Synproxy stats plugin]) AC_PLUGIN([syslog], [$have_syslog], [Syslog logging plugin]) AC_PLUGIN([table], [yes], [Parsing of tabular data]) AC_PLUGIN([tail], [yes], [Parsing of logfiles]) @@ -6809,6 +6849,7 @@ AC_MSG_RESULT([ libhiredis . . . . . $with_libhiredis]) AC_MSG_RESULT([ libi2c-dev . . . . . $with_libi2c]) AC_MSG_RESULT([ libiokit . . . . . . $with_libiokit]) AC_MSG_RESULT([ libiptc . . . . . . . $with_libiptc]) +AC_MSG_RESULT([ libjevents . . . . . $with_libjevents]) AC_MSG_RESULT([ libjvm . . . . . . . $with_java]) AC_MSG_RESULT([ libkstat . . . . . . $with_kstat]) AC_MSG_RESULT([ libkvm . . . . . . . $with_libkvm]) @@ -6907,6 +6948,7 @@ AC_MSG_RESULT([ gps . . . . . . . . . $enable_gps]) AC_MSG_RESULT([ grpc . . . . . . . . $enable_grpc]) AC_MSG_RESULT([ hddtemp . . . . . . . $enable_hddtemp]) AC_MSG_RESULT([ hugepages . . . . . . $enable_hugepages]) +AC_MSG_RESULT([ intel_pmu . . . . . . $enable_intel_pmu]) AC_MSG_RESULT([ intel_rdt . . . . . . $enable_intel_rdt]) AC_MSG_RESULT([ interface . . . . . . $enable_interface]) AC_MSG_RESULT([ ipc . . . . . . . . . $enable_ipc]) @@ -6977,6 +7019,7 @@ AC_MSG_RESULT([ snmp . . . . . . . . $enable_snmp]) AC_MSG_RESULT([ snmp_agent . . . . . $enable_snmp_agent]) AC_MSG_RESULT([ statsd . . . . . . . $enable_statsd]) AC_MSG_RESULT([ swap . . . . . . . . $enable_swap]) +AC_MSG_RESULT([ synproxy . . . . . . $enable_synproxy]) AC_MSG_RESULT([ syslog . . . . . . . $enable_syslog]) AC_MSG_RESULT([ table . . . . . . . . $enable_table]) AC_MSG_RESULT([ tail_csv . . . . . . $enable_tail_csv]) diff --git a/contrib/curl_json/php-fpm.conf b/contrib/curl_json/php-fpm.conf new file mode 100644 index 00000000..34b8b67d --- /dev/null +++ b/contrib/curl_json/php-fpm.conf @@ -0,0 +1,27 @@ +# Example configuration for PHP-FPM + + + Plugin "phpfpm" + Instance "main" + + Type "total_requests" + Instance "accepted" + + + Type "total_requests" + Instance "slow" + + + Type "queue_length" + Instance "listen" + + + Type "vs_processes" + Instance "active" + + + Type "vs_processes" + Instance "total" + + + diff --git a/contrib/redhat/collectd.spec b/contrib/redhat/collectd.spec index b36e2a27..7862663e 100644 --- a/contrib/redhat/collectd.spec +++ b/contrib/redhat/collectd.spec @@ -16,7 +16,7 @@ # # - fetch the desired collectd release file from https://collectd.org/files/ # and save it in your ~/rpmbuild/SOURCES/ directory (or build your own out of -# the git repository: ./build.sh && ./configure && make-dist-bz2) +# the git repository: ./build.sh && ./configure && make dist) # # - copy this file in your ~/rpmbuild/SPECS/ directory. Make sure the # "Version:" tag matches the version from the tarball. @@ -178,6 +178,8 @@ %define with_grpc 0%{!?_without_grpc:0} # plugin lpar disabled, requires AIX %define with_lpar 0%{!?_without_lpar:0} +# plugin intel_pmu disabled, requires libjevents +%define with_intel_pmu 0%{!?_without_intel_pmu:0} # plugin intel_rdt disabled, requires intel-cmt-cat %define with_intel_rdt 0%{!?_without_intel_rdt:0} # plugin mic disabled, requires Mic @@ -245,7 +247,7 @@ Summary: Statistics collection and monitoring daemon Name: collectd Version: 5.7.1 -Release: 6%{?dist} +Release: 7%{?dist} URL: https://collectd.org Source: https://collectd.org/files/%{name}-%{version}.tar.bz2 License: GPLv2 @@ -470,6 +472,16 @@ The HDDTemp plugin collects the temperature of hard disks. The temperatures are provided via SMART and queried by the external hddtemp daemon. %endif +%if %{with_intel_pmu} +%package intel_pmu +Summary: Intel PMU plugin for collectd +Group: System Environment/Daemons +Requires: %{name}%{?_isa} = %{version}-%{release} +%description intel_pmu +The intel_pmu plugin reads performance counters provided by the Linux +kernel perf interface. +%endif + %if %{with_intel_rdt} %package intel_rdt Summary: Intel RDT plugin for collectd @@ -509,8 +521,8 @@ the byte- and packet-counters of selected rules and submit them to collectd. Summary: Java plugin for collectd Group: System Environment/Daemons Requires: %{name}%{?_isa} = %{version}-%{release} -BuildRequires: java-devel, jpackage-utils -Requires: java, jpackage-utils +BuildRequires: java-devel >= 1.6, jpackage-utils >= 1.6 +Requires: java >= 1.6, jpackage-utils >= 1.6 %description java This plugin for collectd allows plugins to be written in Java and executed in an embedded JVM. @@ -1236,6 +1248,12 @@ Collectd utilities %define _with_hugepages --disable-hugepages %endif +%if %{with_intel_pmu} +%define _with_intel_pmu --enable-intel_pmu +%else +%define _with_intel_pmu --disable-intel_pmu +%endif + %if %{with_intel_rdt} %define _with_intel_rdt --enable-intel_rdt %else @@ -1902,6 +1920,7 @@ Collectd utilities %{?_with_grpc} \ %{?_with_hddtemp} \ %{?_with_hugepages} \ + %{?_with_intel_pmu} \ %{?_with_intel_rdt} \ %{?_with_interface} \ %{?_with_ipc} \ @@ -2466,6 +2485,11 @@ fi %{_libdir}/%{name}/hddtemp.so %endif +%if %{with_intel_pmu} +%files intel_pmu +%{_libdir}/%{name}/intel_pmu.so +%endif + %if %{with_intel_rdt} %files intel_rdt %{_libdir}/%{name}/intel_rdt.so @@ -2699,6 +2723,9 @@ fi %doc contrib/ %changelog +* Fri Aug 18 2017 Ruben Kerkhof - 5.7.1-7 +- Add new intel_pmu plugin + * Sun Mar 05 2017 Ruben Kerkhof - 5.7.1-6 - Move recently added plugins to subpackages diff --git a/contrib/systemd.collectd.service b/contrib/systemd.collectd.service index 6333d49a..9c037a4e 100644 --- a/contrib/systemd.collectd.service +++ b/contrib/systemd.collectd.service @@ -19,6 +19,7 @@ ProtectHome=true # dns CAP_NET_RAW # exec CAP_SETUID CAP_SETGID # intel_rdt CAP_SYS_RAWIO +# intel_pmu CAP_SYS_ADMIN # iptables CAP_NET_ADMIN # ping CAP_NET_RAW # smart CAP_SYS_RAWIO diff --git a/proto/types.proto b/proto/types.proto index 952c5418..fde3afaf 100644 --- a/proto/types.proto +++ b/proto/types.proto @@ -38,6 +38,16 @@ message Identifier { string type_instance = 5; } +message MetadataValue { + oneof value { + string string_value = 1; + int64 int64_value = 2; + uint64 uint64_value = 3; + double double_value = 4; + bool bool_value = 5; + }; +} + message Value { oneof value { uint64 counter = 1; @@ -56,4 +66,5 @@ message ValueList { Identifier identifier = 4; repeated string ds_names = 5; -} + map meta_data = 6; +} \ No newline at end of file diff --git a/src/apache.c b/src/apache.c index 650760c3..07b2b57d 100644 --- a/src/apache.c +++ b/src/apache.c @@ -216,29 +216,25 @@ static int config_add(oconfig_item_t *ci) { status = -1; } - if (status == 0) { - char callback_name[3 * DATA_MAX_NAME_LEN]; - - snprintf(callback_name, sizeof(callback_name), "apache/%s/%s", - (st->host != NULL) ? st->host : hostname_g, - (st->name != NULL) ? st->name : "default"); - - status = plugin_register_complex_read( - /* group = */ NULL, - /* name = */ callback_name, - /* callback = */ apache_read_host, - /* interval = */ 0, - &(user_data_t){ - .data = st, .free_func = apache_free, - }); - } - if (status != 0) { apache_free(st); return -1; } - return 0; + char callback_name[3 * DATA_MAX_NAME_LEN]; + + snprintf(callback_name, sizeof(callback_name), "apache/%s/%s", + (st->host != NULL) ? st->host : hostname_g, + (st->name != NULL) ? st->name : "default"); + + return plugin_register_complex_read( + /* group = */ NULL, + /* name = */ callback_name, + /* callback = */ apache_read_host, + /* interval = */ 0, + &(user_data_t){ + .data = st, .free_func = apache_free, + }); } /* int config_add */ static int config(oconfig_item_t *ci) { diff --git a/src/ceph.c b/src/ceph.c index c2284cb3..8bee8e88 100644 --- a/src/ceph.c +++ b/src/ceph.c @@ -418,7 +418,7 @@ static void ceph_daemon_free(struct ceph_daemon *d) { } /* compact_ds_name removed the special characters ":", "_", "-" and "+" from the - * intput string. Characters following these special characters are capitalized. + * input string. Characters following these special characters are capitalized. * Trailing "+" and "-" characters are replaces with the strings "Plus" and * "Minus". */ static int compact_ds_name(char *buffer, size_t buffer_size, char const *src) { diff --git a/src/collectd-nagios.c b/src/collectd-nagios.c index 4c54dad4..89f73b83 100644 --- a/src/collectd-nagios.c +++ b/src/collectd-nagios.c @@ -71,7 +71,7 @@ #endif #endif /* NAN_ZERO_ZERO */ -#include "libcollectdclient/collectd/client.h" +#include "collectd/client.h" #define RET_OKAY 0 #define RET_WARNING 1 diff --git a/src/collectd-python.pod b/src/collectd-python.pod index f4b5f141..1f46f6f3 100644 --- a/src/collectd-python.pod +++ b/src/collectd-python.pod @@ -489,7 +489,7 @@ Methods defined here: =over 4 -=item B([type][, values][, plugin_instance][, type_instance][, plugin][, host][, time][, interval]) -> None. Dispatch a value list. +=item B([type][, message][, plugin_instance][, type_instance][, plugin][, host][, time][, severity][, meta]) -> None. Dispatch a notification. Dispatch this instance to the collectd process. The object has members for each of the possible arguments for this method. For a detailed explanation of these @@ -515,6 +515,16 @@ generated. The severity of this notification. Assign or compare to I, I or I. +=item meta + +These are the meta data for the Notification object. +It has to be a dictionary of numbers, strings or bools. All keys must be +strings. I and I objects will be dispatched as signed integers unless +they are between 2**63 and 2**64-1, which will result in a unsigned integer. +One of these storage classes can be forced by using the classes +B and B. A meta object received by a +notification callback will always contain B or B objects. + =back =head1 FUNCTIONS diff --git a/src/collectd-snmp.pod b/src/collectd-snmp.pod index edb95060..d615088e 100644 --- a/src/collectd-snmp.pod +++ b/src/collectd-snmp.pod @@ -36,6 +36,8 @@ collectd-snmp - Documentation of collectd's C Community "community_string" Collect "std_traffic" Interval 120 + Timeout 10 + Retries 1 Address "192.168.0.42" @@ -60,6 +62,8 @@ collectd-snmp - Documentation of collectd's C Community "more_communities" Collect "powerplus_voltge_input" Interval 300 + Timeout 5 + Retries 5 @@ -78,7 +82,7 @@ and ten threads are used. =head1 CONFIGURATION Since the aim of the C is to provide a generic interface to SNMP, -it's configuration is not trivial and may take some time. +its configuration is not trivial and may take some time. Since the C library is used you can use all the environment variables that are interpreted by that package. See L for more details. @@ -281,6 +285,15 @@ switches, embedded devices, rack monitoring systems and so on. Since the B of generated RRD files depends on this setting it's wise to select a reasonable value once and never change it. +=item B I + +How long to wait for a response. The C library default is 1 second. + +=item B I + +The number of times that a query should be retried after the Timeout expires. +The C library default is 5. + =back =head1 SEE ALSO diff --git a/src/collectd-tg.c b/src/collectd-tg.c index d8b2ea19..48f2dc43 100644 --- a/src/collectd-tg.c +++ b/src/collectd-tg.c @@ -44,8 +44,8 @@ #include "utils_heap.h" -#include "libcollectdclient/collectd/client.h" -#include "libcollectdclient/collectd/network.h" +#include "collectd/client.h" +#include "collectd/network.h" #define DEF_NUM_HOSTS 1000 #define DEF_NUM_PLUGINS 20 diff --git a/src/collectd.conf.in b/src/collectd.conf.in index 6b59d4d5..261abdfd 100644 --- a/src/collectd.conf.in +++ b/src/collectd.conf.in @@ -129,6 +129,7 @@ #@BUILD_PLUGIN_GRPC_TRUE@LoadPlugin grpc #@BUILD_PLUGIN_HDDTEMP_TRUE@LoadPlugin hddtemp #@BUILD_PLUGIN_HUGEPAGES_TRUE@LoadPlugin hugepages +#@BUILD_PLUGIN_INTEL_PMU_TRUE@LoadPlugin intel_pmu #@BUILD_PLUGIN_INTEL_RDT_TRUE@LoadPlugin intel_rdt @BUILD_PLUGIN_INTERFACE_TRUE@@BUILD_PLUGIN_INTERFACE_TRUE@LoadPlugin interface #@BUILD_PLUGIN_IPC_TRUE@LoadPlugin ipc @@ -363,6 +364,9 @@ # ReportByCpu true # ReportByState true # ValuesPercentage false +# ReportNumCpu false +# ReportGuestState false +# SubtractGuestState true # # # @@ -462,6 +466,7 @@ # # # Host "my_host" +# #Plugin "stats" # Instance "some_instance" # User "collectd" # Password "thaiNg0I" @@ -476,6 +481,7 @@ # Type "magic_level" # #InstancePrefix "prefix-" # InstanceFrom "td[1]" +# #PluginInstanceFrom "td[1]" # ValuesFrom "td[2]/span[@class=\"level\"]" # # @@ -491,6 +497,7 @@ # # # +# #Plugin "mycompany" # Driver "mysql" # DriverOption "host" "localhost" # DriverOption "username" "collectd" @@ -587,12 +594,17 @@ # # +# #Plugin "foo" # Instance "foodir" # Name "*.conf" # MTime "-5m" # Size "+10k" # Recursive true # IncludeHidden false +# RegularOnly true +# #FilesSizeType "bytes" +# #FilesCountType "files" +# #TypeInstance "instance" # # @@ -645,6 +657,14 @@ # ValuesPercentage false # +# +# ReportHardwareCacheEvents true +# ReportKernelPMUEvents true +# ReportSoftwareEvents true +# EventList "/var/cache/pmu/GenuineIntel-6-2D-core.json" +# HardwareEvents "L2_RQSTS.CODE_RD_HIT,L2_RQSTS.CODE_RD_MISS" "L2_RQSTS.ALL_CODE_RD" +# + # # Cores "0-2" # @@ -657,12 +677,33 @@ # # -# Sensor "some_sensor" -# Sensor "another_one" -# IgnoreSelected false -# NotifySensorAdd false -# NotifySensorRemove true -# NotifySensorNotPresent false +# +# Sensor "some_sensor" +# Sensor "another_one" +# IgnoreSelected false +# NotifySensorAdd false +# NotifySensorRemove true +# NotifySensorNotPresent false +# NotifyIPMIConnectionState false +# SELEnabled false +# SELClearEvent false +# +# +# Host "server.example.com" +# Address "1.2.3.4" +# Username "user" +# Password "secret" +# #AuthType "md5" +# Sensor "some_sensor" +# Sensor "another_one" +# IgnoreSelected false +# NotifySensorAdd false +# NotifySensorRemove true +# NotifySensorNotPresent false +# NotifyIPMIConnectionState false +# SELEnabled false +# SELClearEvent false +# # # @@ -719,8 +760,11 @@ # # -# McelogClientSocket "/var/run/mcelog-client" -# McelogLogfile "/var/log/mcelog" +# +# McelogClientSocket "/var/run/mcelog-client" +# PersistentNotification false +# +# McelogLogfile "/var/log/mcelog" # # @@ -802,6 +846,11 @@ # QoS 2 # Topic "collectd/#" # CleanSession true +# CACert "/etc/ssl/ca.crt" +# CertificateFile "/etc/ssl/client.crt" +# CertificateKeyFile "/etc/ssl/client.pem" +# TLSProtocol "tlsv1.2" +# CipherSuite "ciphers" # # @@ -928,6 +977,12 @@ # CacheFlush 1800 @LOAD_PLUGIN_NETWORK@ +# +# ReportV2 false +# #ReportV3 false +# #ReportV4 false +# + # # URL "http://localhost/status?auto" # User "www-user" @@ -1015,6 +1070,7 @@ # # # +# #Plugin "warehouse" # ConnectID "db01" # Username "oracle" # Password "secret" @@ -1101,6 +1157,7 @@ # StoreRates true # # +# #Plugin "kingdom" # Host "hostname" # Port "5432" # User "username" @@ -1140,6 +1197,7 @@ # # CollectFileDescriptor true # CollectContextSwitch true +# CollectMemoryMaps true # Process "name" # ProcessMatch "name" "regex" # @@ -1259,6 +1317,8 @@ # Community "community_string" # Collect "std_traffic" # Interval 120 +# Timeout 10 +# Retries 1 # # # Address "192.168.0.42" @@ -1272,6 +1332,8 @@ # Community "more_communities" # Collect "powerplus_voltge_input" # Interval 300 +# Timeout 5 +# Retries 5 # # @@ -1321,10 +1383,12 @@ # ReportBytes true # ValuesAbsolute true # ValuesPercentage false +# ReportIO true # # # +# #Plugin "table" # Instance "slabinfo" # Separator " " # @@ -1376,6 +1440,7 @@ # Bucket 0.5 1.0 # -> bucket-latency-foo-0.5_1 # Bucket 1.0 2.0 # -> bucket-latency-foo-1_2 # Bucket 2.0 0 # -> bucket-latency-foo-2_inf +# #BucketType "bucket" # # Type "latency" # Instance "foo" @@ -1455,7 +1520,7 @@ # SystemManagementInterrupt true # DigitalTemperatureSensor true # PackageThermalManagement true -# RunningAveragePowerLimit "7" +# RunningAveragePowerLimit "7" # # @@ -1505,7 +1570,7 @@ # CollectPurge false # Varnish 2 only # CollectSession false # CollectSHM true -# CollectSMA false # Varnish 2 only +# CollectSMA false # Varnish 2 & 4 only # CollectSMS false # CollectSM false # Varnish 2 only # CollectStruct false @@ -1514,6 +1579,12 @@ # CollectVCL false # CollectVSM false # Varnish 4 only # CollectWorkers false +# CollectLock false # Varnish 4 only +# CollectMempool false # Varnish 4 only +# CollectManagement false # Varnish 4 only +# CollectSMF false # Varnish 4 only +# CollectVBE false # Varnish 4 only +# CollectMSE false # Varnish-Plus 4 only # # @@ -1570,6 +1641,7 @@ # Header "X-Custom-Header: custom_value" # SSLVersion "TLSv1" # Format "Command" +# Prefix "collectd" # metric prefix, only available for KAIROSDB format # Attribute "key" "value" # only available for KAIROSDB format # TTL 0 # data ttl, only available for KAIROSDB format # Metrics true diff --git a/src/collectd.conf.pod b/src/collectd.conf.pod index 67ee0a93..4c4c261e 100644 --- a/src/collectd.conf.pod +++ b/src/collectd.conf.pod @@ -1474,6 +1474,19 @@ in the un-aggregated (per-CPU, per-state) mode as well. When set to B, reports the number of available CPUs. Defaults to B. +=item B B|B + +When set to B, reports the "guest" and "guest_nice" CPU states. +Defaults to B. + +=item B B|B + +This option is only considered when B is set to B. +"guest" and "guest_nice" are included in respectively "user" and "nice". +If set to B, "guest" will be subtracted from "user" and "guest_nice" +will be subtracted from "nice". +Defaults to B. + =back =head2 Plugin C @@ -1611,6 +1624,7 @@ finance page and dispatch the value to collectd. + Plugin "quotes" URL "http://finance.google.com/finance?q=NYSE%3AAMD" User "foo" Password "bar" @@ -1642,6 +1656,11 @@ The following options are valid within B blocks: =over 4 +=item B I + +Use I as the plugin name when submitting values. +Defaults to C. + =item B I URL of the web site to retrieve. Since a regular expression will be used to @@ -1802,6 +1821,11 @@ The following options are valid within B blocks: Use I as the host name when submitting values. Defaults to the global host name setting. +=item B I + +Use I as the plugin name when submitting values. +Defaults to C. + =item B I Sets the plugin instance to I. @@ -1864,6 +1888,7 @@ The B uses B (L) and B Host "my_host" + #Plugin "curl_xml" Instance "some_instance" User "collectd" Password "thaiNg0I" @@ -1877,6 +1902,7 @@ The B uses B (L) and B Type "magic_level" #InstancePrefix "prefix-" InstanceFrom "td[1]" + #PluginInstanceFrom "td[1]" ValuesFrom "td[2]/span[@class=\"level\"]" @@ -1902,10 +1928,16 @@ Within the B block the following options are accepted: Use I as the host name when submitting values. Defaults to the global host name setting. +=item B I + +Use I as the plugin name when submitting values. +Defaults to 'curl_xml'. + =item B I -Use I as the plugin instance when submitting values. Defaults to an -empty string (no plugin instance). +Use I as the plugin instance when submitting values. +May be overridden by B option inside B blocks. +Defaults to an empty string (no plugin instance). =item B I I @@ -1976,9 +2008,19 @@ 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 + +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. + +=back + +If the "base XPath expression" (the argument to the B block) returns +exactly one argument, then I and I may be omitted. +Otherwise, at least one of I or I is required. + +=over 4 =item B I [I ...] @@ -1987,6 +2029,7 @@ 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. +This option is required. =back @@ -2017,6 +2060,7 @@ than those of other plugins. It usually looks something like this: + #Plugin "warehouse" Driver "mysql" Interval 120 DriverOption "host" "localhost" @@ -2198,6 +2242,11 @@ the daemon. Other than that, that name is not used. =over 4 +=item B I + +Use I as the plugin name when submitting query results from +this B. Defaults to C. + =item B I Sets the interval (in seconds) in which the values will be collected from this @@ -2730,12 +2779,16 @@ blocks, the following options are recognized: =over 4 +=item B I + +Use I as the plugin name when submitting values. +Defaults to B. + =item B I -Sets the plugin instance to I. That instance name must be unique, but -it's your responsibility, the plugin doesn't check for that. If not given, the -instance is set to the directory name with all slashes replaced by underscores -and all leading underscores removed. +Sets the plugin instance to I. If not given, the instance is set to +the directory name with all slashes replaced by underscores and all leading +underscores removed. Empty value is allowed. =item B I @@ -2781,6 +2834,26 @@ 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. +=item B I|I + +Controls whether or not to include only regular files in the count. +Defaults to I, i.e. by default non regular files are ignored. + +=item B I + +Sets the type used to dispatch files combined size. Empty value ("") disables +reporting. Defaults to B. + +=item B I + +Sets the type used to dispatch number of files. Empty value ("") disables +reporting. Defaults to B. + +=item B I + +Sets the I used to dispatch values. Defaults to an empty string +(no plugin instance). + =back =head2 Plugin C @@ -3073,6 +3146,92 @@ Defaults to B. =back +=head2 Plugin C + +The I plugin collects performance counters data on Intel CPUs using +Linux perf interface. All events are reported on a per core basis. + +B + + + ReportHardwareCacheEvents true + ReportKernelPMUEvents true + ReportSoftwareEvents true + EventList "/var/cache/pmu/GenuineIntel-6-2D-core.json" + HardwareEvents "L2_RQSTS.CODE_RD_HIT,L2_RQSTS.CODE_RD_MISS" "L2_RQSTS.ALL_CODE_RD" + + +B + +=over 4 + +=item B B|B + +Enable or disable measuring of hardware CPU cache events: + - L1-dcache-loads + - L1-dcache-load-misses + - L1-dcache-stores + - L1-dcache-store-misses + - L1-dcache-prefetches + - L1-dcache-prefetch-misses + - L1-icache-loads + - L1-icache-load-misses + - L1-icache-prefetches + - L1-icache-prefetch-misses + - LLC-loads + - LLC-load-misses + - LLC-stores + - LLC-store-misses + - LLC-prefetches + - LLC-prefetch-misses + - dTLB-loads + - dTLB-load-misses + - dTLB-stores + - dTLB-store-misses + - dTLB-prefetches + - dTLB-prefetch-misses + - iTLB-loads + - iTLB-load-misses + - branch-loads + - branch-load-misses + +=item B B|B + +Enable or disable measuring of the following events: + - cpu-cycles + - instructions + - cache-references + - cache-misses + - branches + - branch-misses + - bus-cycles + +=item B B|B + +Enable or disable measuring of software events provided by kernel: + - cpu-clock + - task-clock + - context-switches + - cpu-migrations + - page-faults + - minor-faults + - major-faults + - alignment-faults + - emulation-faults + +=item B I + +JSON performance counter event list file name. To be able to monitor all Intel +CPU specific events JSON event list file should be downloaded. Use the pmu-tools +event_download.py script to download event list for current CPU. + +=item B I + +This field is a list of event names or groups of comma separated event names. +This option requires B option to be configured. + +=back + =head2 Plugin C The I plugin collects information provided by monitoring features of @@ -3197,8 +3356,43 @@ This option is only available on Solaris. =head2 Plugin C +The B allows to monitor server platform status using the Intelligent +Platform Management Interface (IPMI). Local and remote interfaces are supported. + +The plugin configuration consists of one or more B blocks which +specify one I connection each. Each block requires one unique string +argument as the instance name. If instances are not configured, an instance with +the default option values will be created. + +For backwards compatibility, any option other than B block will trigger +legacy config handling and it will be treated as an option within B +block. This support will go away in the next major version of Collectd. + +Within the B blocks, the following options are allowed: + =over 4 +=item B
I
+ +Hostname or IP to connect to. If not specified, plugin will try to connect to +local management controller (BMC). + +=item B I + +=item B I + +The username and the password to use for the connection to remote BMC. + +=item B I|I + +Forces the authentication type to use for the connection to remote BMC. +By default most secure type is seleted. + +=item B I + +Sets the B field of dispatched values. Defaults to the global hostname +setting. + =item B I Selects sensors to collect or to ignore, depending on B. @@ -3227,6 +3421,24 @@ If a sensor disappears a notification is sent. If you have for example dual power supply and one of them is (un)plugged then a notification is sent. +=item B I|I + +If a IPMI connection state changes after initialization time of a minute +a notification is sent. Defaults to B. + +=item B I|I + +If system event log (SEL) is enabled, plugin will listen for sensor threshold +and discrete events. When event is received the notification is sent. +Defaults to B. + +=item B I|I + +If SEL clear event is enabled, plugin will delete event from SEL list after +it is received and successfully handled. In this case other tools that are +subscribed for SEL events will receive an empty event. +Defaults to B. + =back =head2 Plugin C @@ -3478,17 +3690,36 @@ By default the plugin connects to B<"/var/run/mcelog-client"> to check if the mcelog server is running. When the server is running, the plugin will tail the specified logfile to retrieve machine check exception information and send a notification with the details from the logfile. The plugin will use the mcelog -client protocol to retrieve memory related machine check exceptions. +client protocol to retrieve memory related machine check exceptions. Note that +for memory exceptions, notifications are only sent when there is a change in +the number of corrected/uncorrected memory errors. -=over 4 +=head3 The Memory block + +Note: these options cannot be used in conjunction with the logfile options, they are mutually +exclusive. + +=over 3 =item B I Connect to the mcelog client socket using the UNIX domain socket at I. Defaults to B<"/var/run/mcelog-client">. +=item B B|B +Override default configuration to only send notifications when sent when there +is a change in the number of corrected/uncorrected memory errors. When set to +true notifications will be sent for every read cycle. Default is false. Does +not affect the stats being dispatched. + +=back + +=over 4 + =item B I -The mcelog file to parse. Defaults to B<"/var/log/mcelog">. +The mcelog file to parse. Defaults to B<"/var/log/mcelog">. Note: this option +cannot be used in conjunction with the memory block options, they are mutually +exclusive. =back @@ -3536,6 +3767,7 @@ Synopsis of the configuration: Server "localhost" Key "page_key" + Plugin "plugin_name" Regex "(\\d+) bytes sent" DSType CounterAdd @@ -3563,6 +3795,11 @@ B block. When connected to the memcached server, asks for the page I. +=item B I + +Use I as the plugin name when submitting values. +Defaults to C. + =item EBE Match blocks define which strings to look for and how matches substrings are @@ -4068,18 +4305,18 @@ the B branch. Path to the PEM-encoded CA certificate file. Setting this option enables TLS communication with the MQTT broker, and as such, B should be the TLS-enabled port of the MQTT broker. -A valid TLS configuration requires B, B and B. +This option enables the use of TLS. =item B I Path to the PEM-encoded certificate file to use as client certificate when connecting to the MQTT broker. -A valid TLS configuration requires B, B and B. +Only valid if B and B are also set. =item B I Path to the unencrypted PEM-encoded key file corresponding to B. -A valid TLS configuration requires B, B and B. +Only valid if B and B are also set. =item B I @@ -4087,13 +4324,14 @@ If configured, this specifies the string protocol version (e.g. C, C) to use for the TLS connection to the broker. If not set a default version is used which depends on the version of OpenSSL the Mosquitto library was linked against. +Only valid if B is set. =item B I A string describing the ciphers available for use. See L and the C utility for more information. If unset, the default ciphers will be used. - +Only valid if B is set. =back @@ -4223,11 +4461,11 @@ If enabled, the plugin sends a notification if the replication slave I/O and / or SQL threads are not running. Defaults to B. =item B I - + Enable the collection of wsrep plugin statistics, used in Master-Master replication setups like in MySQL Galera/Percona XtraDB Cluster. User needs only privileges to execute 'SHOW GLOBAL STATUS' - + =item B I Sets the connect timeout for the MySQL client. @@ -5061,6 +5299,25 @@ statistics available. Defaults to B. =back +=head2 Plugin C + +The I collects information about the usage of the Network File +System (NFS). It counts the number of procedure calls for each procedure, +grouped by version and whether the system runs as server or client. + +It is possibly to omit metrics for a specific NFS version by setting one or +more of the following options to B (all of them default to B). + +=over 4 + +=item B B|B + +=item B B|B + +=item B B|B + +=back + =head2 Plugin C This plugin collects the number of connections and requests handled by the @@ -5531,15 +5788,12 @@ The OpenVPN plugin reads a status file maintained by OpenVPN and gathers traffic statistics about connected clients. To set up OpenVPN to write to the status file periodically, use the -B<--status> option of OpenVPN. Since OpenVPN can write two different formats, -you need to set the required format, too. This is done by setting -B<--status-version> to B<2>. +B<--status> option of OpenVPN. So, in a nutshell you need: openvpn $OTHER_OPTIONS \ - --status "/var/run/openvpn-status" 10 \ - --status-version 2 + --status "/var/run/openvpn-status" 10 Available options: @@ -5594,6 +5848,7 @@ plugin's documentation above for details. + #Plugin "warehouse" ConnectID "db01" Username "oracle" Password "secret" @@ -5616,6 +5871,11 @@ values submitted to the daemon. Other than that, that name is not used. =over 4 +=item B I + +Use I as the plugin name when submitting query results from +this B. Defaults to C. + =item B I Defines the "database alias" or "service name" to connect to. Usually, these @@ -5671,7 +5931,7 @@ The address of the OVS DB server JSON-RPC interface used by the plugin. To enable the interface, OVS DB daemon should be running with C<--remote=ptcp:> option. See L for more details. The option may be either network hostname, IPv4 numbers-and-dots notation or IPv6 hexadecimal string -format. Defaults to B<'localhost'>. +format. Defaults to C. =item B I @@ -5737,7 +5997,7 @@ The address of the OVS DB server JSON-RPC interface used by the plugin. To enable the interface, OVS DB daemon should be running with C<--remote=ptcp:> option. See L for more details. The option may be either network hostname, IPv4 numbers-and-dots notation or IPv6 hexadecimal string -format. Defaults to B<'localhost'>. +format. Defaults to C. =item B I @@ -5969,6 +6229,7 @@ L. + Plugin "kingdom" Host "hostname" Port "5432" User "username" @@ -6290,6 +6551,11 @@ activating this option. The draw-back is, that data covering the specified amount of time will be lost, for example, if a single statement within the transaction fails or if the database server crashes. +=item B I + +Use I as the plugin name when submitting query results from +this B. Defaults to C. + =item B I Specify the plugin instance name that should be used instead of the database @@ -6543,39 +6809,81 @@ C/var/run/collectd-powerdns>. =head2 Plugin C -=over 4 +Collects information about processes of local system. -=item B I +By default, with no process matches configured, only general statistics is +collected: the number of processes in each state and fork rate. -Select more detailed statistics of processes matching this name. The statistics -collected for these selected processes are: +Process matches can be configured by B and B options. +These may also be a block in which further options may be specified. + +The statistics collected for matched processes are: - size of the resident segment size (RSS) - user- and system-time used - number of processes - number of threads - number of open files (under Linux) + - number of memory mapped files (under Linux) - io data (where available) - context switches (under Linux) - minor and major pagefaults. -Some platforms have a limit on the length of process names. I must stay -below this limit. +B + + + CollectFileDescriptor true + CollectContextSwitch true + Process "name" + ProcessMatch "name" "regex" + + CollectFileDescriptor false + CollectContextSwitch false + + + CollectFileDescriptor false + CollectContextSwitch true + + + +=over 4 + +=item B I + +Select more detailed statistics of processes matching this name. + +Some platforms have a limit on the length of process names. +I must stay below this limit. =item B I I -Similar to the B option this allows one to select more detailed -statistics of processes matching the specified I (see L for -details). The statistics of all matching processes are summed up and -dispatched to the daemon using the specified I as an identifier. This -allows one to "group" several processes together. I must not contain -slashes. +Select more detailed statistics of processes matching the specified I +(see L for details). The statistics of all matching processes are +summed up and dispatched to the daemon using the specified I as an +identifier. This allows one to "group" several processes together. +I must not contain slashes. =item B I -Collect context switch of the process. +Collect the number of context switches for matched processes. +Disabled by default. + +=item B I + +Collect number of file descriptors of matched processes. +Disabled by default. + +=item B I + +Collect the number of memory mapped files of the process. +The limit for this number is configured via F in +the Linux kernel. =back +Options B and B may be used inside +B and B blocks - then they affect corresponding match +only. Otherwise they set the default value for subsequent matches. + =head2 Plugin C Collects a lot of information about various network protocols, such as I, @@ -6957,14 +7265,20 @@ one (exclusive). When the C plugin uses a cache (by setting B, see below) it writes all values for a certain RRD-file if the oldest value is older than -(or equal to) the number of seconds specified. If some RRD-file is not updated +(or equal to) the number of seconds specified by B. +That check happens on new values arriwal. If some RRD-file is not updated anymore for some reason (the computer was shut down, the network is broken, -etc.) some values may still be in the cache. If B is set, then the -entire cache is searched for entries older than B seconds and -written to disk every I seconds. Since this is kind of expensive and -does nothing under normal circumstances, this value should not be too small. -900 seconds might be a good value, though setting this to 7200 seconds doesn't -normally do much harm either. +etc.) some values may still be in the cache. If B is set, then +every I seconds the entire cache is searched for entries older than +B + B seconds. The entries found are written to +disk. Since scanning the entire cache is kind of expensive and does nothing +under normal circumstances, this value should not be too small. 900 seconds +might be a good value, though setting this to 7200 seconds doesn't normally +do much harm either. + +Defaults to 10x B. +B must be larger than or equal to B, otherwise the +above default is used. =item B I @@ -7397,6 +7711,13 @@ available and free. Defaults to B. This is useful for deploying I in a heterogeneous environment, where swap sizes differ and you want to specify generic thresholds or similar. +=item B B|B + +Enables or disables reporting swap IO. Defaults to B. + +This is useful for the cases when swap IO is not neccessary, is not available, +or is not reliable. + =back =head2 Plugin C @@ -7432,6 +7753,7 @@ filesystem or CSV (comma separated values) files.
+ #Plugin "slab" Instance "slabinfo" Separator " " @@ -7458,10 +7780,14 @@ The following options are available inside a B
block: =over 4 +=item B I + +If specified, I is used as the plugin name when submitting values. +Defaults to B
. + =item B I -If specified, I is used as the plugin instance. So, in the above -example, the plugin name C would be used. If omitted, the +If specified, I is used as the plugin instance. If omitted, the filename of the table is used instead, with all special characters replaced with an underscore (C<_>). @@ -7531,6 +7857,7 @@ user using (extended) regular expressions, as described in L. + Plugin "mail" Instance "exim" Interval 60 @@ -7551,6 +7878,7 @@ user using (extended) regular expressions, as described in L. Percentile 99 Bucket 0 100 + #BucketType "bucket" Type "latency" Instance "foo" @@ -7562,11 +7890,13 @@ The config consists of one or more B blocks, each of which configures one logfile to parse. Within each B block, there are one or more B blocks, which configure a regular expression to search for. -The B option in the B block may be used to set the plugin -instance. So in the above example the plugin name C would be used. -This plugin instance is for all B blocks that B it, until the -next B option. This way you can extract several plugin instances from -one logfile, handy when parsing syslog and the like. +The B and B options in the B block may be used to set +the plugin name and instance respectively. So in the above example the plugin name +C would be used. + +These options are applied for all B blocks that B it, until the +next B or B option. This way you can extract several plugin +instances from one logfile, handy when parsing syslog and the like. The B option allows you to define the length of time between reads. If this is not set, the default Interval will be used. @@ -7670,6 +8000,7 @@ B Percentile 99 Bucket 0 100 + BucketType "bucket" =over 4 @@ -7706,11 +8037,17 @@ the following schema: Bucket 20 50 Bucket 50 0 -Metrics are reported with the I C and the I +Metrics are reported with the I set by B option (C +by default) and the I CTypeE[-EInstanceE]-Elower_boundE_Eupper_boundE>. This option may be repeated to calculate more than one rate. +=item B I + +Sets the type used to dispatch B metrics. +Optional, by default C will be used. + =back =back @@ -7747,7 +8084,8 @@ B Index 1 - Instance "snort-eth0" + Plugin "snortstats" + Instance "eth0" Interval 600 Collect "snort-dropped" @@ -7799,6 +8137,11 @@ I block but there can be multiple if you have multiple CSV files. =over 4 +=item B I + +Use I as the plugin name when submitting values. +Defaults to C. + =item B I Sets the I used when dispatching the values. @@ -8062,9 +8405,9 @@ collections. The different bits of this bit mask accepted by this plugin are: Boolean enabling the use of logical core numbering for per core statistics. When enabled, CnE> is used as plugin instance, where I is a -sequential number assigned by the kernel. Otherwise, CnE> is used -where I is the n-th core of the socket, causing name conflicts when there is -more than one socket. +dynamic number assigned by the kernel. Otherwise, CnE> is used +if there is only one package and CnE-coreEmE> if there is +more than one, where I is the n-th core of package I. =back @@ -8170,6 +8513,12 @@ Synopsis: CollectVCL false CollectVSM false CollectWorkers false + CollectLock false + CollectMempool false + CollectManagement false + CollectSMF false + CollectVBE false + CollectMSE false @@ -8245,8 +8594,9 @@ log messages which is flushed to disk when full. True by default. =item B B|B malloc or umem (umem_alloc(3MALLOC) based) storage statistics. The umem storage -component is Solaris specific. Only available with Varnish 2.x. False by -default. +component is Solaris specific. Note: SMA, SMF and MSE share counters, enable +only the one used by the Varnish instance. Only available with Varnish 2.x. +False by default. =item B B|B @@ -8255,7 +8605,8 @@ component is used internally only. False by default. =item B B|B -file (memory mapped file) storage statistics. Only available with Varnish 2.x. +file (memory mapped file) storage statistics. Only available with Varnish 2.x., +in varnish 4.x. use CollectSMF. False by default. =item B B|B @@ -8286,6 +8637,35 @@ statistics subsystems). Only available with Varnish 4.x. False by default. Collect statistics about worker threads. False by default. +=item B B|B + +Backend counters. Only available with Varnish 4.x. False by default. + +=item B B|B + +file (memory mapped file) storage statistics. Only available with Varnish 4.x. +Note: SMA, SMF and MSE share counters, enable only the one used by the Varnish +instance. Used to be called SM in Varnish 2.x. False by default. + +=item B B|B + +Management process counters. Only available with Varnish 4.x. False by default. + +=item B B|B + +Lock counters. Only available with Varnish 4.x. False by default. + +=item B B|B + +Memory pool counters. Only available with Varnish 4.x. False by default. + +=item B B|B + +Varnish Massive Storage Engine 2.0 (MSE2) is an improved storage backend for +Varnish, replacing the traditional malloc and file storages. Only available +with Varnish-Plus 4.x. Note: SMA, SMF and MSE share counters, enable only the +one used by the Varnish instance. False by default. + =back =head2 Plugin C @@ -8350,7 +8730,7 @@ will be collected. =item B B|B If I is set to B, the default, then the device name -seen by the guest will be used for reporting metrics. +seen by the guest will be used for reporting metrics. This corresponds to the CtargetE> node in the XML definition of the domain. @@ -8930,6 +9310,12 @@ Sets the Cassandra ttl for the data points. Please refer to L +=item B I + +Only available for the KAIROSDB output format. + +Sets the metrics prefix I. Defaults to I. + =item B B|B Controls whether I are POSTed to this location. Defaults to B. @@ -9111,6 +9497,7 @@ Synopsis: Prefix "collectd/" Database 1 MaxSetSize -1 + MaxSetDuration -1 StoreRates true @@ -9173,6 +9560,12 @@ to C<0>. The B option limits the number of items that the I can hold. Negative values for I sets no limit, which is the default behavior. +=item B I + +The B option limits the duration of items that the +I can hold. Negative values for I sets no duration, which +is the default behavior. + =item B B|B If set to B (the default), convert counter values to rates. If set to diff --git a/src/collectd.pod b/src/collectd.pod index 8e68fc01..1dd899b8 100644 --- a/src/collectd.pod +++ b/src/collectd.pod @@ -40,11 +40,17 @@ the read callbacks once. A return code not equal to zero indicates an error. =item B<-P> Ipid-fileE> -Specify an alternative pid file. This overwrites any settings in the config +Specify an alternative pid file. This overwrites any settings in the config file. This is thought for init-scripts that require the PID-file in a certain directory to work correctly. For everyday-usage use the B config-option. +=item B<-B> + +If set, collectd will I try to create its base directory. If the base +directory does not exist, it will exit rather than trying to create the +directory. + =item B<-f> Don't fork to the background. I will also B close standard file diff --git a/src/collectdctl.c b/src/collectdctl.c index 4b0db2a7..54c8081e 100644 --- a/src/collectdctl.c +++ b/src/collectdctl.c @@ -65,7 +65,7 @@ #endif #endif /* NAN_ZERO_ZERO */ -#include "libcollectdclient/collectd/client.h" +#include "collectd/client.h" #ifndef PREFIX #define PREFIX "/opt/" PACKAGE_NAME diff --git a/src/cpu.c b/src/cpu.c index 307ae499..d48ab886 100644 --- a/src/cpu.c +++ b/src/cpu.c @@ -98,9 +98,11 @@ #define COLLECTD_CPU_STATE_INTERRUPT 5 #define COLLECTD_CPU_STATE_SOFTIRQ 6 #define COLLECTD_CPU_STATE_STEAL 7 -#define COLLECTD_CPU_STATE_IDLE 8 -#define COLLECTD_CPU_STATE_ACTIVE 9 /* sum of (!idle) */ -#define COLLECTD_CPU_STATE_MAX 10 /* #states */ +#define COLLECTD_CPU_STATE_GUEST 8 +#define COLLECTD_CPU_STATE_GUEST_NICE 9 +#define COLLECTD_CPU_STATE_IDLE 10 +#define COLLECTD_CPU_STATE_ACTIVE 11 /* sum of (!idle) */ +#define COLLECTD_CPU_STATE_MAX 12 /* #states */ #if HAVE_STATGRAB_H #include @@ -119,7 +121,7 @@ static const char *cpu_state_names[] = {"user", "system", "wait", "nice", "swap", "interrupt", "softirq", "steal", - "idle", "active"}; + "guest", "guest_nice", "idle", "active"}; #ifdef PROCESSOR_CPU_LOAD_INFO static mach_port_t port_host; @@ -193,9 +195,12 @@ static _Bool report_by_cpu = 1; static _Bool report_by_state = 1; static _Bool report_percent = 0; static _Bool report_num_cpu = 0; +static _Bool report_guest = 0; +static _Bool subtract_guest = 1; static const char *config_keys[] = {"ReportByCpu", "ReportByState", - "ReportNumCpu", "ValuesPercentage"}; + "ReportNumCpu", "ValuesPercentage", + "ReportGuestState", "SubtractGuestState"}; static int config_keys_num = STATIC_ARRAY_SIZE(config_keys); static int cpu_config(char const *key, char const *value) /* {{{ */ @@ -208,6 +213,10 @@ static int cpu_config(char const *key, char const *value) /* {{{ */ report_by_state = IS_TRUE(value) ? 1 : 0; else if (strcasecmp(key, "ReportNumCpu") == 0) report_num_cpu = IS_TRUE(value) ? 1 : 0; + else if (strcasecmp(key, "ReportGuestState") == 0) + report_guest = IS_TRUE(value) ? 1 : 0; + else if (strcasecmp(key, "SubtractGuestState") == 0) + subtract_guest = IS_TRUE(value) ? 1 : 0; else return -1; @@ -524,7 +533,7 @@ static void cpu_commit_without_aggregation(void) /* {{{ */ static void cpu_commit(void) /* {{{ */ { gauge_t global_rates[COLLECTD_CPU_STATE_MAX] = { - NAN, NAN, NAN, NAN, NAN, NAN, NAN, NAN, NAN, NAN /* Batman! */ + NAN, NAN, NAN, NAN, NAN, NAN, NAN, NAN, NAN, NAN, NAN, NAN /* Batman! */ }; if (report_num_cpu) @@ -545,7 +554,8 @@ static void cpu_commit(void) /* {{{ */ for (size_t cpu_num = 0; cpu_num < global_cpu_num; cpu_num++) { cpu_state_t *this_cpu_states = get_cpu_state(cpu_num, 0); gauge_t local_rates[COLLECTD_CPU_STATE_MAX] = {NAN, NAN, NAN, NAN, NAN, - NAN, NAN, NAN, NAN, NAN}; + NAN, NAN, NAN, NAN, NAN, + NAN, NAN }; for (size_t state = 0; state < COLLECTD_CPU_STATE_MAX; state++) if (this_cpu_states[state].has_value) @@ -632,7 +642,7 @@ static int cpu_read(void) { FILE *fh; char buf[1024]; - char *fields[9]; + char *fields[11]; int numfields; if ((fh = fopen("/proc/stat", "r")) == NULL) { @@ -648,14 +658,15 @@ static int cpu_read(void) { if ((buf[3] < '0') || (buf[3] > '9')) continue; - numfields = strsplit(buf, fields, 9); + numfields = strsplit(buf, fields, STATIC_ARRAY_SIZE(fields)); if (numfields < 5) continue; cpu = atoi(fields[0] + 3); - cpu_stage(cpu, COLLECTD_CPU_STATE_USER, (derive_t)atoll(fields[1]), now); - cpu_stage(cpu, COLLECTD_CPU_STATE_NICE, (derive_t)atoll(fields[2]), now); + /* Do not stage User and Nice immediately: we may need to alter them later: */ + long long user_value = atoll(fields[1]); + long long nice_value = atoll(fields[2]); cpu_stage(cpu, COLLECTD_CPU_STATE_SYSTEM, (derive_t)atoll(fields[3]), now); cpu_stage(cpu, COLLECTD_CPU_STATE_IDLE, (derive_t)atoll(fields[4]), now); @@ -665,11 +676,40 @@ static int cpu_read(void) { now); cpu_stage(cpu, COLLECTD_CPU_STATE_SOFTIRQ, (derive_t)atoll(fields[7]), now); + } - if (numfields >= 9) - cpu_stage(cpu, COLLECTD_CPU_STATE_STEAL, (derive_t)atoll(fields[8]), - now); + if (numfields >= 9) { /* Steal (since Linux 2.6.11) */ + cpu_stage(cpu, COLLECTD_CPU_STATE_STEAL, (derive_t)atoll(fields[8]), now); } + + if (numfields >= 10) { /* Guest (since Linux 2.6.24) */ + if (report_guest) { + long long value = atoll(fields[9]); + cpu_stage(cpu, COLLECTD_CPU_STATE_GUEST, (derive_t)value, now); + /* Guest is included in User; optionally subtract Guest from User: */ + if (subtract_guest) { + user_value -= value; + if (user_value < 0) user_value = 0; + } + } + } + + if (numfields >= 11) { /* Guest_nice (since Linux 2.6.33) */ + if (report_guest) { + long long value = atoll(fields[10]); + cpu_stage(cpu, COLLECTD_CPU_STATE_GUEST_NICE, (derive_t)value, now); + /* Guest_nice is included in Nice; optionally subtract Guest_nice from + Nice: */ + if (subtract_guest) { + nice_value -= value; + if (nice_value < 0) nice_value = 0; + } + } + } + + /* Eventually stage User and Nice: */ + cpu_stage(cpu, COLLECTD_CPU_STATE_USER, (derive_t)user_value, now); + cpu_stage(cpu, COLLECTD_CPU_STATE_NICE, (derive_t)nice_value, now); } fclose(fh); /* }}} #endif defined(KERNEL_LINUX) */ diff --git a/src/cpython.h b/src/cpython.h index 69a4298d..38951c0f 100644 --- a/src/cpython.h +++ b/src/cpython.h @@ -181,6 +181,7 @@ extern PyTypeObject ValuesType; typedef struct { PluginData data; + PyObject *meta; /* dict */ int severity; char message[NOTIF_MAX_MSG_LEN]; } Notification; diff --git a/src/curl.c b/src/curl.c index cc48c774..35ec1f83 100644 --- a/src/curl.c +++ b/src/curl.c @@ -53,6 +53,7 @@ struct web_page_s; typedef struct web_page_s web_page_t; struct web_page_s /* {{{ */ { + char *plugin_name; char *instance; char *url; @@ -146,6 +147,7 @@ static void cc_web_page_free(web_page_t *wp) /* {{{ */ curl_easy_cleanup(wp->curl); wp->curl = NULL; + sfree(wp->plugin_name); sfree(wp->instance); sfree(wp->url); @@ -412,6 +414,7 @@ static int cc_config_add_page(oconfig_item_t *ci) /* {{{ */ ERROR("curl plugin: calloc failed."); return -1; } + page->plugin_name = NULL; page->url = NULL; page->user = NULL; page->pass = NULL; @@ -435,7 +438,9 @@ static int cc_config_add_page(oconfig_item_t *ci) /* {{{ */ for (int i = 0; i < ci->children_num; i++) { oconfig_item_t *child = ci->children + i; - if (strcasecmp("URL", child->key) == 0) + if (strcasecmp("Plugin", child->key) == 0) + status = cf_util_get_string(child, &page->plugin_name); + else if (strcasecmp("URL", child->key) == 0) status = cf_util_get_string(child, &page->url); else if (strcasecmp("User", child->key) == 0) status = cf_util_get_string(child, &page->user); @@ -566,7 +571,8 @@ static void cc_submit(const web_page_t *wp, const web_match_t *wm, /* {{{ */ vl.values = &value; vl.values_len = 1; - sstrncpy(vl.plugin, "curl", sizeof(vl.plugin)); + sstrncpy(vl.plugin, (wp->plugin_name != NULL) ? wp->plugin_name : "curl", + sizeof(vl.plugin)); sstrncpy(vl.plugin_instance, wp->instance, sizeof(vl.plugin_instance)); sstrncpy(vl.type, wm->type, sizeof(vl.type)); if (wm->instance != NULL) @@ -581,7 +587,8 @@ static void cc_submit_response_code(const web_page_t *wp, long code) /* {{{ */ vl.values = &(value_t){.gauge = (gauge_t)code}; vl.values_len = 1; - sstrncpy(vl.plugin, "curl", sizeof(vl.plugin)); + sstrncpy(vl.plugin, (wp->plugin_name != NULL) ? wp->plugin_name : "curl", + sizeof(vl.plugin)); sstrncpy(vl.plugin_instance, wp->instance, sizeof(vl.plugin_instance)); sstrncpy(vl.type, "response_code", sizeof(vl.type)); @@ -594,7 +601,8 @@ static void cc_submit_response_time(const web_page_t *wp, /* {{{ */ vl.values = &(value_t){.gauge = response_time}; vl.values_len = 1; - sstrncpy(vl.plugin, "curl", sizeof(vl.plugin)); + sstrncpy(vl.plugin, (wp->plugin_name != NULL) ? wp->plugin_name : "curl", + sizeof(vl.plugin)); sstrncpy(vl.plugin_instance, wp->instance, sizeof(vl.plugin_instance)); sstrncpy(vl.type, "response_time", sizeof(vl.type)); @@ -623,7 +631,7 @@ static int cc_read_page(web_page_t *wp) /* {{{ */ if (wp->response_time) cc_submit_response_time(wp, CDTIME_T_TO_DOUBLE(cdtime() - start)); if (wp->stats != NULL) - curl_stats_dispatch(wp->stats, wp->curl, hostname_g, "curl", wp->instance); + curl_stats_dispatch(wp->stats, wp->curl, NULL, "curl", wp->instance); if (wp->response_code) { long response_code = 0; diff --git a/src/curl_json.c b/src/curl_json.c index 35b11c02..a2f287a8 100644 --- a/src/curl_json.c +++ b/src/curl_json.c @@ -82,6 +82,7 @@ typedef struct { struct cj_s /* {{{ */ { char *instance; + char *plugin_name; char *host; char *sock; @@ -225,11 +226,6 @@ static void cj_advance_array(cj_t *db) { #define CJ_CB_ABORT 0 #define CJ_CB_CONTINUE 1 -static int cj_cb_boolean(void *ctx, int boolVal) { - cj_advance_array(ctx); - return CJ_CB_CONTINUE; -} - static int cj_cb_null(void *ctx) { cj_advance_array(ctx); return CJ_CB_CONTINUE; @@ -291,6 +287,13 @@ static int cj_cb_string(void *ctx, const unsigned char *val, yajl_len_t len) { return cj_cb_number(ctx, (const char *)val, len); } /* int cj_cb_string */ +static int cj_cb_boolean(void *ctx, int boolVal) { + if (boolVal) + return cj_cb_number(ctx, "1", 1); + else + return cj_cb_number(ctx, "0", 1); +} /* int cj_cb_boolean */ + static int cj_cb_end(void *ctx) { cj_t *db = (cj_t *)ctx; memset(&db->state[db->depth], 0, sizeof(db->state[db->depth])); @@ -396,6 +399,7 @@ static void cj_free(void *arg) /* {{{ */ db->tree = NULL; sfree(db->instance); + sfree(db->plugin_name); sfree(db->host); sfree(db->sock); @@ -672,6 +676,8 @@ static int cj_config_add_url(oconfig_item_t *ci) /* {{{ */ if (strcasecmp("Instance", child->key) == 0) status = cf_util_get_string(child, &db->instance); + else if (strcasecmp("Plugin", child->key) == 0) + status = cf_util_get_string(child, &db->plugin_name); else if (strcasecmp("Host", child->key) == 0) status = cf_util_get_string(child, &db->host); else if (db->url && strcasecmp("User", child->key) == 0) @@ -805,7 +811,8 @@ static void cj_submit_impl(cj_t *db, cj_key_t *key, value_t *value) /* {{{ */ sstrncpy(vl.type_instance, key->instance, sizeof(vl.type_instance)); sstrncpy(vl.host, cj_host(db), sizeof(vl.host)); - sstrncpy(vl.plugin, "curl_json", sizeof(vl.plugin)); + sstrncpy(vl.plugin, (db->plugin_name != NULL) ? db->plugin_name : "curl_json", + sizeof(vl.plugin)); sstrncpy(vl.plugin_instance, db->instance, sizeof(vl.plugin_instance)); sstrncpy(vl.type, key->type, sizeof(vl.type)); diff --git a/src/curl_xml.c b/src/curl_xml.c index e83de73e..c99e3f1d 100644 --- a/src/curl_xml.c +++ b/src/curl_xml.c @@ -54,6 +54,7 @@ struct cx_xpath_s /* {{{ */ size_t values_len; char *instance_prefix; char *instance; + char *plugin_instance_from; int is_table; unsigned long magic; }; @@ -71,6 +72,7 @@ typedef struct cx_namespace_s cx_namespace_t; struct cx_s /* {{{ */ { char *instance; + char *plugin_name; char *host; char *url; @@ -95,7 +97,7 @@ struct cx_s /* {{{ */ size_t buffer_size; size_t buffer_fill; - llist_t *list; /* list of xpath blocks */ + llist_t *xpath_list; /* list of xpath blocks */ }; typedef struct cx_s cx_t; /* }}} */ @@ -105,9 +107,8 @@ typedef struct cx_s cx_t; /* }}} */ 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; + cx_t *db = user_data; if (db == NULL) { ERROR("curl_xml plugin: cx_curl_callback: " "user_data pointer is NULL."); @@ -118,9 +119,7 @@ static size_t cx_curl_callback(void *buf, /* {{{ */ return len; if ((db->buffer_fill + len) >= db->buffer_size) { - char *temp; - - temp = realloc(db->buffer, db->buffer_fill + len + 1); + char *temp = realloc(db->buffer, db->buffer_fill + len + 1); if (temp == NULL) { ERROR("curl_xml plugin: realloc failed."); return 0; @@ -144,29 +143,28 @@ static void cx_xpath_free(cx_xpath_t *xpath) /* {{{ */ sfree(xpath->path); sfree(xpath->type); sfree(xpath->instance_prefix); + sfree(xpath->plugin_instance_from); sfree(xpath->instance); sfree(xpath->values); sfree(xpath); } /* }}} void cx_xpath_free */ -static void cx_list_free(llist_t *list) /* {{{ */ +static void cx_xpath_list_free(llist_t *list) /* {{{ */ { llentry_t *le; le = llist_head(list); while (le != NULL) { - llentry_t *le_next; + llentry_t *le_next = le->next; - le_next = le->next; - - sfree(le->key); + /* this also frees xpath->path used for le->key */ cx_xpath_free(le->value); le = le_next; } llist_destroy(list); -} /* }}} void cx_list_free */ +} /* }}} void cx_xpath_list_free */ static void cx_free(void *arg) /* {{{ */ { @@ -183,11 +181,12 @@ static void cx_free(void *arg) /* {{{ */ curl_easy_cleanup(db->curl); db->curl = NULL; - if (db->list != NULL) - cx_list_free(db->list); + if (db->xpath_list != NULL) + cx_xpath_list_free(db->xpath_list); sfree(db->buffer); sfree(db->instance); + sfree(db->plugin_name); sfree(db->host); sfree(db->url); @@ -208,7 +207,7 @@ static void cx_free(void *arg) /* {{{ */ sfree(db); } /* }}} void cx_free */ -static const char *cx_host(cx_t *db) /* {{{ */ +static const char *cx_host(const cx_t *db) /* {{{ */ { if (db->host == NULL) return hostname_g; @@ -250,13 +249,11 @@ static int cx_check_type(const data_set_t *ds, cx_xpath_t *xpath) /* {{{ */ 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); +static xmlXPathObjectPtr cx_evaluate_xpath(xmlXPathContextPtr xpath_ctx, + char *expr) /* {{{ */ +{ + xmlXPathObjectPtr xpath_obj = + xmlXPathEvalExpression(BAD_CAST expr, xpath_ctx); if (xpath_obj == NULL) { WARNING("curl_xml plugin: " "Error unable to evaluate xpath expression \"%s\". Skipping...", @@ -279,51 +276,65 @@ static int cx_if_not_text_node(xmlNodePtr node) /* {{{ */ 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); +/* + * Returned value should be freed with xmlFree(). + */ +static char *cx_get_text_node_value(xmlXPathContextPtr xpath_ctx, /* {{{ */ + char *expr, const char *from_option) { + xmlXPathObjectPtr values_node_obj = cx_evaluate_xpath(xpath_ctx, expr); if (values_node_obj == NULL) - return -1; /* Error already logged. */ + return NULL; /* Error already logged. */ - values_node = values_node_obj->nodesetval; - tmp_size = (values_node) ? values_node->nodeNr : 0; + xmlNodeSetPtr values_node = values_node_obj->nodesetval; + size_t 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); + "relative xpath expression \"%s\" from '%s' doesn't match " + "any of the nodes.", + expr, from_option); xmlXPathFreeObject(values_node_obj); - return -1; + return NULL; } if (tmp_size > 1) { WARNING("curl_xml plugin: " - "relative xpath expression \"%s\" is expected to return " - "only one node. Skipping...", - xpath->values[index].path); + "relative xpath expression \"%s\" from '%s' is expected to return " + "only one text node. Skipping the node.", + expr, from_option); xmlXPathFreeObject(values_node_obj); - return -1; + return NULL; } /* 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); + "relative xpath expression \"%s\" from '%s' is expected to return " + "only text/attribute node which is not the case. " + "Skipping the node.", + expr, from_option); xmlXPathFreeObject(values_node_obj); - return -1; + return NULL; } - node_value = (char *)xmlNodeGetContent(values_node->nodeTab[0]); + char *node_value = (char *)xmlNodeGetContent(values_node->nodeTab[0]); + + /* free up object */ + xmlXPathFreeObject(values_node_obj); + + return node_value; +} /* }}} char * cx_get_text_node_value */ + +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) { + + char *node_value = cx_get_text_node_value( + xpath_ctx, xpath->values[index].path, "ValuesFrom"); + + if (node_value == NULL) + return -1; + switch (ds->ds[index].type) { case DS_TYPE_COUNTER: vl->values[index].counter = @@ -345,9 +356,7 @@ static int cx_handle_single_value_xpath(xmlXPathContextPtr xpath_ctx, /* {{{ */ /* endptr = */ NULL); } - /* free up object */ - xmlXPathFreeObject(values_node_obj); - sfree(node_value); + xmlFree(node_value); /* We have reached here which means that * we have got something to work */ @@ -358,7 +367,6 @@ 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; assert(xpath->values_len > 0); assert(xpath->values_len == vl->values_len); @@ -366,8 +374,7 @@ static int cx_handle_all_value_xpaths(xmlXPathContextPtr xpath_ctx, /* {{{ */ vl->values = values; for (size_t i = 0; i < xpath->values_len; i++) { - status = cx_handle_single_value_xpath(xpath_ctx, xpath, ds, vl, i); - if (status != 0) + if (cx_handle_single_value_xpath(xpath_ctx, xpath, ds, vl, i) != 0) return -1; /* An error has been printed. */ } /* for (i = 0; i < xpath->values_len; i++) */ @@ -378,150 +385,96 @@ static int cx_handle_all_value_xpaths(xmlXPathContextPtr xpath_ctx, /* {{{ */ } /* }}} 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 `Instance' option is not optional in this case. Check for the - * condition and inform the user. */ - if (is_table && (xpath->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; - } + cx_xpath_t *xpath, value_list_t *vl) { - /* instance has to be an xpath expression */ + /* Handle type instance */ 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) { - 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); + char *node_value = + cx_get_text_node_value(xpath_ctx, xpath->instance, "InstanceFrom"); + if (node_value == NULL) 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) { - char *node_value = (char *)xmlNodeGetContent(instance_node->nodeTab[0]); + if (xpath->instance_prefix != NULL) snprintf(vl->type_instance, sizeof(vl->type_instance), "%s%s", xpath->instance_prefix, node_value); - sfree(node_value); - } 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) { - char *node_value = (char *)xmlNodeGetContent(instance_node->nodeTab[0]); + else sstrncpy(vl->type_instance, node_value, sizeof(vl->type_instance)); - sfree(node_value); - } - } - /* Free `instance_node_obj' this late, because `instance_node' points to - * somewhere inside this structure. */ - xmlXPathFreeObject(instance_node_obj); + xmlFree(node_value); + } else if (xpath->instance_prefix != NULL) + sstrncpy(vl->type_instance, xpath->instance_prefix, + sizeof(vl->type_instance)); + + /* Handle plugin instance */ + if (xpath->plugin_instance_from != NULL) { + char *node_value = cx_get_text_node_value( + xpath_ctx, xpath->plugin_instance_from, "PluginInstanceFrom"); + + if (node_value == NULL) + return -1; + + sstrncpy(vl->plugin_instance, node_value, sizeof(vl->plugin_instance)); + xmlFree(node_value); + } return 0; } /* }}} int cx_handle_instance_xpath */ -static int cx_handle_base_xpath(char const *plugin_instance, /* {{{ */ - char const *host, xmlXPathContextPtr xpath_ctx, - const data_set_t *ds, char *base_xpath, - cx_xpath_t *xpath) { - int total_nodes; +static int cx_handle_xpath(const cx_t *db, /* {{{ */ + xmlXPathContextPtr xpath_ctx, cx_xpath_t *xpath) { - xmlXPathObjectPtr base_node_obj = NULL; - xmlNodeSetPtr base_nodes = NULL; - - value_list_t vl = VALUE_LIST_INIT; + const data_set_t *ds = plugin_get_ds(xpath->type); + if (cx_check_type(ds, xpath) != 0) + return -1; - base_node_obj = cx_evaluate_xpath(xpath_ctx, BAD_CAST base_xpath); + xmlXPathObjectPtr base_node_obj = cx_evaluate_xpath(xpath_ctx, xpath->path); 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; + xmlNodeSetPtr base_nodes = base_node_obj->nodesetval; + int 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); + xpath->path); 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) { + /* InstanceFrom or PluginInstanceFrom in the xpath block is required */ + if (total_nodes > 1 && xpath->instance == NULL && + xpath->plugin_instance_from == NULL) { ERROR("curl_xml plugin: " - "InstanceFrom is must in xpath block since the base xpath expression " - "\"%s\" " + "InstanceFrom or PluginInstanceFrom is must in xpath block " + "since the base xpath expression \"%s\" " "returned multiple results. Skipping the xpath block...", - base_xpath); + xpath->path); + xmlXPathFreeObject(base_node_obj); return -1; } + value_list_t vl = VALUE_LIST_INIT; + /* 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, host, sizeof(vl.host)); - if (plugin_instance != NULL) - sstrncpy(vl.plugin_instance, plugin_instance, sizeof(vl.plugin_instance)); + sstrncpy(vl.plugin, (db->plugin_name != NULL) ? db->plugin_name : "curl_xml", + sizeof(vl.plugin)); + sstrncpy(vl.host, cx_host(db), sizeof(vl.host)); for (int 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) + if (db->instance != NULL) + sstrncpy(vl.plugin_instance, db->instance, sizeof(vl.plugin_instance)); + + if (cx_handle_instance_xpath(xpath_ctx, xpath, &vl) != 0) continue; /* An error has already been reported. */ - status = cx_handle_all_value_xpaths(xpath_ctx, xpath, ds, &vl); - if (status != 0) + if (cx_handle_all_value_xpaths(xpath_ctx, xpath, ds, &vl) != 0) continue; /* An error has been logged. */ } /* for (i = 0; i < total_nodes; i++) */ @@ -529,24 +482,17 @@ static int cx_handle_base_xpath(char const *plugin_instance, /* {{{ */ xmlXPathFreeObject(base_node_obj); return 0; -} /* }}} cx_handle_base_xpath */ +} /* }}} cx_handle_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; +static int cx_handle_parsed_xml(cx_t *db, xmlDocPtr doc, /* {{{ */ + xmlXPathContextPtr xpath_ctx) { int status = -1; - le = llist_head(db->list); + llentry_t *le = llist_head(db->xpath_list); while (le != NULL) { - /* get the ds */ - xpath = (cx_xpath_t *)le->value; - ds = plugin_get_ds(xpath->type); + cx_xpath_t *xpath = (cx_xpath_t *)le->value; - if ((cx_check_type(ds, xpath) == 0) && - (cx_handle_base_xpath(db->instance, cx_host(db), xpath_ctx, ds, le->key, - xpath) == 0)) + if (cx_handle_xpath(db, xpath_ctx, xpath) == 0) status = 0; /* we got atleast one success */ le = le->next; @@ -555,20 +501,16 @@ static int cx_handle_parsed_xml(xmlDocPtr doc, /* {{{ */ return status; } /* }}} cx_handle_parsed_xml */ -static int cx_parse_stats_xml(xmlChar *xml, cx_t *db) /* {{{ */ +static int cx_parse_xml(cx_t *db, char *xml) /* {{{ */ { - int status; - xmlDocPtr doc; - xmlXPathContextPtr xpath_ctx; - /* Load the XML */ - doc = xmlParseDoc(xml); + xmlDocPtr doc = xmlParseDoc(BAD_CAST xml); if (doc == NULL) { ERROR("curl_xml plugin: Failed to parse the xml document - %s", xml); return -1; } - xpath_ctx = xmlXPathNewContext(doc); + xmlXPathContextPtr xpath_ctx = xmlXPathNewContext(doc); if (xpath_ctx == NULL) { ERROR("curl_xml plugin: Failed to create the xml context"); xmlFreeDoc(doc); @@ -577,7 +519,7 @@ static int cx_parse_stats_xml(xmlChar *xml, cx_t *db) /* {{{ */ for (size_t i = 0; i < db->namespaces_num; i++) { cx_namespace_t const *ns = db->namespaces + i; - status = + int status = xmlXPathRegisterNs(xpath_ctx, BAD_CAST ns->prefix, BAD_CAST ns->url); if (status != 0) { ERROR("curl_xml plugin: " @@ -589,25 +531,29 @@ static int cx_parse_stats_xml(xmlChar *xml, cx_t *db) /* {{{ */ } } - status = cx_handle_parsed_xml(doc, xpath_ctx, db); + int status = cx_handle_parsed_xml(db, doc, xpath_ctx); /* Cleanup */ xmlXPathFreeContext(xpath_ctx); xmlFreeDoc(doc); return status; -} /* }}} cx_parse_stats_xml */ +} /* }}} cx_parse_xml */ -static int cx_curl_perform(cx_t *db, CURL *curl) /* {{{ */ +static int cx_read(user_data_t *ud) /* {{{ */ { - int status; + if ((ud == NULL) || (ud->data == NULL)) { + ERROR("curl_xml plugin: cx_read: Invalid user data."); + return -1; + } + long rc; - char *ptr; char *url; + cx_t *db = (cx_t *)ud->data; db->buffer_fill = 0; curl_easy_setopt(db->curl, CURLOPT_URL, db->url); - status = curl_easy_perform(curl); + int status = curl_easy_perform(db->curl); if (status != CURLE_OK) { ERROR("curl_xml plugin: curl_easy_perform failed with status %i: %s (%s)", status, db->curl_errbuf, db->url); @@ -617,8 +563,8 @@ static int cx_curl_perform(cx_t *db, CURL *curl) /* {{{ */ curl_stats_dispatch(db->stats, db->curl, cx_host(db), "curl_xml", db->instance); - curl_easy_getinfo(curl, CURLINFO_EFFECTIVE_URL, &url); - curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &rc); + curl_easy_getinfo(db->curl, CURLINFO_EFFECTIVE_URL, &url); + curl_easy_getinfo(db->curl, CURLINFO_RESPONSE_CODE, &rc); /* The response code is zero if a non-HTTP transport was used. */ if ((rc != 0) && (rc != 200)) { @@ -628,26 +574,10 @@ static int cx_curl_perform(cx_t *db, CURL *curl) /* {{{ */ return -1; } - ptr = db->buffer; - - status = cx_parse_stats_xml(BAD_CAST ptr, db); + status = cx_parse_xml(db, db->buffer); 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 {{{ */ @@ -685,18 +615,13 @@ static int cx_config_add_values(const char *name, cx_xpath_t *xpath, /* {{{ */ static int cx_config_add_xpath(cx_t *db, oconfig_item_t *ci) /* {{{ */ { - cx_xpath_t *xpath; - char *name; - llentry_t *le; - int status; - - xpath = calloc(1, sizeof(*xpath)); + cx_xpath_t *xpath = calloc(1, sizeof(*xpath)); if (xpath == NULL) { ERROR("curl_xml plugin: calloc failed."); return -1; } - status = cf_util_get_string(ci, &xpath->path); + int status = cf_util_get_string(ci, &xpath->path); if (status != 0) { cx_xpath_free(xpath); return status; @@ -720,6 +645,8 @@ static int cx_config_add_xpath(cx_t *db, oconfig_item_t *ci) /* {{{ */ 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("PluginInstanceFrom", child->key) == 0) + status = cf_util_get_string(child, &xpath->plugin_instance_from); else if (strcasecmp("ValuesFrom", child->key) == 0) status = cx_config_add_values("ValuesFrom", xpath, child); else { @@ -742,37 +669,25 @@ static int cx_config_add_xpath(cx_t *db, oconfig_item_t *ci) /* {{{ */ return -1; } - if (db->list == NULL) { - db->list = llist_create(); - if (db->list == NULL) { - ERROR("curl_xml plugin: list creation failed."); - cx_xpath_free(xpath); - return -1; - } - } - - name = strdup(xpath->path); - if (name == NULL) { - ERROR("curl_xml plugin: strdup failed."); + if (xpath->values_len == 0) { + WARNING("curl_xml plugin: `ValuesFrom' missing in `xpath' block."); cx_xpath_free(xpath); return -1; } - le = llentry_create(name, xpath); + llentry_t *le = llentry_create(xpath->path, xpath); if (le == NULL) { ERROR("curl_xml plugin: llentry_create failed."); cx_xpath_free(xpath); - sfree(name); return -1; } - llist_append(db->list, le); + llist_append(db->xpath_list, le); return 0; } /* }}} int cx_config_add_xpath */ static int cx_config_add_namespace(cx_t *db, /* {{{ */ oconfig_item_t *ci) { - cx_namespace_t *ns; if ((ci->values_num != 2) || (ci->values[0].type != OCONFIG_TYPE_STRING) || (ci->values[1].type != OCONFIG_TYPE_STRING)) { @@ -781,8 +696,8 @@ static int cx_config_add_namespace(cx_t *db, /* {{{ */ return EINVAL; } - ns = realloc(db->namespaces, - sizeof(*db->namespaces) * (db->namespaces_num + 1)); + cx_namespace_t *ns = realloc( + db->namespaces, sizeof(*db->namespaces) * (db->namespaces_num + 1)); if (ns == NULL) { ERROR("curl_xml plugin: realloc failed."); return ENOMEM; @@ -871,43 +786,51 @@ static int cx_init_curl(cx_t *db) /* {{{ */ static int cx_config_add_url(oconfig_item_t *ci) /* {{{ */ { - cx_t *db; - int status = 0; - 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 = calloc(1, sizeof(*db)); + cx_t *db = calloc(1, sizeof(*db)); if (db == NULL) { ERROR("curl_xml plugin: calloc failed."); return -1; } - db->timeout = -1; + db->instance = strdup("default"); + if (db->instance == NULL) { + ERROR("curl_xml plugin: strdup failed."); + sfree(db); + return -1; + } - 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); - cx_free(db); + db->xpath_list = llist_create(); + if (db->xpath_list == NULL) { + ERROR("curl_xml plugin: list creation failed."); + sfree(db->instance); + sfree(db); return -1; } + db->timeout = -1; + + int status = cf_util_get_string(ci, &db->url); + if (status != 0) { + llist_destroy(db->xpath_list); + sfree(db->instance); + sfree(db); + return status; + } + /* Fill the `cx_t' structure.. */ for (int 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("Plugin", child->key) == 0) + status = cf_util_get_string(child, &db->plugin_name); else if (strcasecmp("Host", child->key) == 0) status = cf_util_get_string(child, &db->host); else if (strcasecmp("User", child->key) == 0) @@ -945,39 +868,34 @@ static int cx_config_add_url(oconfig_item_t *ci) /* {{{ */ 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 (status != 0) { + cx_free(db); + return status; } - /* If all went well, register this database for reading */ - if (status == 0) { - char *cb_name; - - if (db->instance == NULL) - db->instance = strdup("default"); - - DEBUG("curl_xml plugin: Registering new read callback: %s", db->instance); - - cb_name = ssnprintf_alloc("curl_xml-%s-%s", db->instance, db->url); + if (llist_size(db->xpath_list) == 0) { + WARNING("curl_xml plugin: No `xpath' block within `URL' block `%s'.", + db->url); + cx_free(db); + return -1; + } - plugin_register_complex_read(/* group = */ "curl_xml", cb_name, cx_read, - /* interval = */ 0, - &(user_data_t){ - .data = db, .free_func = cx_free, - }); - sfree(cb_name); - } else { + if (cx_init_curl(db) != 0) { cx_free(db); return -1; } + /* If all went well, register this database for reading */ + DEBUG("curl_xml plugin: Registering new read callback: %s", db->instance); + + char *cb_name = ssnprintf_alloc("curl_xml-%s-%s", db->instance, db->url); + + plugin_register_complex_read(/* group = */ "curl_xml", cb_name, cx_read, + /* interval = */ 0, + &(user_data_t){ + .data = db, .free_func = cx_free, + }); + sfree(cb_name); return 0; } /* }}} int cx_config_add_url */ @@ -985,19 +903,14 @@ static int cx_config_add_url(oconfig_item_t *ci) /* {{{ */ static int cx_config(oconfig_item_t *ci) /* {{{ */ { - int success; - int errors; - int status; - - success = 0; - errors = 0; + int success = 0; + int errors = 0; for (int 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) + if (cx_config_add_url(child) == 0) success++; else errors++; diff --git a/src/daemon/collectd.c b/src/daemon/collectd.c index 2edfa377..dd9b12f8 100644 --- a/src/daemon/collectd.c +++ b/src/daemon/collectd.c @@ -47,16 +47,6 @@ #define COLLECTD_LOCALE "C" #endif -/* - * Global variables - */ -char hostname_g[DATA_MAX_NAME_LEN]; -cdtime_t interval_g; -int timeout_g; -#if HAVE_LIBKSTAT -kstat_ctl_t *kc; -#endif /* HAVE_LIBKSTAT */ - static int loop = 0; static void *do_flush(void __attribute__((unused)) * arg) { @@ -91,13 +81,19 @@ static int init_hostname(void) { struct addrinfo *ai_list; int status; + long hostname_len = sysconf(_SC_HOST_NAME_MAX); + if (hostname_len == -1) { + hostname_len = NI_MAXHOST; + } + char hostname[hostname_len]; + str = global_option_get("Hostname"); if ((str != NULL) && (str[0] != 0)) { - sstrncpy(hostname_g, str, sizeof(hostname_g)); + hostname_set(str); return 0; } - if (gethostname(hostname_g, sizeof(hostname_g)) != 0) { + if (gethostname(hostname, hostname_len) != 0) { fprintf(stderr, "`gethostname' failed and no " "hostname was configured.\n"); return -1; @@ -109,14 +105,14 @@ static int init_hostname(void) { struct addrinfo ai_hints = {.ai_flags = AI_CANONNAME}; - status = getaddrinfo(hostname_g, NULL, &ai_hints, &ai_list); + status = getaddrinfo(hostname, NULL, &ai_hints, &ai_list); if (status != 0) { ERROR("Looking up \"%s\" failed. You have set the " "\"FQDNLookup\" option, but I cannot resolve " "my hostname to a fully qualified domain " "name. Please fix the network " "configuration.", - hostname_g); + hostname); return -1; } @@ -125,7 +121,7 @@ static int init_hostname(void) { if (ai_ptr->ai_canonname == NULL) continue; - sstrncpy(hostname_g, ai_ptr->ai_canonname, sizeof(hostname_g)); + hostname_set(ai_ptr->ai_canonname); break; } @@ -158,7 +154,7 @@ static int init_global_variables(void) { return 0; } /* int init_global_variables */ -static int change_basedir(const char *orig_dir) { +static int change_basedir(const char *orig_dir, _Bool create) { char *dir; size_t dirlen; int status; @@ -183,7 +179,7 @@ static int change_basedir(const char *orig_dir) { if (status == 0) { free(dir); return 0; - } else if (errno != ENOENT) { + } else if (!create || (errno != ENOENT)) { char errbuf[1024]; ERROR("change_basedir: chdir (%s): %s", dir, sstrerror(errno, errbuf, sizeof(errbuf))); @@ -250,6 +246,7 @@ __attribute__((noreturn)) static void exit_usage(int status) { #if COLLECT_DAEMON " -f Don't fork to the background.\n" #endif + " -B Don't create the BaseDir\n" " -h Display help (this message)\n" "\nBuiltin defaults:\n" " Config file " CONFIGFILE "\n" @@ -454,21 +451,18 @@ static int notify_systemd(void) { } #endif /* KERNEL_LINUX */ -int main(int argc, char **argv) { - const char *configfile = CONFIGFILE; - int test_config = 0; - int test_readall = 0; - const char *basedir; -#if COLLECT_DAEMON - pid_t pid; - int daemonize = 1; -#endif - int exit_status = 0; +struct cmdline_config { + _Bool test_config; + _Bool test_readall; + _Bool create_basedir; + const char *configfile; + _Bool daemonize; +}; +void read_cmdline(int argc, char **argv, struct cmdline_config *config) { /* read options */ while (1) { int c; - c = getopt(argc, argv, "htTC:" #if COLLECT_DAEMON "fP:" @@ -479,17 +473,20 @@ int main(int argc, char **argv) { break; switch (c) { + case 'B': + config->create_basedir = 0; + break; case 'C': - configfile = optarg; + config->configfile = optarg; break; case 't': - test_config = 1; + config->test_config = 1; break; case 'T': - test_readall = 1; + config->test_readall = 1; global_option_set("ReadThreads", "-1", 1); #if COLLECT_DAEMON - daemonize = 0; + config->daemonize = 0; #endif /* COLLECT_DAEMON */ break; #if COLLECT_DAEMON @@ -497,7 +494,7 @@ int main(int argc, char **argv) { global_option_set("PIDFile", optarg, 1); break; case 'f': - daemonize = 0; + config->daemonize = 0; break; #endif /* COLLECT_DAEMON */ case 'h': @@ -507,19 +504,17 @@ int main(int argc, char **argv) { exit_usage(1); } /* switch (c) */ } /* while (1) */ +} - if (optind < argc) - exit_usage(1); - - plugin_init_ctx(); - +int configure_collectd(struct cmdline_config *config) { + const char *basedir; /* * Read options from the config file, the environment and the command * line (in that order, with later options overwriting previous ones in * general). * Also, this will automatically load modules. */ - if (cf_read(configfile)) { + if (cf_read(config->configfile)) { fprintf(stderr, "Error: Reading the config file failed!\n" "Read the logs for details.\n"); return 1; @@ -533,23 +528,47 @@ int main(int argc, char **argv) { fprintf(stderr, "Don't have a basedir to use. This should not happen. Ever."); return 1; - } else if (change_basedir(basedir)) { + } else if (change_basedir(basedir, config->create_basedir)) { fprintf(stderr, "Error: Unable to change to directory `%s'.\n", basedir); return 1; } /* - * Set global variables or, if that failes, exit. We cannot run with + * Set global variables or, if that fails, exit. We cannot run with * them being uninitialized. If nothing is configured, then defaults * are being used. So this means that the user has actually done * something wrong. */ if (init_global_variables() != 0) - exit(EXIT_FAILURE); + return 1; + + return 0; +} + +int main(int argc, char **argv) { +#if COLLECT_DAEMON + pid_t pid; +#endif + int exit_status = 0; - if (test_config) + struct cmdline_config config = { + .daemonize = 1, .create_basedir = 1, .configfile = CONFIGFILE, + }; + + read_cmdline(argc, argv, &config); + + if (config.test_config) return 0; + if (optind < argc) + exit_usage(1); + + plugin_init_ctx(); + + int status; + if ((status = configure_collectd(&config)) != 0) + exit(EXIT_FAILURE); + #if COLLECT_DAEMON /* * fork off child @@ -562,7 +581,7 @@ int main(int argc, char **argv) { * Only daemonize if we're not being supervised * by upstart or systemd (when using Linux). */ - if (daemonize + if (config.daemonize #ifdef KERNEL_LINUX && notify_upstart() == 0 && notify_systemd() == 0 #endif @@ -612,7 +631,7 @@ int main(int argc, char **argv) { status); return 1; } - } /* if (daemonize) */ + } /* if (config.daemonize) */ #endif /* COLLECT_DAEMON */ struct sigaction sig_pipe_action = {.sa_handler = SIG_IGN}; @@ -657,7 +676,7 @@ int main(int argc, char **argv) { exit_status = 1; } - if (test_readall) { + if (config.test_readall) { if (plugin_read_all_once() != 0) { ERROR("Error: one or more plugin read callbacks failed."); exit_status = 1; @@ -676,7 +695,7 @@ int main(int argc, char **argv) { } #if COLLECT_DAEMON - if (daemonize) + if (config.daemonize) pidfile_remove(); #endif /* COLLECT_DAEMON */ diff --git a/src/daemon/collectd.h b/src/daemon/collectd.h index 01d484ee..0558aa40 100644 --- a/src/daemon/collectd.h +++ b/src/daemon/collectd.h @@ -186,10 +186,6 @@ #include #endif -#if HAVE_KSTAT_H -#include -#endif - #ifndef PACKAGE_NAME #define PACKAGE_NAME "collectd" #endif @@ -267,11 +263,6 @@ #define GAUGE_FORMAT "%.15g" #endif -/* Type for time as used by "utils_time.h" */ -typedef uint64_t cdtime_t; - -extern char hostname_g[]; -extern cdtime_t interval_g; -extern int timeout_g; +#include "globals.h" #endif /* COLLECTD_H */ diff --git a/src/daemon/common.c b/src/daemon/common.c index 3ae61d85..cf981dc0 100644 --- a/src/daemon/common.c +++ b/src/daemon/common.c @@ -212,7 +212,7 @@ void sfree (void **ptr) } #endif -ssize_t sread(int fd, void *buf, size_t count) { +int sread(int fd, void *buf, size_t count) { char *ptr; size_t nleft; ssize_t status; @@ -230,10 +230,7 @@ ssize_t sread(int fd, void *buf, size_t count) { return status; if (status == 0) { - DEBUG("Received EOF from fd %i. " - "Closing fd and returning error.", - fd); - close(fd); + DEBUG("Received EOF from fd %i. ", fd); return -1; } @@ -246,7 +243,7 @@ ssize_t sread(int fd, void *buf, size_t count) { return 0; } -ssize_t swrite(int fd, const void *buf, size_t count) { +int swrite(int fd, const void *buf, size_t count) { const char *ptr; size_t nleft; ssize_t status; @@ -269,7 +266,8 @@ ssize_t swrite(int fd, const void *buf, size_t count) { if (recv(fd, buffer, sizeof(buffer), MSG_PEEK | MSG_DONTWAIT) == 0) { /* if recv returns zero (even though poll() said there is data to be * read), that means the connection has been closed */ - return errno ? errno : -1; + errno = ECONNRESET; + return -1; } } diff --git a/src/daemon/common.h b/src/daemon/common.h index afd292a3..7f860521 100644 --- a/src/daemon/common.h +++ b/src/daemon/common.h @@ -79,8 +79,7 @@ char *sstrerror(int errnum, char *buf, size_t buflen); * * DESCRIPTION * Reads exactly `n' bytes or fails. Syntax and other behavior is analogous - * to `read(2)'. If EOF is received the file descriptor is closed and an - * error is returned. + * to `read(2)'. * * PARAMETERS * `fd' File descriptor to write to. @@ -91,7 +90,7 @@ char *sstrerror(int errnum, char *buf, size_t buflen); * Zero upon success or non-zero if an error occurred. `errno' is set in this * case. */ -ssize_t sread(int fd, void *buf, size_t count); +int sread(int fd, void *buf, size_t count); /* * NAME @@ -110,7 +109,7 @@ ssize_t sread(int fd, void *buf, size_t count); * Zero upon success or non-zero if an error occurred. `errno' is set in this * case. */ -ssize_t swrite(int fd, const void *buf, size_t count); +int swrite(int fd, const void *buf, size_t count); /* * NAME diff --git a/src/daemon/configfile.c b/src/daemon/configfile.c index 0d295c1c..f5086ae6 100644 --- a/src/daemon/configfile.c +++ b/src/daemon/configfile.c @@ -461,9 +461,9 @@ static int cf_ci_replace_child(oconfig_item_t *dst, oconfig_item_t *src, return 0; } - temp = - realloc(dst->children, sizeof(oconfig_item_t) * - (dst->children_num + src->children_num - 1)); + temp = realloc(dst->children, + sizeof(oconfig_item_t) * + (dst->children_num + src->children_num - 1)); if (temp == NULL) { ERROR("configfile: realloc failed."); return -1; @@ -502,8 +502,9 @@ static int cf_ci_append_children(oconfig_item_t *dst, oconfig_item_t *src) { if ((src == NULL) || (src->children_num == 0)) return 0; - temp = realloc(dst->children, sizeof(oconfig_item_t) * - (dst->children_num + src->children_num)); + temp = + realloc(dst->children, + sizeof(oconfig_item_t) * (dst->children_num + src->children_num)); if (temp == NULL) { ERROR("configfile: realloc failed."); return -1; @@ -874,7 +875,8 @@ const char *global_option_get(const char *option) { return NULL; } - return (cf_global_options[i].value != NULL) ? cf_global_options[i].value : cf_global_options[i].def; + return (cf_global_options[i].value != NULL) ? cf_global_options[i].value + : cf_global_options[i].def; } /* char *global_option_get */ long global_option_get_long(const char *option, long default_value) { @@ -1031,6 +1033,7 @@ int cf_read(const char *filename) { } return ret; + } /* int cf_read */ /* Assures the config option is a string, duplicates it and returns the copy in diff --git a/src/daemon/globals.c b/src/daemon/globals.c new file mode 100644 index 00000000..5c6749ff --- /dev/null +++ b/src/daemon/globals.c @@ -0,0 +1,48 @@ +/** + * collectd - src/globals.c + * Copyright (C) 2017 Google LLC + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + **/ + +#include "common.h" +#include "globals.h" + +#if HAVE_KSTAT_H +#include +#endif + +/* + * Global variables + */ +char *hostname_g; +cdtime_t interval_g; +int timeout_g; +#if HAVE_KSTAT_H +kstat_ctl_t *kc; +#endif + +void hostname_set(char const *hostname) { + char *h = strdup(hostname); + if (h == NULL) + return; + + sfree(hostname_g); + hostname_g = h; +} diff --git a/src/daemon/globals.h b/src/daemon/globals.h new file mode 100644 index 00000000..bc11d6bf --- /dev/null +++ b/src/daemon/globals.h @@ -0,0 +1,43 @@ +/** + * collectd - src/globals.h + * Copyright (C) 2017 Google LLC + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + **/ + +#ifndef GLOBALS_H +#define GLOBALS_H + +#include + +#ifndef DATA_MAX_NAME_LEN +#define DATA_MAX_NAME_LEN 128 +#endif + +/* Type for time as used by "utils_time.h" */ +typedef uint64_t cdtime_t; + +/* hostname_set updates hostname_g */ +void hostname_set(char const *hostname); + +extern char *hostname_g; +extern cdtime_t interval_g; +extern int pidfile_from_cli; +extern int timeout_g; +#endif /* GLOBALS_H */ diff --git a/src/daemon/plugin.c b/src/daemon/plugin.c index 0451e8a7..ae99d5fd 100644 --- a/src/daemon/plugin.c +++ b/src/daemon/plugin.c @@ -191,16 +191,21 @@ static int plugin_update_internal_statistics(void) { /* {{{ */ return 0; } /* }}} int plugin_update_internal_statistics */ -static void destroy_callback(callback_func_t *cf) /* {{{ */ +static void free_userdata(user_data_t const *ud) /* {{{ */ { - if (cf == NULL) + if (ud == NULL) return; - if ((cf->cf_udata.data != NULL) && (cf->cf_udata.free_func != NULL)) { - cf->cf_udata.free_func(cf->cf_udata.data); - cf->cf_udata.data = NULL; - cf->cf_udata.free_func = NULL; + if ((ud->data != NULL) && (ud->free_func != NULL)) { + ud->free_func(ud->data); } +} /* }}} void free_userdata */ + +static void destroy_callback(callback_func_t *cf) /* {{{ */ +{ + if (cf == NULL) + return; + free_userdata(&cf->cf_udata); sfree(cf); } /* }}} void destroy_callback */ @@ -345,6 +350,7 @@ static int create_register_callback(llist_t **list, /* {{{ */ cf = calloc(1, sizeof(*cf)); if (cf == NULL) { + free_userdata(ud); ERROR("plugin: create_register_callback: calloc failed."); return -1; } @@ -1120,8 +1126,7 @@ static int plugin_insert_read(read_func_t *rf) { if (le != NULL) { pthread_mutex_unlock(&read_lock); WARNING("The read function \"%s\" is already registered. " - "Check for duplicate \"LoadPlugin\" lines " - "in your configuration!", + "Check for duplicates in your configuration!", rf->rf_name); return EINVAL; } @@ -1186,6 +1191,7 @@ int plugin_register_complex_read(const char *group, const char *name, rf = calloc(1, sizeof(*rf)); if (rf == NULL) { + free_userdata(user_data); ERROR("plugin_register_complex_read: calloc failed."); return ENOMEM; } @@ -1211,6 +1217,7 @@ int plugin_register_complex_read(const char *group, const char *name, status = plugin_insert_read(rf); if (status != 0) { + free_userdata(&rf->rf_udata); sfree(rf->rf_name); sfree(rf); } @@ -1303,11 +1310,7 @@ int plugin_register_flush(const char *name, plugin_flush_cb callback, }); sfree(flush_name); - if (status != 0) { - sfree(cb->name); - sfree(cb); - return status; - } + return status; } return 0; diff --git a/src/daemon/plugin.h b/src/daemon/plugin.h index 4f877e0e..a9ee72d4 100644 --- a/src/daemon/plugin.h +++ b/src/daemon/plugin.h @@ -36,23 +36,19 @@ #include -#ifndef DATA_MAX_NAME_LEN -#define DATA_MAX_NAME_LEN 128 -#endif - #define DS_TYPE_COUNTER 0 #define DS_TYPE_GAUGE 1 #define DS_TYPE_DERIVE 2 #define DS_TYPE_ABSOLUTE 3 #define DS_TYPE_TO_STRING(t) \ - (t == DS_TYPE_COUNTER) ? "counter" : (t == DS_TYPE_GAUGE) \ - ? "gauge" \ - : (t == DS_TYPE_DERIVE) \ - ? "derive" \ - : (t == DS_TYPE_ABSOLUTE) \ - ? "absolute" \ - : "unknown" + (t == DS_TYPE_COUNTER) \ + ? "counter" \ + : (t == DS_TYPE_GAUGE) \ + ? "gauge" \ + : (t == DS_TYPE_DERIVE) \ + ? "derive" \ + : (t == DS_TYPE_ABSOLUTE) ? "absolute" : "unknown" #ifndef LOG_ERR #define LOG_ERR 3 diff --git a/src/daemon/plugin_mock.c b/src/daemon/plugin_mock.c index ca985398..6df4c15d 100644 --- a/src/daemon/plugin_mock.c +++ b/src/daemon/plugin_mock.c @@ -30,7 +30,7 @@ kstat_ctl_t *kc = NULL; #endif /* HAVE_LIBKSTAT */ -char hostname_g[] = "example.com"; +char *hostname_g = "example.com"; void plugin_set_dir(const char *dir) { /* nop */ } diff --git a/src/daemon/utils_cache.c b/src/daemon/utils_cache.c index 7d6e8fd6..ea7c3e33 100644 --- a/src/daemon/utils_cache.c +++ b/src/daemon/utils_cache.c @@ -913,6 +913,15 @@ int uc_iterator_get_interval(uc_iter_t *iter, cdtime_t *ret_interval) { return 0; } /* int uc_iterator_get_name */ +int uc_iterator_get_meta(uc_iter_t *iter, meta_data_t **ret_meta) { + if ((iter == NULL) || (iter->entry == NULL) || (ret_meta == NULL)) + return -1; + + *ret_meta = meta_data_clone(iter->entry->meta); + + return 0; +} /* int uc_iterator_get_meta */ + /* * Meta data interface */ diff --git a/src/daemon/utils_cache.h b/src/daemon/utils_cache.h index 8ba7133a..08c2f10a 100644 --- a/src/daemon/utils_cache.h +++ b/src/daemon/utils_cache.h @@ -106,6 +106,8 @@ int uc_iterator_get_values(uc_iter_t *iter, value_t **ret_values, size_t *ret_num); /* Return the interval of the value at the current position. */ int uc_iterator_get_interval(uc_iter_t *iter, cdtime_t *ret_interval); +/* Return the metadata for the value at the current position. */ +int uc_iterator_get_meta(uc_iter_t *iter, meta_data_t **ret_meta); /* * Meta data interface diff --git a/src/dbi.c b/src/dbi.c index a577aefc..62ef1dc4 100644 --- a/src/dbi.c +++ b/src/dbi.c @@ -62,6 +62,7 @@ struct cdbi_database_s /* {{{ */ { char *name; char *select_db; + char *plugin_name; cdtime_t interval; @@ -172,7 +173,10 @@ static void cdbi_database_free(cdbi_database_t *db) /* {{{ */ return; sfree(db->name); + sfree(db->select_db); + sfree(db->plugin_name); sfree(db->driver); + sfree(db->host); for (size_t i = 0; i < db->driver_options_num; i++) { sfree(db->driver_options[i].key); @@ -184,7 +188,10 @@ static void cdbi_database_free(cdbi_database_t *db) /* {{{ */ if (db->q_prep_areas) for (size_t i = 0; i < db->queries_num; ++i) udb_query_delete_preparation_area(db->q_prep_areas[i]); - free(db->q_prep_areas); + sfree(db->q_prep_areas); + /* N.B.: db->queries references objects "owned" by the global queries + * variable. Free the array here, but not the content. */ + sfree(db->queries); sfree(db); } /* }}} void cdbi_database_free */ @@ -298,6 +305,8 @@ static int cdbi_config_add_database(oconfig_item_t *ci) /* {{{ */ status = cf_util_get_string(child, &db->host); else if (strcasecmp("Interval", child->key) == 0) status = cf_util_get_cdtime(child, &db->interval); + else if (strcasecmp("Plugin", child->key) == 0) + status = cf_util_get_string(child, &db->plugin_name); else { WARNING("dbi plugin: Option `%s' not allowed here.", child->key); status = -1; @@ -540,7 +549,8 @@ static int cdbi_read_database_query(cdbi_database_t *db, /* {{{ */ udb_query_prepare_result( q, prep_area, (db->host ? db->host : hostname_g), - /* plugin = */ "dbi", db->name, column_names, column_num, + /* plugin = */ (db->plugin_name != NULL) ? db->plugin_name : "dbi", + db->name, column_names, column_num, /* interval = */ (db->interval > 0) ? db->interval : 0); /* 0 = error; 1 = success; */ diff --git a/src/dpdkevents.c b/src/dpdkevents.c index efb76947..32d3d6af 100644 --- a/src/dpdkevents.c +++ b/src/dpdkevents.c @@ -488,7 +488,6 @@ static void dpdk_events_gauge_submit(const char *plugin_instance, .plugin = DPDK_EVENTS_PLUGIN, .type = "gauge", .meta = NULL}; - sstrncpy(vl.host, hostname_g, sizeof(vl.host)); sstrncpy(vl.plugin_instance, plugin_instance, sizeof(vl.plugin_instance)); sstrncpy(vl.type_instance, type_instance, sizeof(vl.type_instance)); plugin_dispatch_values(&vl); diff --git a/src/dpdkstat.c b/src/dpdkstat.c index d36cb8fb..c95ba0c6 100644 --- a/src/dpdkstat.c +++ b/src/dpdkstat.c @@ -373,7 +373,6 @@ static void dpdk_stats_counter_submit(const char *plugin_instance, vl.values = &(value_t){.derive = value}; vl.values_len = 1; vl.time = port_read_time; - sstrncpy(vl.host, hostname_g, sizeof(vl.host)); sstrncpy(vl.plugin, DPDK_STATS_PLUGIN, sizeof(vl.plugin)); sstrncpy(vl.plugin_instance, plugin_instance, sizeof(vl.plugin_instance)); dpdk_stats_resolve_cnt_type(vl.type, sizeof(vl.type), cnt_name); diff --git a/src/email.c b/src/email.c index e5f015b2..00e7413d 100644 --- a/src/email.c +++ b/src/email.c @@ -402,9 +402,15 @@ static void *open_connection(void __attribute__((unused)) * arg) { { struct group sg; struct group *grp; - char grbuf[4096]; int status; + long int grbuf_size = sysconf(_SC_GETGR_R_SIZE_MAX); + if (grbuf_size <= 0) + grbuf_size = sysconf(_SC_PAGESIZE); + if (grbuf_size <= 0) + grbuf_size = 4096; + char grbuf[grbuf_size]; + grp = NULL; status = getgrnam_r(group, &sg, grbuf, sizeof(grbuf), &grp); if (status != 0) { diff --git a/src/exec.c b/src/exec.c index 22da3160..a9f7be5c 100644 --- a/src/exec.c +++ b/src/exec.c @@ -368,11 +368,17 @@ static int fork_child(program_list_t *pl, int *fd_in, int *fd_out, struct passwd *sp_ptr; struct passwd sp; - char nambuf[4096]; if (pl->pid != 0) return -1; + long int nambuf_size = sysconf(_SC_GETPW_R_SIZE_MAX); + if (nambuf_size <= 0) + nambuf_size = sysconf(_SC_PAGESIZE); + if (nambuf_size <= 0) + nambuf_size = 4096; + char nambuf[nambuf_size]; + if ((create_pipe(fd_pipe_in) == -1) || (create_pipe(fd_pipe_out) == -1) || (create_pipe(fd_pipe_err) == -1)) goto failed; @@ -405,7 +411,14 @@ static int fork_child(program_list_t *pl, int *fd_in, int *fd_out, struct group *gr_ptr = NULL; struct group gr; - status = getgrnam_r(pl->group, &gr, nambuf, sizeof(nambuf), &gr_ptr); + long int grbuf_size = sysconf(_SC_GETGR_R_SIZE_MAX); + if (grbuf_size <= 0) + grbuf_size = sysconf(_SC_PAGESIZE); + if (grbuf_size <= 0) + grbuf_size = 4096; + char grbuf[grbuf_size]; + + status = getgrnam_r(pl->group, &gr, grbuf, sizeof(grbuf), &gr_ptr); if (status != 0) { ERROR("exec plugin: Failed to get group information " "for group ``%s'': %s", diff --git a/src/filecount.c b/src/filecount.c index a2ec8e82..7842aa61 100644 --- a/src/filecount.c +++ b/src/filecount.c @@ -34,10 +34,15 @@ #define FC_RECURSIVE 1 #define FC_HIDDEN 2 +#define FC_REGULAR 4 struct fc_directory_conf_s { char *path; + char *plugin_name; char *instance; + char *files_size_type; + char *files_num_type; + char *type_instance; int options; @@ -58,31 +63,56 @@ typedef struct fc_directory_conf_s fc_directory_conf_t; static fc_directory_conf_t **directories = NULL; static size_t directories_num = 0; +void fc_free_dir(fc_directory_conf_t *dir) { + sfree(dir->path); + sfree(dir->plugin_name); + sfree(dir->instance); + sfree(dir->files_size_type); + sfree(dir->files_num_type); + sfree(dir->type_instance); + sfree(dir->name); + + sfree(dir); +} /* void fc_free_dir */ + static void fc_submit_dir(const fc_directory_conf_t *dir) { value_list_t vl = VALUE_LIST_INIT; - vl.values = &(value_t){.gauge = (gauge_t)dir->files_num}; - vl.values_len = 1; - sstrncpy(vl.plugin, "filecount", sizeof(vl.plugin)); - sstrncpy(vl.plugin_instance, dir->instance, sizeof(vl.plugin_instance)); - sstrncpy(vl.type, "files", sizeof(vl.type)); + sstrncpy(vl.plugin, dir->plugin_name, sizeof(vl.plugin)); + if (dir->instance != NULL) + sstrncpy(vl.plugin_instance, dir->instance, sizeof(vl.plugin_instance)); + if (dir->type_instance != NULL) + sstrncpy(vl.type_instance, dir->type_instance, sizeof(vl.type_instance)); - plugin_dispatch_values(&vl); + vl.values_len = 1; - vl.values = &(value_t){.gauge = (gauge_t)dir->files_size}; - sstrncpy(vl.type, "bytes", sizeof(vl.type)); + if (dir->files_num_type != NULL) { + vl.values = &(value_t){.gauge = (gauge_t)dir->files_num}; + sstrncpy(vl.type, dir->files_num_type, sizeof(vl.type)); + plugin_dispatch_values(&vl); + } - plugin_dispatch_values(&vl); + if (dir->files_size_type != NULL) { + vl.values = &(value_t){.gauge = (gauge_t)dir->files_size}; + sstrncpy(vl.type, dir->files_size_type, sizeof(vl.type)); + plugin_dispatch_values(&vl); + } } /* void fc_submit_dir */ /* * Config: * * + * Plugin "foo" * Instance "foobar" * Name "*.conf" * MTime -3600 * Size "+10M" + * Recursive true + * IncludeHidden false + * FilesSizeType "bytes" + * FilesCountType "files" + * TypeInstance "instance" * * * @@ -94,7 +124,6 @@ static void fc_submit_dir(const fc_directory_conf_t *dir) { static int fc_config_set_instance(fc_directory_conf_t *dir, const char *str) { char buffer[1024]; char *ptr; - char *copy; sstrncpy(buffer, str, sizeof(buffer)); for (ptr = buffer; *ptr != 0; ptr++) @@ -104,10 +133,7 @@ static int fc_config_set_instance(fc_directory_conf_t *dir, const char *str) { for (ptr = buffer; *ptr == '_'; ptr++) /* do nothing */; - if (*ptr == 0) - return -1; - - copy = strdup(ptr); + char *copy = strdup(ptr); if (copy == NULL) return -1; @@ -130,15 +156,13 @@ static int fc_config_add_dir_instance(fc_directory_conf_t *dir, static int fc_config_add_dir_name(fc_directory_conf_t *dir, oconfig_item_t *ci) { - char *temp; - if ((ci->values_num != 1) || (ci->values[0].type != OCONFIG_TYPE_STRING)) { WARNING("filecount plugin: The `Name' config option needs exactly one " "string argument."); return -1; } - temp = strdup(ci->values[0].value.string); + char *temp = strdup(ci->values[0].value.string); if (temp == NULL) { ERROR("filecount plugin: strdup failed."); return -1; @@ -152,9 +176,6 @@ static int fc_config_add_dir_name(fc_directory_conf_t *dir, static int fc_config_add_dir_mtime(fc_directory_conf_t *dir, oconfig_item_t *ci) { - char *endptr; - double temp; - if ((ci->values_num != 1) || ((ci->values[0].type != OCONFIG_TYPE_STRING) && (ci->values[0].type != OCONFIG_TYPE_NUMBER))) { WARNING("filecount plugin: The `MTime' config option needs exactly one " @@ -168,8 +189,8 @@ static int fc_config_add_dir_mtime(fc_directory_conf_t *dir, } errno = 0; - endptr = NULL; - temp = strtod(ci->values[0].value.string, &endptr); + char *endptr = NULL; + double temp = strtod(ci->values[0].value.string, &endptr); if ((errno != 0) || (endptr == NULL) || (endptr == ci->values[0].value.string)) { WARNING("filecount plugin: Converting `%s' to a number failed.", @@ -220,9 +241,6 @@ static int fc_config_add_dir_mtime(fc_directory_conf_t *dir, static int fc_config_add_dir_size(fc_directory_conf_t *dir, oconfig_item_t *ci) { - char *endptr; - double temp; - if ((ci->values_num != 1) || ((ci->values[0].type != OCONFIG_TYPE_STRING) && (ci->values[0].type != OCONFIG_TYPE_NUMBER))) { WARNING("filecount plugin: The `Size' config option needs exactly one " @@ -236,8 +254,8 @@ static int fc_config_add_dir_size(fc_directory_conf_t *dir, } errno = 0; - endptr = NULL; - temp = strtod(ci->values[0].value.string, &endptr); + char *endptr = NULL; + double temp = strtod(ci->values[0].value.string, &endptr); if ((errno != 0) || (endptr == NULL) || (endptr == ci->values[0].value.string)) { WARNING("filecount plugin: Converting `%s' to a number failed.", @@ -303,9 +321,6 @@ static int fc_config_add_dir_option(fc_directory_conf_t *dir, } /* int fc_config_add_dir_option */ static int fc_config_add_dir(oconfig_item_t *ci) { - fc_directory_conf_t *dir; - int status; - if ((ci->values_num != 1) || (ci->values[0].type != OCONFIG_TYPE_STRING)) { WARNING("filecount plugin: `Directory' needs exactly one string " "argument."); @@ -313,7 +328,7 @@ static int fc_config_add_dir(oconfig_item_t *ci) { } /* Initialize `dir' */ - dir = calloc(1, sizeof(*dir)); + fc_directory_conf_t *dir = calloc(1, sizeof(*dir)); if (dir == NULL) { ERROR("filecount plugin: calloc failed."); return -1; @@ -322,23 +337,36 @@ static int fc_config_add_dir(oconfig_item_t *ci) { dir->path = strdup(ci->values[0].value.string); if (dir->path == NULL) { ERROR("filecount plugin: strdup failed."); - sfree(dir); + fc_free_dir(dir); return -1; } - fc_config_set_instance(dir, dir->path); - - dir->options = FC_RECURSIVE; + dir->options = FC_RECURSIVE | FC_REGULAR; dir->name = NULL; + dir->plugin_name = strdup("filecount"); + dir->instance = NULL; + dir->type_instance = NULL; dir->mtime = 0; dir->size = 0; - status = 0; + dir->files_size_type = strdup("bytes"); + dir->files_num_type = strdup("files"); + + if (dir->plugin_name == NULL || dir->files_size_type == NULL || + dir->files_num_type == NULL) { + ERROR("filecount plugin: strdup failed."); + fc_free_dir(dir); + return -1; + } + + int status = 0; for (int i = 0; i < ci->children_num; i++) { oconfig_item_t *option = ci->children + i; - if (strcasecmp("Instance", option->key) == 0) + if (strcasecmp("Plugin", option->key) == 0) + status = cf_util_get_string(option, &dir->plugin_name); + else if (strcasecmp("Instance", option->key) == 0) status = fc_config_add_dir_instance(dir, option); else if (strcasecmp("Name", option->key) == 0) status = fc_config_add_dir_name(dir, option); @@ -350,6 +378,14 @@ static int fc_config_add_dir(oconfig_item_t *ci) { 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 if (strcasecmp("RegularOnly", option->key) == 0) + status = fc_config_add_dir_option(dir, option, FC_REGULAR); + else if (strcasecmp("FilesSizeType", option->key) == 0) + status = cf_util_get_string(option, &dir->files_size_type); + else if (strcasecmp("FilesCountType", option->key) == 0) + status = cf_util_get_string(option, &dir->files_num_type); + else if (strcasecmp("TypeInstance", option->key) == 0) + status = cf_util_get_string(option, &dir->type_instance); else { WARNING("filecount plugin: fc_config_add_dir: " "Option `%s' not allowed here.", @@ -361,28 +397,52 @@ static int fc_config_add_dir(oconfig_item_t *ci) { break; } /* for (ci->children) */ - if (status == 0) { - fc_directory_conf_t **temp; + if (status != 0) { + fc_free_dir(dir); + return -1; + } - temp = realloc(directories, sizeof(*directories) * (directories_num + 1)); - if (temp == NULL) { - ERROR("filecount plugin: realloc failed."); - status = -1; - } else { - directories = temp; - directories[directories_num] = dir; - directories_num++; + /* Set default plugin instance */ + if (dir->instance == NULL) { + fc_config_set_instance(dir, dir->path); + if (dir->instance == NULL || strlen(dir->instance) == 0) { + ERROR("filecount plugin: failed to build plugin instance name."); + fc_free_dir(dir); + return -1; } } - if (status != 0) { - sfree(dir->name); + /* Handle disabled types */ + if (strlen(dir->instance) == 0) sfree(dir->instance); - sfree(dir->path); - sfree(dir); + + if (strlen(dir->files_size_type) == 0) + sfree(dir->files_size_type); + + if (strlen(dir->files_num_type) == 0) + sfree(dir->files_num_type); + + if (dir->files_size_type == NULL && dir->files_num_type == NULL) { + WARNING("filecount plugin: Both `FilesSizeType' and `FilesCountType ' " + "are disabled for '%s'. There's no types to report.", + dir->path); + fc_free_dir(dir); return -1; } + /* Ready to add it to list */ + fc_directory_conf_t **temp = + realloc(directories, sizeof(*directories) * (directories_num + 1)); + if (temp == NULL) { + ERROR("filecount plugin: realloc failed."); + fc_free_dir(dir); + return -1; + } + + directories = temp; + directories[directories_num] = dir; + directories_num++; + return 0; } /* int fc_config_add_dir */ @@ -414,14 +474,13 @@ static int fc_read_dir_callback(const char *dirname, const char *filename, fc_directory_conf_t *dir = user_data; char abs_path[PATH_MAX]; struct stat statbuf; - int status; if (dir == NULL) return -1; snprintf(abs_path, sizeof(abs_path), "%s/%s", dirname, filename); - status = lstat(abs_path, &statbuf); + int status = lstat(abs_path, &statbuf); if (status != 0) { ERROR("filecount plugin: stat (%s) failed.", abs_path); return -1; @@ -432,7 +491,7 @@ static int fc_read_dir_callback(const char *dirname, const char *filename, abs_path, fc_read_dir_callback, dir, /* include hidden = */ (dir->options & FC_HIDDEN) ? 1 : 0); return status; - } else if (!S_ISREG(statbuf.st_mode)) { + } else if ((dir->options & FC_REGULAR) && !S_ISREG(statbuf.st_mode)) { return 0; } @@ -442,6 +501,11 @@ static int fc_read_dir_callback(const char *dirname, const char *filename, return 0; } + if (!S_ISREG(statbuf.st_mode)) { + dir->files_num++; + return 0; + } + if (dir->mtime != 0) { time_t mtime = dir->now; @@ -478,15 +542,13 @@ static int fc_read_dir_callback(const char *dirname, const char *filename, } /* int fc_read_dir_callback */ static int fc_read_dir(fc_directory_conf_t *dir) { - int status; - dir->files_num = 0; dir->files_size = 0; if (dir->mtime != 0) dir->now = time(NULL); - status = + int status = walk_directory(dir->path, fc_read_dir_callback, dir, /* include hidden */ (dir->options & FC_HIDDEN) ? 1 : 0); if (status != 0) { diff --git a/src/grpc.cc b/src/grpc.cc index 2f16dbcb..0f5cfec0 100644 --- a/src/grpc.cc +++ b/src/grpc.cc @@ -56,6 +56,8 @@ using collectd::QueryValuesResponse; using google::protobuf::util::TimeUtil; +typedef google::protobuf::Map grpcMetadata; + /* * private types */ @@ -154,6 +156,124 @@ static grpc::Status unmarshal_ident(const collectd::types::Identifier &msg, return grpc::Status::OK; } /* unmarshal_ident() */ +static grpc::Status marshal_meta_data(meta_data_t *meta, + grpcMetadata *mutable_meta_data) { + char **meta_data_keys = nullptr; + int meta_data_keys_len = meta_data_toc(meta, &meta_data_keys); + if (meta_data_keys_len < 0) { + return grpc::Status(grpc::StatusCode::INTERNAL, + grpc::string("error getting metadata keys")); + } + + for (int i = 0; i < meta_data_keys_len; i++) { + char *key = meta_data_keys[i]; + int md_type = meta_data_type(meta, key); + + collectd::types::MetadataValue md_value; + md_value.Clear(); + + switch (md_type) { + case MD_TYPE_STRING: + char *md_string; + if (meta_data_get_string(meta, key, &md_string) != 0 || md_string == nullptr) { + strarray_free(meta_data_keys, meta_data_keys_len); + return grpc::Status(grpc::StatusCode::INTERNAL, + grpc::string("missing metadata")); + } + md_value.set_string_value(md_string); + free(md_string); + break; + case MD_TYPE_SIGNED_INT: + int64_t int64_value; + if (meta_data_get_signed_int(meta, key, &int64_value) != 0) { + strarray_free(meta_data_keys, meta_data_keys_len); + return grpc::Status(grpc::StatusCode::INTERNAL, + grpc::string("missing metadata")); + } + md_value.set_int64_value(int64_value); + break; + case MD_TYPE_UNSIGNED_INT: + uint64_t uint64_value; + if (meta_data_get_unsigned_int(meta, key, &uint64_value) != 0) { + strarray_free(meta_data_keys, meta_data_keys_len); + return grpc::Status(grpc::StatusCode::INTERNAL, + grpc::string("missing metadata")); + } + md_value.set_uint64_value(uint64_value); + break; + case MD_TYPE_DOUBLE: + double double_value; + if (meta_data_get_double(meta, key, &double_value) != 0) { + strarray_free(meta_data_keys, meta_data_keys_len); + return grpc::Status(grpc::StatusCode::INTERNAL, + grpc::string("missing metadata")); + } + md_value.set_double_value(double_value); + break; + case MD_TYPE_BOOLEAN: + bool bool_value; + if (meta_data_get_boolean(meta, key, &bool_value) != 0) { + strarray_free(meta_data_keys, meta_data_keys_len); + return grpc::Status(grpc::StatusCode::INTERNAL, + grpc::string("missing metadata")); + } + md_value.set_bool_value(bool_value); + break; + default: + strarray_free(meta_data_keys, meta_data_keys_len); + ERROR("grpc: invalid metadata type (%d)", md_type); + return grpc::Status(grpc::StatusCode::INTERNAL, + grpc::string("unknown metadata type")); + } + + (*mutable_meta_data)[grpc::string(key)] = md_value; + + strarray_free(meta_data_keys, meta_data_keys_len); + } + + return grpc::Status::OK; +} + +static grpc::Status unmarshal_meta_data(const grpcMetadata &rpc_metadata, + meta_data_t **md_out) { + *md_out = meta_data_create(); + if (*md_out == nullptr) { + return grpc::Status(grpc::StatusCode::RESOURCE_EXHAUSTED, + grpc::string("failed to metadata list")); + } + for (auto kv: rpc_metadata) { + auto k = kv.first.c_str(); + auto v = kv.second; + + // The meta_data collection individually allocates copies of the keys and + // string values for each entry, so it's safe for us to pass a reference + // to our short-lived strings. + + switch (v.value_case()) { + case collectd::types::MetadataValue::ValueCase::kStringValue: + meta_data_add_string(*md_out, k, v.string_value().c_str()); + break; + case collectd::types::MetadataValue::ValueCase::kInt64Value: + meta_data_add_signed_int(*md_out, k, v.int64_value()); + break; + case collectd::types::MetadataValue::ValueCase::kUint64Value: + meta_data_add_unsigned_int(*md_out, k, v.uint64_value()); + break; + case collectd::types::MetadataValue::ValueCase::kDoubleValue: + meta_data_add_double(*md_out, k, v.double_value()); + break; + case collectd::types::MetadataValue::ValueCase::kBoolValue: + meta_data_add_boolean(*md_out, k, v.bool_value()); + break; + default: + meta_data_destroy(*md_out); + return grpc::Status(grpc::StatusCode::INVALID_ARGUMENT, + grpc::string("Metadata of unknown type")); + } + } + return grpc::Status::OK; +} + static grpc::Status marshal_value_list(const value_list_t *vl, collectd::types::ValueList *msg) { auto id = msg->mutable_identifier(); @@ -170,9 +290,18 @@ static grpc::Status marshal_value_list(const value_list_t *vl, msg->set_allocated_time(new google::protobuf::Timestamp(t)); msg->set_allocated_interval(new google::protobuf::Duration(d)); + msg->clear_meta_data(); + if (vl->meta != nullptr) { + grpc::Status status = marshal_meta_data(vl->meta, msg->mutable_meta_data()); + if (!status.ok()) { + return status; + } + } + for (size_t i = 0; i < vl->values_len; ++i) { auto v = msg->add_values(); - switch (ds->ds[i].type) { + int value_type = ds->ds[i].type; + switch (value_type) { case DS_TYPE_COUNTER: v->set_counter(vl->values[i].counter); break; @@ -186,6 +315,7 @@ static grpc::Status marshal_value_list(const value_list_t *vl, v->set_absolute(vl->values[i].absolute); break; default: + ERROR("grpc: invalid value type (%d)", value_type); return grpc::Status(grpc::StatusCode::INTERNAL, grpc::string("unknown value type")); } @@ -207,6 +337,10 @@ static grpc::Status unmarshal_value_list(const collectd::types::ValueList &msg, if (!status.ok()) return status; + status = unmarshal_meta_data(msg.meta_data(), &vl->meta); + if (!status.ok()) + return status; + value_t *values = NULL; size_t values_len = 0; @@ -249,7 +383,8 @@ static grpc::Status unmarshal_value_list(const collectd::types::ValueList &msg, if (status.ok()) { vl->values = values; vl->values_len = values_len; - } else if (values) { + } else { + meta_data_destroy(vl->meta); free(values); } @@ -280,6 +415,7 @@ public: auto vl = value_lists.front(); value_lists.pop(); sfree(vl.values); + meta_data_destroy(vl.meta); } return status; @@ -328,7 +464,6 @@ private: if (!ident_matches(&vl, match)) continue; - if (uc_iterator_get_time(iter, &vl.time) < 0) { status = grpc::Status(grpc::StatusCode::INTERNAL, @@ -346,6 +481,10 @@ private: grpc::string("failed to retrieve values")); break; } + if (uc_iterator_get_meta(iter, &vl.meta) < 0) { + status = grpc::Status(grpc::StatusCode::INTERNAL, + grpc::string("failed to retrieve value metadata")); + } value_lists->push(vl); } // while (uc_iterator_next(iter, &name) == 0) diff --git a/src/intel_pmu.c b/src/intel_pmu.c new file mode 100644 index 00000000..fd2bd6f3 --- /dev/null +++ b/src/intel_pmu.c @@ -0,0 +1,598 @@ +/** + * collectd - src/intel_pmu.c + * + * Copyright(c) 2017 Intel Corporation. All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * Authors: + * Serhiy Pshyk + **/ + +#include "collectd.h" +#include "common.h" + +#include +#include + +#define PMU_PLUGIN "intel_pmu" + +#define HW_CACHE_READ_ACCESS \ + (((PERF_COUNT_HW_CACHE_OP_READ) << 8) | \ + ((PERF_COUNT_HW_CACHE_RESULT_ACCESS) << 16)) + +#define HW_CACHE_WRITE_ACCESS \ + (((PERF_COUNT_HW_CACHE_OP_WRITE) << 8) | \ + ((PERF_COUNT_HW_CACHE_RESULT_ACCESS) << 16)) + +#define HW_CACHE_PREFETCH_ACCESS \ + (((PERF_COUNT_HW_CACHE_OP_PREFETCH) << 8) | \ + ((PERF_COUNT_HW_CACHE_RESULT_ACCESS) << 16)) + +#define HW_CACHE_READ_MISS \ + (((PERF_COUNT_HW_CACHE_OP_READ) << 8) | \ + ((PERF_COUNT_HW_CACHE_RESULT_MISS) << 16)) + +#define HW_CACHE_WRITE_MISS \ + (((PERF_COUNT_HW_CACHE_OP_WRITE) << 8) | \ + ((PERF_COUNT_HW_CACHE_RESULT_MISS) << 16)) + +#define HW_CACHE_PREFETCH_MISS \ + (((PERF_COUNT_HW_CACHE_OP_PREFETCH) << 8) | \ + ((PERF_COUNT_HW_CACHE_RESULT_MISS) << 16)) + +struct event_info { + char *name; + uint64_t config; +}; +typedef struct event_info event_info_t; + +struct intel_pmu_ctx_s { + _Bool hw_cache_events; + _Bool kernel_pmu_events; + _Bool sw_events; + char event_list_fn[PATH_MAX]; + char **hw_events; + size_t hw_events_count; + struct eventlist *event_list; +}; +typedef struct intel_pmu_ctx_s intel_pmu_ctx_t; + +event_info_t g_kernel_pmu_events[] = { + {.name = "cpu-cycles", .config = PERF_COUNT_HW_CPU_CYCLES}, + {.name = "instructions", .config = PERF_COUNT_HW_INSTRUCTIONS}, + {.name = "cache-references", .config = PERF_COUNT_HW_CACHE_REFERENCES}, + {.name = "cache-misses", .config = PERF_COUNT_HW_CACHE_MISSES}, + {.name = "branches", .config = PERF_COUNT_HW_BRANCH_INSTRUCTIONS}, + {.name = "branch-misses", .config = PERF_COUNT_HW_BRANCH_MISSES}, + {.name = "bus-cycles", .config = PERF_COUNT_HW_BUS_CYCLES}, +}; + +event_info_t g_hw_cache_events[] = { + + {.name = "L1-dcache-loads", + .config = (PERF_COUNT_HW_CACHE_L1D | HW_CACHE_READ_ACCESS)}, + {.name = "L1-dcache-load-misses", + .config = (PERF_COUNT_HW_CACHE_L1D | HW_CACHE_READ_MISS)}, + {.name = "L1-dcache-stores", + .config = (PERF_COUNT_HW_CACHE_L1D | HW_CACHE_WRITE_ACCESS)}, + {.name = "L1-dcache-store-misses", + .config = (PERF_COUNT_HW_CACHE_L1D | HW_CACHE_WRITE_MISS)}, + {.name = "L1-dcache-prefetches", + .config = (PERF_COUNT_HW_CACHE_L1D | HW_CACHE_PREFETCH_ACCESS)}, + {.name = "L1-dcache-prefetch-misses", + .config = (PERF_COUNT_HW_CACHE_L1D | HW_CACHE_PREFETCH_MISS)}, + + {.name = "L1-icache-loads", + .config = (PERF_COUNT_HW_CACHE_L1I | HW_CACHE_READ_ACCESS)}, + {.name = "L1-icache-load-misses", + .config = (PERF_COUNT_HW_CACHE_L1I | HW_CACHE_READ_MISS)}, + {.name = "L1-icache-prefetches", + .config = (PERF_COUNT_HW_CACHE_L1I | HW_CACHE_PREFETCH_ACCESS)}, + {.name = "L1-icache-prefetch-misses", + .config = (PERF_COUNT_HW_CACHE_L1I | HW_CACHE_PREFETCH_MISS)}, + + {.name = "LLC-loads", + .config = (PERF_COUNT_HW_CACHE_LL | HW_CACHE_READ_ACCESS)}, + {.name = "LLC-load-misses", + .config = (PERF_COUNT_HW_CACHE_LL | HW_CACHE_READ_MISS)}, + {.name = "LLC-stores", + .config = (PERF_COUNT_HW_CACHE_LL | HW_CACHE_WRITE_ACCESS)}, + {.name = "LLC-store-misses", + .config = (PERF_COUNT_HW_CACHE_LL | HW_CACHE_WRITE_MISS)}, + {.name = "LLC-prefetches", + .config = (PERF_COUNT_HW_CACHE_LL | HW_CACHE_PREFETCH_ACCESS)}, + {.name = "LLC-prefetch-misses", + .config = (PERF_COUNT_HW_CACHE_LL | HW_CACHE_PREFETCH_MISS)}, + + {.name = "dTLB-loads", + .config = (PERF_COUNT_HW_CACHE_DTLB | HW_CACHE_READ_ACCESS)}, + {.name = "dTLB-load-misses", + .config = (PERF_COUNT_HW_CACHE_DTLB | HW_CACHE_READ_MISS)}, + {.name = "dTLB-stores", + .config = (PERF_COUNT_HW_CACHE_DTLB | HW_CACHE_WRITE_ACCESS)}, + {.name = "dTLB-store-misses", + .config = (PERF_COUNT_HW_CACHE_DTLB | HW_CACHE_WRITE_MISS)}, + {.name = "dTLB-prefetches", + .config = (PERF_COUNT_HW_CACHE_DTLB | HW_CACHE_PREFETCH_ACCESS)}, + {.name = "dTLB-prefetch-misses", + .config = (PERF_COUNT_HW_CACHE_DTLB | HW_CACHE_PREFETCH_MISS)}, + + {.name = "iTLB-loads", + .config = (PERF_COUNT_HW_CACHE_ITLB | HW_CACHE_READ_ACCESS)}, + {.name = "iTLB-load-misses", + .config = (PERF_COUNT_HW_CACHE_ITLB | HW_CACHE_READ_MISS)}, + + {.name = "branch-loads", + .config = (PERF_COUNT_HW_CACHE_BPU | HW_CACHE_READ_ACCESS)}, + {.name = "branch-load-misses", + .config = (PERF_COUNT_HW_CACHE_BPU | HW_CACHE_READ_MISS)}, +}; + +event_info_t g_sw_events[] = { + {.name = "cpu-clock", .config = PERF_COUNT_SW_CPU_CLOCK}, + + {.name = "task-clock", .config = PERF_COUNT_SW_TASK_CLOCK}, + + {.name = "context-switches", .config = PERF_COUNT_SW_CONTEXT_SWITCHES}, + + {.name = "cpu-migrations", .config = PERF_COUNT_SW_CPU_MIGRATIONS}, + + {.name = "page-faults", .config = PERF_COUNT_SW_PAGE_FAULTS}, + + {.name = "minor-faults", .config = PERF_COUNT_SW_PAGE_FAULTS_MIN}, + + {.name = "major-faults", .config = PERF_COUNT_SW_PAGE_FAULTS_MAJ}, + + {.name = "alignment-faults", .config = PERF_COUNT_SW_ALIGNMENT_FAULTS}, + + {.name = "emulation-faults", .config = PERF_COUNT_SW_EMULATION_FAULTS}, +}; + +static intel_pmu_ctx_t g_ctx; + +#if COLLECT_DEBUG +static void pmu_dump_events() { + + DEBUG(PMU_PLUGIN ": Events:"); + + struct event *e; + + for (e = g_ctx.event_list->eventlist; e; e = e->next) { + DEBUG(PMU_PLUGIN ": event : %s", e->event); + DEBUG(PMU_PLUGIN ": group_lead: %d", e->group_leader); + DEBUG(PMU_PLUGIN ": end_group : %d", e->end_group); + DEBUG(PMU_PLUGIN ": type : %#x", e->attr.type); + DEBUG(PMU_PLUGIN ": config : %#x", (unsigned)e->attr.config); + DEBUG(PMU_PLUGIN ": size : %d", e->attr.size); + } +} + +static void pmu_dump_config(void) { + + DEBUG(PMU_PLUGIN ": Config:"); + DEBUG(PMU_PLUGIN ": hw_cache_events : %d", g_ctx.hw_cache_events); + DEBUG(PMU_PLUGIN ": kernel_pmu_events : %d", g_ctx.kernel_pmu_events); + DEBUG(PMU_PLUGIN ": software_events : %d", g_ctx.sw_events); + + for (size_t i = 0; i < g_ctx.hw_events_count; i++) { + DEBUG(PMU_PLUGIN ": hardware_events[%zu]: %s", i, g_ctx.hw_events[i]); + } +} + +#endif /* COLLECT_DEBUG */ + +static int pmu_config_hw_events(oconfig_item_t *ci) { + + if (strcasecmp("HardwareEvents", ci->key) != 0) { + return -EINVAL; + } + + g_ctx.hw_events = calloc(ci->values_num, sizeof(char *)); + if (g_ctx.hw_events == NULL) { + ERROR(PMU_PLUGIN ": Failed to allocate hw events."); + return -ENOMEM; + } + + for (int i = 0; i < ci->values_num; i++) { + if (ci->values[i].type != OCONFIG_TYPE_STRING) { + WARNING(PMU_PLUGIN ": The %s option requires string arguments.", ci->key); + continue; + } + + g_ctx.hw_events[g_ctx.hw_events_count] = strdup(ci->values[i].value.string); + if (g_ctx.hw_events[g_ctx.hw_events_count] == NULL) { + ERROR(PMU_PLUGIN ": Failed to allocate hw events entry."); + return -ENOMEM; + } + + g_ctx.hw_events_count++; + } + + return 0; +} + +static int pmu_config(oconfig_item_t *ci) { + + DEBUG(PMU_PLUGIN ": %s:%d", __FUNCTION__, __LINE__); + + for (int i = 0; i < ci->children_num; i++) { + int ret = 0; + oconfig_item_t *child = ci->children + i; + + if (strcasecmp("ReportHardwareCacheEvents", child->key) == 0) { + ret = cf_util_get_boolean(child, &g_ctx.hw_cache_events); + } else if (strcasecmp("ReportKernelPMUEvents", child->key) == 0) { + ret = cf_util_get_boolean(child, &g_ctx.kernel_pmu_events); + } else if (strcasecmp("EventList", child->key) == 0) { + ret = cf_util_get_string_buffer(child, g_ctx.event_list_fn, + sizeof(g_ctx.event_list_fn)); + } else if (strcasecmp("HardwareEvents", child->key) == 0) { + ret = pmu_config_hw_events(child); + } else if (strcasecmp("ReportSoftwareEvents", child->key) == 0) { + ret = cf_util_get_boolean(child, &g_ctx.sw_events); + } else { + ERROR(PMU_PLUGIN ": Unknown configuration parameter \"%s\".", child->key); + ret = -1; + } + + if (ret != 0) { + DEBUG(PMU_PLUGIN ": %s:%d ret=%d", __FUNCTION__, __LINE__, ret); + return ret; + } + } + +#if COLLECT_DEBUG + pmu_dump_config(); +#endif + + return 0; +} + +static void pmu_submit_counter(int cpu, char *event, counter_t value, + meta_data_t *meta) { + value_list_t vl = VALUE_LIST_INIT; + + vl.values = &(value_t){.counter = value}; + vl.values_len = 1; + + sstrncpy(vl.plugin, PMU_PLUGIN, sizeof(vl.plugin)); + if (cpu == -1) { + snprintf(vl.plugin_instance, sizeof(vl.plugin_instance), "all"); + } else { + vl.meta = meta; + snprintf(vl.plugin_instance, sizeof(vl.plugin_instance), "%d", cpu); + } + sstrncpy(vl.type, "counter", sizeof(vl.type)); + sstrncpy(vl.type_instance, event, sizeof(vl.type_instance)); + + plugin_dispatch_values(&vl); +} + +meta_data_t *pmu_meta_data_create(const struct efd *efd) { + meta_data_t *meta = NULL; + + /* create meta data only if value was scaled */ + if (efd->val[1] == efd->val[2] || !efd->val[2]) { + return NULL; + } + + meta = meta_data_create(); + if (meta == NULL) { + ERROR(PMU_PLUGIN ": meta_data_create failed."); + return NULL; + } + + meta_data_add_unsigned_int(meta, "intel_pmu:raw_count", efd->val[0]); + meta_data_add_unsigned_int(meta, "intel_pmu:time_enabled", efd->val[1]); + meta_data_add_unsigned_int(meta, "intel_pmu:time_running", efd->val[2]); + + return meta; +} + +static void pmu_dispatch_data(void) { + + struct event *e; + + for (e = g_ctx.event_list->eventlist; e; e = e->next) { + uint64_t all_value = 0; + int event_enabled = 0; + for (int i = 0; i < g_ctx.event_list->num_cpus; i++) { + + if (e->efd[i].fd < 0) + continue; + + event_enabled++; + + /* If there are more events than counters, the kernel uses time + * multiplexing. With multiplexing, at the end of the run, + * the counter is scaled basing on total time enabled vs time running. + * final_count = raw_count * time_enabled/time_running + */ + uint64_t value = event_scaled_value(e, i); + all_value += value; + + /* get meta data with information about scaling */ + meta_data_t *meta = pmu_meta_data_create(&e->efd[i]); + + /* dispatch per CPU value */ + pmu_submit_counter(i, e->event, value, meta); + + meta_data_destroy(meta); + } + + if (event_enabled > 0) { + DEBUG(PMU_PLUGIN ": %-20s %'10lu", e->event, all_value); + /* dispatch all CPU value */ + pmu_submit_counter(-1, e->event, all_value, NULL); + } + } +} + +static int pmu_read(__attribute__((unused)) user_data_t *ud) { + int ret; + + DEBUG(PMU_PLUGIN ": %s:%d", __FUNCTION__, __LINE__); + + ret = read_all_events(g_ctx.event_list); + if (ret != 0) { + ERROR(PMU_PLUGIN ": Failed to read values of all events."); + return ret; + } + + pmu_dispatch_data(); + + return 0; +} + +static int pmu_add_events(struct eventlist *el, uint32_t type, + event_info_t *events, size_t count) { + + for (size_t i = 0; i < count; i++) { + /* Allocate memory for event struct that contains array of efd structs + for all cores */ + struct event *e = + calloc(sizeof(struct event) + sizeof(struct efd) * el->num_cpus, 1); + if (e == NULL) { + ERROR(PMU_PLUGIN ": Failed to allocate event structure"); + return -ENOMEM; + } + + e->attr.type = type; + e->attr.config = events[i].config; + e->attr.size = PERF_ATTR_SIZE_VER0; + if (!el->eventlist) + el->eventlist = e; + if (el->eventlist_last) + el->eventlist_last->next = e; + el->eventlist_last = e; + e->event = strdup(events[i].name); + } + + return 0; +} + +static int pmu_add_hw_events(struct eventlist *el, char **e, size_t count) { + + for (size_t i = 0; i < count; i++) { + + size_t group_events_count = 0; + + char *events = strdup(e[i]); + if (!events) + return -1; + + char *s, *tmp; + for (s = strtok_r(events, ",", &tmp); s; s = strtok_r(NULL, ",", &tmp)) { + + /* Multiple events parsed in one entry */ + if (group_events_count == 1) { + /* Mark previously added event as group leader */ + el->eventlist_last->group_leader = 1; + } + + /* Allocate memory for event struct that contains array of efd structs + for all cores */ + struct event *e = + calloc(sizeof(struct event) + sizeof(struct efd) * el->num_cpus, 1); + if (e == NULL) { + free(events); + return -ENOMEM; + } + + if (resolve_event(s, &e->attr) == 0) { + e->next = NULL; + if (!el->eventlist) + el->eventlist = e; + if (el->eventlist_last) + el->eventlist_last->next = e; + el->eventlist_last = e; + e->event = strdup(s); + } else { + DEBUG(PMU_PLUGIN ": Cannot resolve %s", s); + sfree(e); + } + + group_events_count++; + } + + /* Multiple events parsed in one entry */ + if (group_events_count > 1) { + /* Mark last added event as group end */ + el->eventlist_last->end_group = 1; + } + + free(events); + } + + return 0; +} + +static void pmu_free_events(struct eventlist *el) { + + if (el == NULL) + return; + + struct event *e = el->eventlist; + + while (e) { + struct event *next = e->next; + sfree(e); + e = next; + } + + el->eventlist = NULL; +} + +static int pmu_setup_events(struct eventlist *el, bool measure_all, + int measure_pid) { + struct event *e, *leader = NULL; + int ret = -1; + + for (e = el->eventlist; e; e = e->next) { + + for (int i = 0; i < el->num_cpus; i++) { + if (setup_event(e, i, leader, measure_all, measure_pid) < 0) { + WARNING(PMU_PLUGIN ": perf event '%s' is not available (cpu=%d).", + e->event, i); + } else { + /* success if at least one event was set */ + ret = 0; + } + } + + if (e->group_leader) + leader = e; + if (e->end_group) + leader = NULL; + } + + return ret; +} + +static int pmu_init(void) { + int ret; + + DEBUG(PMU_PLUGIN ": %s:%d", __FUNCTION__, __LINE__); + + g_ctx.event_list = alloc_eventlist(); + if (g_ctx.event_list == NULL) { + ERROR(PMU_PLUGIN ": Failed to allocate event list."); + return -ENOMEM; + } + + if (g_ctx.hw_cache_events) { + ret = + pmu_add_events(g_ctx.event_list, PERF_TYPE_HW_CACHE, g_hw_cache_events, + STATIC_ARRAY_SIZE(g_hw_cache_events)); + if (ret != 0) { + ERROR(PMU_PLUGIN ": Failed to add hw cache events."); + goto init_error; + } + } + + if (g_ctx.kernel_pmu_events) { + ret = pmu_add_events(g_ctx.event_list, PERF_TYPE_HARDWARE, + g_kernel_pmu_events, + STATIC_ARRAY_SIZE(g_kernel_pmu_events)); + if (ret != 0) { + ERROR(PMU_PLUGIN ": Failed to add kernel PMU events."); + goto init_error; + } + } + + /* parse events names if config option is present and is not empty */ + if (g_ctx.hw_events_count) { + + ret = read_events(g_ctx.event_list_fn); + if (ret != 0) { + ERROR(PMU_PLUGIN ": Failed to read event list file '%s'.", + g_ctx.event_list_fn); + return ret; + } + + ret = pmu_add_hw_events(g_ctx.event_list, g_ctx.hw_events, + g_ctx.hw_events_count); + if (ret != 0) { + ERROR(PMU_PLUGIN ": Failed to add hardware events."); + goto init_error; + } + } + + if (g_ctx.sw_events) { + ret = pmu_add_events(g_ctx.event_list, PERF_TYPE_SOFTWARE, g_sw_events, + STATIC_ARRAY_SIZE(g_sw_events)); + if (ret != 0) { + ERROR(PMU_PLUGIN ": Failed to add software events."); + goto init_error; + } + } + +#if COLLECT_DEBUG + pmu_dump_events(); +#endif + + if (g_ctx.event_list->eventlist != NULL) { + /* measure all processes */ + ret = pmu_setup_events(g_ctx.event_list, true, -1); + if (ret != 0) { + ERROR(PMU_PLUGIN ": Failed to setup perf events for the event list."); + goto init_error; + } + } else { + WARNING(PMU_PLUGIN + ": Events list is empty. No events were setup for monitoring."); + } + + return 0; + +init_error: + + pmu_free_events(g_ctx.event_list); + sfree(g_ctx.event_list); + for (size_t i = 0; i < g_ctx.hw_events_count; i++) { + sfree(g_ctx.hw_events[i]); + } + sfree(g_ctx.hw_events); + g_ctx.hw_events_count = 0; + + return ret; +} + +static int pmu_shutdown(void) { + + DEBUG(PMU_PLUGIN ": %s:%d", __FUNCTION__, __LINE__); + + pmu_free_events(g_ctx.event_list); + sfree(g_ctx.event_list); + for (size_t i = 0; i < g_ctx.hw_events_count; i++) { + sfree(g_ctx.hw_events[i]); + } + sfree(g_ctx.hw_events); + g_ctx.hw_events_count = 0; + + return 0; +} + +void module_register(void) { + plugin_register_init(PMU_PLUGIN, pmu_init); + plugin_register_complex_config(PMU_PLUGIN, pmu_config); + plugin_register_complex_read(NULL, PMU_PLUGIN, pmu_read, 0, NULL); + plugin_register_shutdown(PMU_PLUGIN, pmu_shutdown); +} diff --git a/src/ipmi.c b/src/ipmi.c index c8c80429..f28ac52c 100644 --- a/src/ipmi.c +++ b/src/ipmi.c @@ -21,6 +21,7 @@ * Florian octo Forster * Peter Holik * Bruno Prémont + * Pavel Rochnyak **/ #include "collectd.h" @@ -29,57 +30,83 @@ #include "plugin.h" #include "utils_ignorelist.h" +#include #include #include +#include #include #include #include +#define ERR_BUF_SIZE 1024 + /* * Private data types */ struct c_ipmi_sensor_list_s; typedef struct c_ipmi_sensor_list_s c_ipmi_sensor_list_t; +struct c_ipmi_instance_s { + char *name; + ignorelist_t *ignorelist; + _Bool notify_add; + _Bool notify_remove; + _Bool notify_notpresent; + _Bool notify_conn; + _Bool sel_enabled; + _Bool sel_clear_event; + + char *host; + char *connaddr; + char *username; + char *password; + unsigned int authtype; + + _Bool connected; + ipmi_con_t *connection; + pthread_mutex_t sensor_list_lock; + c_ipmi_sensor_list_t *sensor_list; + + _Bool active; + pthread_t thread_id; + int init_in_progress; + + struct c_ipmi_instance_s *next; +}; +typedef struct c_ipmi_instance_s c_ipmi_instance_t; + struct c_ipmi_sensor_list_s { ipmi_sensor_id_t sensor_id; char sensor_name[DATA_MAX_NAME_LEN]; char sensor_type[DATA_MAX_NAME_LEN]; + char type_instance[DATA_MAX_NAME_LEN]; int sensor_not_present; c_ipmi_sensor_list_t *next; + c_ipmi_instance_t *instance; + unsigned int use; }; +struct c_ipmi_db_type_map_s { + enum ipmi_unit_type_e type; + const char *type_name; +}; +typedef struct c_ipmi_db_type_map_s c_ipmi_db_type_map_t; + /* * Module global variables */ -static pthread_mutex_t sensor_list_lock = PTHREAD_MUTEX_INITIALIZER; -static c_ipmi_sensor_list_t *sensor_list = NULL; - -static int c_ipmi_init_in_progress = 0; -static int c_ipmi_active = 0; -static pthread_t thread_id = (pthread_t)0; - -static const char *config_keys[] = {"Sensor", "IgnoreSelected", - "NotifySensorAdd", "NotifySensorRemove", - "NotifySensorNotPresent"}; -static int config_keys_num = STATIC_ARRAY_SIZE(config_keys); - -static ignorelist_t *ignorelist = NULL; - -static int c_ipmi_nofiy_add = 0; -static int c_ipmi_nofiy_remove = 0; -static int c_ipmi_nofiy_notpresent = 0; +static os_handler_t *os_handler = NULL; +static c_ipmi_instance_t *instances = NULL; /* * Misc private functions */ -static void c_ipmi_error(const char *func, int status) { - char errbuf[4096] = {0}; +static void c_ipmi_error(c_ipmi_instance_t *st, const char *func, int status) { + char errbuf[ERR_BUF_SIZE] = {0}; - if (IPMI_IS_OS_ERR(status)) { - sstrerror(IPMI_GET_OS_ERR(status), errbuf, sizeof(errbuf)); - } else if (IPMI_IS_IPMI_ERR(status)) { - ipmi_get_error_string(IPMI_GET_IPMI_ERR(status), errbuf, sizeof(errbuf)); + if (IPMI_IS_OS_ERR(status) || IPMI_IS_RMCPP_ERR(status) || + IPMI_IS_IPMI_ERR(status)) { + ipmi_get_error_string(status, errbuf, sizeof(errbuf)); } if (errbuf[0] == 0) { @@ -87,40 +114,90 @@ static void c_ipmi_error(const char *func, int status) { } errbuf[sizeof(errbuf) - 1] = 0; - ERROR("ipmi plugin: %s failed: %s", func, errbuf); + ERROR("ipmi plugin: %s failed for `%s`: %s", func, st->name, errbuf); } /* void c_ipmi_error */ +static void c_ipmi_log(os_handler_t *handler, const char *format, + enum ipmi_log_type_e log_type, va_list ap) { + char msg[ERR_BUF_SIZE]; + + vsnprintf(msg, sizeof(msg), format, ap); + + switch (log_type) { + case IPMI_LOG_INFO: + INFO("ipmi plugin: %s", msg); + break; + case IPMI_LOG_WARNING: + NOTICE("ipmi plugin: %s", msg); + break; + case IPMI_LOG_SEVERE: + WARNING("ipmi plugin: %s", msg); + break; + case IPMI_LOG_FATAL: + ERROR("ipmi plugin: %s", msg); + break; + case IPMI_LOG_ERR_INFO: + ERROR("ipmi plugin: %s", msg); + break; +#if COLLECT_DEBUG + case IPMI_LOG_DEBUG_START: + case IPMI_LOG_DEBUG: + DEBUG("ipmi plugin: %s", msg); + break; + case IPMI_LOG_DEBUG_CONT: + case IPMI_LOG_DEBUG_END: + DEBUG("%s", msg); + break; +#else + case IPMI_LOG_DEBUG_START: + case IPMI_LOG_DEBUG: + case IPMI_LOG_DEBUG_CONT: + case IPMI_LOG_DEBUG_END: + break; +#endif + } +} /* void c_ipmi_log */ + +static notification_t c_ipmi_notification_init(c_ipmi_instance_t const *st, + int severity) { + notification_t n = {severity, cdtime(), "", "", "ipmi", "", "", "", NULL}; + + sstrncpy(n.host, (st->host != NULL) ? st->host : hostname_g, sizeof(n.host)); + return n; +} /* notification_t c_ipmi_notification_init */ + /* * Sensor handlers */ /* Prototype for sensor_list_remove, so sensor_read_handler can call it. */ -static int sensor_list_remove(ipmi_sensor_t *sensor); +static int sensor_list_remove(c_ipmi_instance_t *st, ipmi_sensor_t *sensor); static void sensor_read_handler(ipmi_sensor_t *sensor, int err, enum ipmi_value_present_e value_present, unsigned int __attribute__((unused)) raw_value, - double value, - ipmi_states_t __attribute__((unused)) * states, + double value, ipmi_states_t *states, void *user_data) { value_list_t vl = VALUE_LIST_INIT; - c_ipmi_sensor_list_t *list_item = (c_ipmi_sensor_list_t *)user_data; + c_ipmi_sensor_list_t *list_item = user_data; + c_ipmi_instance_t *st = list_item->instance; + + list_item->use--; if (err != 0) { - if ((err & 0xff) == IPMI_NOT_PRESENT_CC) { + if (IPMI_IS_IPMI_ERR(err) && + IPMI_GET_IPMI_ERR(err) == IPMI_NOT_PRESENT_CC) { if (list_item->sensor_not_present == 0) { list_item->sensor_not_present = 1; - INFO("ipmi plugin: sensor_read_handler: sensor %s " + INFO("ipmi plugin: sensor_read_handler: sensor `%s` of `%s` " "not present.", - list_item->sensor_name); + list_item->sensor_name, st->name); - if (c_ipmi_nofiy_notpresent) { - notification_t n = { - NOTIF_WARNING, cdtime(), "", "", "ipmi", "", "", "", NULL}; + if (st->notify_notpresent) { + notification_t n = c_ipmi_notification_init(st, NOTIF_WARNING); - sstrncpy(n.host, hostname_g, sizeof(n.host)); - sstrncpy(n.type_instance, list_item->sensor_name, + sstrncpy(n.type_instance, list_item->type_instance, sizeof(n.type_instance)); sstrncpy(n.type, list_item->sensor_type, sizeof(n.type)); snprintf(n.message, sizeof(n.message), "sensor %s not present", @@ -132,44 +209,48 @@ static void sensor_read_handler(ipmi_sensor_t *sensor, int err, } else if (IPMI_IS_IPMI_ERR(err) && IPMI_GET_IPMI_ERR(err) == IPMI_NOT_SUPPORTED_IN_PRESENT_STATE_CC) { - INFO("ipmi plugin: sensor_read_handler: Sensor %s not ready", - list_item->sensor_name); + INFO("ipmi plugin: sensor_read_handler: Sensor `%s` of `%s` not ready.", + list_item->sensor_name, st->name); + } else if (IPMI_IS_IPMI_ERR(err) && + IPMI_GET_IPMI_ERR(err) == IPMI_TIMEOUT_CC) { + INFO("ipmi plugin: sensor_read_handler: Sensor `%s` of `%s` timed out.", + list_item->sensor_name, st->name); } else { + char errbuf[ERR_BUF_SIZE] = {0}; + ipmi_get_error_string(err, errbuf, sizeof(errbuf) - 1); + if (IPMI_IS_IPMI_ERR(err)) - INFO("ipmi plugin: sensor_read_handler: Removing sensor %s, " - "because it failed with IPMI error %#x.", - list_item->sensor_name, IPMI_GET_IPMI_ERR(err)); + INFO("ipmi plugin: sensor_read_handler: Sensor `%s` of `%s` failed: " + "%s.", + list_item->sensor_name, st->name, errbuf); else if (IPMI_IS_OS_ERR(err)) - INFO("ipmi plugin: sensor_read_handler: Removing sensor %s, " - "because it failed with OS error %#x.", - list_item->sensor_name, IPMI_GET_OS_ERR(err)); + INFO("ipmi plugin: sensor_read_handler: Sensor `%s` of `%s` failed: " + "%s (%#x).", + list_item->sensor_name, st->name, errbuf, IPMI_GET_OS_ERR(err)); else if (IPMI_IS_RMCPP_ERR(err)) - INFO("ipmi plugin: sensor_read_handler: Removing sensor %s, " - "because it failed with RMCPP error %#x.", - list_item->sensor_name, IPMI_GET_RMCPP_ERR(err)); + INFO("ipmi plugin: sensor_read_handler: Sensor `%s` of `%s` failed: " + "%s.", + list_item->sensor_name, st->name, errbuf); else if (IPMI_IS_SOL_ERR(err)) - INFO("ipmi plugin: sensor_read_handler: Removing sensor %s, " - "because it failed with RMCPP error %#x.", - list_item->sensor_name, IPMI_GET_SOL_ERR(err)); + INFO("ipmi plugin: sensor_read_handler: Sensor `%s` of `%s` failed: " + "%s (%#x).", + list_item->sensor_name, st->name, errbuf, IPMI_GET_SOL_ERR(err)); else - INFO("ipmi plugin: sensor_read_handler: Removing sensor %s, " - "because it failed with error %#x. of class %#x", - list_item->sensor_name, err & 0xff, err & 0xffffff00); - sensor_list_remove(sensor); + INFO("ipmi plugin: sensor_read_handler: Sensor `%s` of `%s` failed " + "with error %#x. of class %#x", + list_item->sensor_name, st->name, err & 0xff, err & 0xffffff00); } return; } else if (list_item->sensor_not_present == 1) { list_item->sensor_not_present = 0; - INFO("ipmi plugin: sensor_read_handler: sensor %s present.", - list_item->sensor_name); + INFO("ipmi plugin: sensor_read_handler: sensor `%s` of `%s` present.", + list_item->sensor_name, st->name); - if (c_ipmi_nofiy_notpresent) { - notification_t n = {NOTIF_OKAY, cdtime(), "", "", "ipmi", - "", "", "", NULL}; + if (st->notify_notpresent) { + notification_t n = c_ipmi_notification_init(st, NOTIF_OKAY); - sstrncpy(n.host, hostname_g, sizeof(n.host)); - sstrncpy(n.type_instance, list_item->sensor_name, + sstrncpy(n.type_instance, list_item->type_instance, sizeof(n.type_instance)); sstrncpy(n.type, list_item->sensor_type, sizeof(n.type)); snprintf(n.message, sizeof(n.message), "sensor %s present", @@ -180,84 +261,167 @@ static void sensor_read_handler(ipmi_sensor_t *sensor, int err, } if (value_present != IPMI_BOTH_VALUES_PRESENT) { - INFO("ipmi plugin: sensor_read_handler: Removing sensor %s, " + INFO("ipmi plugin: sensor_read_handler: Removing sensor `%s` of `%s`, " "because it provides %s. If you need this sensor, " "please file a bug report.", - list_item->sensor_name, + list_item->sensor_name, st->name, (value_present == IPMI_RAW_VALUE_PRESENT) ? "only the raw value" : "no value"); - sensor_list_remove(sensor); + sensor_list_remove(st, sensor); + return; + } + + if (!ipmi_is_sensor_scanning_enabled(states)) { + DEBUG("ipmi plugin: sensor_read_handler: Skipping sensor `%s` of `%s`, " + "it is in 'scanning disabled' state.", + list_item->sensor_name, st->name); + return; + } + + if (ipmi_is_initial_update_in_progress(states)) { + DEBUG("ipmi plugin: sensor_read_handler: Skipping sensor `%s` of `%s`, " + "it is in 'initial update in progress' state.", + list_item->sensor_name, st->name); return; } vl.values = &(value_t){.gauge = value}; vl.values_len = 1; + if (st->host != NULL) + sstrncpy(vl.host, st->host, sizeof(vl.host)); sstrncpy(vl.plugin, "ipmi", sizeof(vl.plugin)); sstrncpy(vl.type, list_item->sensor_type, sizeof(vl.type)); - sstrncpy(vl.type_instance, list_item->sensor_name, sizeof(vl.type_instance)); + sstrncpy(vl.type_instance, list_item->type_instance, + sizeof(vl.type_instance)); plugin_dispatch_values(&vl); } /* void sensor_read_handler */ -static int sensor_list_add(ipmi_sensor_t *sensor) { +static void sensor_get_name(ipmi_sensor_t *sensor, char *buffer, int buf_len) { + char temp[DATA_MAX_NAME_LEN] = {0}; + ipmi_entity_t *ent = ipmi_sensor_get_entity(sensor); + const char *entity_id_string = ipmi_entity_get_entity_id_string(ent); + char sensor_name[DATA_MAX_NAME_LEN] = ""; + char *sensor_name_ptr; + + if ((buffer == NULL) || (buf_len == 0)) + return; + + ipmi_sensor_get_name(sensor, temp, sizeof(temp)); + temp[sizeof(temp) - 1] = 0; + + if (entity_id_string != NULL && strlen(temp)) + snprintf(sensor_name, sizeof(sensor_name), "%s %s", temp, entity_id_string); + else if (entity_id_string != NULL) + sstrncpy(sensor_name, entity_id_string, sizeof(sensor_name)); + else + sstrncpy(sensor_name, temp, sizeof(sensor_name)); + + if (strlen(temp)) { + sstrncpy(temp, sensor_name, sizeof(temp)); + sensor_name_ptr = strstr(temp, ")."); + if (sensor_name_ptr != NULL) { + /* If name is something like "foo (123).bar", + * change that to "bar (123)". + * Both, sensor_name_ptr and sensor_id_ptr point to memory within the + * `temp' array, which holds a copy of the current `sensor_name'. */ + char *sensor_id_ptr; + + /* `sensor_name_ptr' points to ").bar". */ + sensor_name_ptr[1] = 0; + /* `temp' holds "foo (123)\0bar\0". */ + sensor_name_ptr += 2; + /* `sensor_name_ptr' now points to "bar". */ + + sensor_id_ptr = strstr(temp, "("); + if (sensor_id_ptr != NULL) { + /* `sensor_id_ptr' now points to "(123)". */ + snprintf(sensor_name, sizeof(sensor_name), "%s %s", sensor_name_ptr, + sensor_id_ptr); + } + /* else: don't touch sensor_name. */ + } + } + sstrncpy(buffer, sensor_name, buf_len); +} + +static const char *sensor_unit_to_type(ipmi_sensor_t *sensor) { + static const c_ipmi_db_type_map_t ipmi_db_type_map[] = { + {IPMI_UNIT_TYPE_WATTS, "power"}, {IPMI_UNIT_TYPE_CFM, "flow"}}; + + /* check the modifier and rate of the sensor value */ + if ((ipmi_sensor_get_modifier_unit_use(sensor) != IPMI_MODIFIER_UNIT_NONE) || + (ipmi_sensor_get_rate_unit(sensor) != IPMI_RATE_UNIT_NONE)) + return NULL; + + /* find the db type by using sensor base unit type */ + enum ipmi_unit_type_e ipmi_type = ipmi_sensor_get_base_unit(sensor); + for (int i = 0; i < STATIC_ARRAY_SIZE(ipmi_db_type_map); i++) + if (ipmi_db_type_map[i].type == ipmi_type) + return ipmi_db_type_map[i].type_name; + + return NULL; +} /* const char* sensor_unit_to_type */ + +static int sensor_list_add(c_ipmi_instance_t *st, ipmi_sensor_t *sensor) { ipmi_sensor_id_t sensor_id; c_ipmi_sensor_list_t *list_item; c_ipmi_sensor_list_t *list_prev; char buffer[DATA_MAX_NAME_LEN] = {0}; - const char *entity_id_string; - char sensor_name[DATA_MAX_NAME_LEN]; - char *sensor_name_ptr; + char *sensor_name_ptr = buffer; int sensor_type; const char *type; - ipmi_entity_t *ent = ipmi_sensor_get_entity(sensor); sensor_id = ipmi_sensor_convert_to_id(sensor); + sensor_get_name(sensor, buffer, sizeof(buffer)); + + DEBUG("ipmi plugin: sensor_list_add: Found sensor `%s` of `%s`," + " Type: %#x" + " Event reading type: %#x" + " Direction: %#x" + " Event support: %#x", + sensor_name_ptr, st->name, ipmi_sensor_get_sensor_type(sensor), + ipmi_sensor_get_event_reading_type(sensor), + ipmi_sensor_get_sensor_direction(sensor), + ipmi_sensor_get_event_support(sensor)); + + /* Both `ignorelist' and `sensor_name_ptr' may be NULL. */ + if (ignorelist_match(st->ignorelist, sensor_name_ptr) != 0) + return 0; - ipmi_sensor_get_name(sensor, buffer, sizeof(buffer)); - buffer[sizeof(buffer) - 1] = 0; + /* FIXME: Use rate unit or base unit to scale the value */ - entity_id_string = ipmi_entity_get_entity_id_string(ent); + sensor_type = ipmi_sensor_get_sensor_type(sensor); - if (entity_id_string == NULL) - sstrncpy(sensor_name, buffer, sizeof(sensor_name)); - else - snprintf(sensor_name, sizeof(sensor_name), "%s %s", buffer, - entity_id_string); - - sstrncpy(buffer, sensor_name, sizeof(buffer)); - sensor_name_ptr = strstr(buffer, ")."); - if (sensor_name_ptr != NULL) { - /* If name is something like "foo (123).bar", - * change that to "bar (123)". - * Both, sensor_name_ptr and sensor_id_ptr point to memory within the - * `buffer' array, which holds a copy of the current `sensor_name'. */ - char *sensor_id_ptr; - - /* `sensor_name_ptr' points to ").bar". */ - sensor_name_ptr[1] = 0; - /* `buffer' holds "foo (123)\0bar\0". */ - sensor_name_ptr += 2; - /* `sensor_name_ptr' now points to "bar". */ - - sensor_id_ptr = strstr(buffer, "("); - if (sensor_id_ptr != NULL) { - /* `sensor_id_ptr' now points to "(123)". */ - snprintf(sensor_name, sizeof(sensor_name), "%s %s", sensor_name_ptr, - sensor_id_ptr); - } - /* else: don't touch sensor_name. */ + /* + * ipmitool/lib/ipmi_sdr.c sdr_sensor_has_analog_reading() has a notice + * about 'Threshold sensors' and 'analog readings'. Discrete sensor may + * have analog data, but discrete sensors support is not implemented + * in Collectd yet. + * + * ipmi_sensor_id_get_reading() supports only 'Threshold' sensors. + * See lib/sensor.c:4842, stand_ipmi_sensor_get_reading() for details. + */ + if (!ipmi_sensor_get_is_readable(sensor)) { + INFO("ipmi plugin: sensor_list_add: Ignore sensor `%s` of `%s`, " + "because it isn't readable! Its type: (%#x, %s). ", + sensor_name_ptr, st->name, sensor_type, + ipmi_sensor_get_sensor_type_string(sensor)); + return -1; } - sensor_name_ptr = sensor_name; - - /* Both `ignorelist' and `plugin_instance' may be NULL. */ - if (ignorelist_match(ignorelist, sensor_name_ptr) != 0) - return 0; - /* FIXME: Use rate unit or base unit to scale the value */ + if (ipmi_sensor_get_event_reading_type(sensor) != + IPMI_EVENT_READING_TYPE_THRESHOLD) { + INFO("ipmi plugin: sensor_list_add: Ignore sensor `%s` of `%s`, " + "because it is discrete (%#x)! Its type: (%#x, %s). ", + sensor_name_ptr, st->name, sensor_type, + ipmi_sensor_get_event_reading_type(sensor), + ipmi_sensor_get_sensor_type_string(sensor)); + return -1; + } - sensor_type = ipmi_sensor_get_sensor_type(sensor); switch (sensor_type) { case IPMI_SENSOR_TYPE_TEMPERATURE: type = "temperature"; @@ -275,22 +439,31 @@ static int sensor_list_add(ipmi_sensor_t *sensor) { type = "fanspeed"; break; + case IPMI_SENSOR_TYPE_MEMORY: + type = "memory"; + break; + default: { - const char *sensor_type_str; + /* try to get collectd DB type based on sensor base unit type */ + if ((type = sensor_unit_to_type(sensor)) != NULL) + break; - sensor_type_str = ipmi_sensor_get_sensor_type_string(sensor); - INFO("ipmi plugin: sensor_list_add: Ignore sensor %s, " - "because I don't know how to handle its type (%#x, %s). " - "If you need this sensor, please file a bug report.", - sensor_name_ptr, sensor_type, sensor_type_str); + INFO("ipmi plugin: sensor_list_add: Ignore sensor `%s` of `%s`, " + "because I don't know how to handle its units (%#x, %#x, %#x). " + "Sensor type: (%#x, %s). If you need this sensor, please file " + "a bug report at http://collectd.org/.", + sensor_name_ptr, st->name, ipmi_sensor_get_base_unit(sensor), + ipmi_sensor_get_modifier_unit(sensor), + ipmi_sensor_get_rate_unit(sensor), sensor_type, + ipmi_sensor_get_sensor_type_string(sensor)); return -1; } } /* switch (sensor_type) */ - pthread_mutex_lock(&sensor_list_lock); + pthread_mutex_lock(&st->sensor_list_lock); list_prev = NULL; - for (list_item = sensor_list; list_item != NULL; + for (list_item = st->sensor_list; list_item != NULL; list_item = list_item->next) { if (ipmi_cmp_sensor_id(sensor_id, list_item->sensor_id) == 0) break; @@ -298,34 +471,47 @@ static int sensor_list_add(ipmi_sensor_t *sensor) { } /* for (list_item) */ if (list_item != NULL) { - pthread_mutex_unlock(&sensor_list_lock); + pthread_mutex_unlock(&st->sensor_list_lock); return 0; } list_item = (c_ipmi_sensor_list_t *)calloc(1, sizeof(c_ipmi_sensor_list_t)); if (list_item == NULL) { - pthread_mutex_unlock(&sensor_list_lock); + pthread_mutex_unlock(&st->sensor_list_lock); return -1; } + list_item->instance = st; list_item->sensor_id = ipmi_sensor_convert_to_id(sensor); if (list_prev != NULL) list_prev->next = list_item; else - sensor_list = list_item; + st->sensor_list = list_item; + + /* if sensor provides the percentage value, use "percent" collectd type + and add the `percent` to the type instance of the reported value */ + if (ipmi_sensor_get_percentage(sensor)) { + snprintf(list_item->type_instance, sizeof(list_item->type_instance), + "percent-%s", sensor_name_ptr); + type = "percent"; + } else { + /* use type instance as a name of the sensor */ + sstrncpy(list_item->type_instance, sensor_name_ptr, + sizeof(list_item->type_instance)); + } sstrncpy(list_item->sensor_name, sensor_name_ptr, sizeof(list_item->sensor_name)); sstrncpy(list_item->sensor_type, type, sizeof(list_item->sensor_type)); - pthread_mutex_unlock(&sensor_list_lock); + pthread_mutex_unlock(&st->sensor_list_lock); - if (c_ipmi_nofiy_add && (c_ipmi_init_in_progress == 0)) { - notification_t n = {NOTIF_OKAY, cdtime(), "", "", "ipmi", "", "", "", NULL}; + if (st->notify_add && (st->init_in_progress == 0)) { + notification_t n = c_ipmi_notification_init(st, NOTIF_OKAY); - sstrncpy(n.host, hostname_g, sizeof(n.host)); - sstrncpy(n.type_instance, list_item->sensor_name, sizeof(n.type_instance)); + sstrncpy(n.type_instance, list_item->type_instance, + sizeof(n.type_instance)); sstrncpy(n.type, list_item->sensor_type, sizeof(n.type)); snprintf(n.message, sizeof(n.message), "sensor %s added", list_item->sensor_name); @@ -336,17 +522,17 @@ static int sensor_list_add(ipmi_sensor_t *sensor) { return 0; } /* int sensor_list_add */ -static int sensor_list_remove(ipmi_sensor_t *sensor) { +static int sensor_list_remove(c_ipmi_instance_t *st, ipmi_sensor_t *sensor) { ipmi_sensor_id_t sensor_id; c_ipmi_sensor_list_t *list_item; c_ipmi_sensor_list_t *list_prev; sensor_id = ipmi_sensor_convert_to_id(sensor); - pthread_mutex_lock(&sensor_list_lock); + pthread_mutex_lock(&st->sensor_list_lock); list_prev = NULL; - for (list_item = sensor_list; list_item != NULL; + for (list_item = st->sensor_list; list_item != NULL; list_item = list_item->next) { if (ipmi_cmp_sensor_id(sensor_id, list_item->sensor_id) == 0) break; @@ -354,26 +540,25 @@ static int sensor_list_remove(ipmi_sensor_t *sensor) { } /* for (list_item) */ if (list_item == NULL) { - pthread_mutex_unlock(&sensor_list_lock); + pthread_mutex_unlock(&st->sensor_list_lock); return -1; } if (list_prev == NULL) - sensor_list = list_item->next; + st->sensor_list = list_item->next; else list_prev->next = list_item->next; list_prev = NULL; list_item->next = NULL; - pthread_mutex_unlock(&sensor_list_lock); + pthread_mutex_unlock(&st->sensor_list_lock); - if (c_ipmi_nofiy_remove && c_ipmi_active) { - notification_t n = {NOTIF_WARNING, cdtime(), "", "", "ipmi", "", "", "", - NULL}; + if (st->notify_remove && st->active) { + notification_t n = c_ipmi_notification_init(st, NOTIF_WARNING); - sstrncpy(n.host, hostname_g, sizeof(n.host)); - sstrncpy(n.type_instance, list_item->sensor_name, sizeof(n.type_instance)); + sstrncpy(n.type_instance, list_item->type_instance, + sizeof(n.type_instance)); sstrncpy(n.type, list_item->sensor_type, sizeof(n.type)); snprintf(n.message, sizeof(n.message), "sensor %s removed", list_item->sensor_name); @@ -385,29 +570,37 @@ static int sensor_list_remove(ipmi_sensor_t *sensor) { return 0; } /* int sensor_list_remove */ -static int sensor_list_read_all(void) { - pthread_mutex_lock(&sensor_list_lock); +static int sensor_list_read_all(c_ipmi_instance_t *st) { + pthread_mutex_lock(&st->sensor_list_lock); - for (c_ipmi_sensor_list_t *list_item = sensor_list; list_item != NULL; + for (c_ipmi_sensor_list_t *list_item = st->sensor_list; list_item != NULL; list_item = list_item->next) { + DEBUG("ipmi plugin: try read sensor `%s` of `%s`, use: %d", + list_item->sensor_name, st->name, list_item->use); + + /* Reading already initiated */ + if (list_item->use) + continue; + + list_item->use++; ipmi_sensor_id_get_reading(list_item->sensor_id, sensor_read_handler, - /* user data = */ list_item); + /* user data = */ (void *)list_item); } /* for (list_item) */ - pthread_mutex_unlock(&sensor_list_lock); + pthread_mutex_unlock(&st->sensor_list_lock); return 0; } /* int sensor_list_read_all */ -static int sensor_list_remove_all(void) { +static int sensor_list_remove_all(c_ipmi_instance_t *st) { c_ipmi_sensor_list_t *list_item; - pthread_mutex_lock(&sensor_list_lock); + pthread_mutex_lock(&st->sensor_list_lock); - list_item = sensor_list; - sensor_list = NULL; + list_item = st->sensor_list; + st->sensor_list = NULL; - pthread_mutex_unlock(&sensor_list_lock); + pthread_mutex_unlock(&st->sensor_list_lock); while (list_item != NULL) { c_ipmi_sensor_list_t *list_next = list_item->next; @@ -420,204 +613,679 @@ static int sensor_list_remove_all(void) { return 0; } /* int sensor_list_remove_all */ +static int sensor_convert_threshold_severity(enum ipmi_thresh_e severity) { + switch (severity) { + case IPMI_LOWER_NON_CRITICAL: + case IPMI_UPPER_NON_CRITICAL: + return NOTIF_OKAY; + case IPMI_LOWER_CRITICAL: + case IPMI_UPPER_CRITICAL: + return NOTIF_WARNING; + case IPMI_LOWER_NON_RECOVERABLE: + case IPMI_UPPER_NON_RECOVERABLE: + return NOTIF_FAILURE; + default: + return NOTIF_OKAY; + } /* switch (severity) */ +} /* int sensor_convert_threshold_severity */ + +static void add_event_common_data(notification_t *n, ipmi_sensor_t *sensor, + enum ipmi_event_dir_e dir, + ipmi_event_t *event) { + ipmi_entity_t *ent = ipmi_sensor_get_entity(sensor); + + plugin_notification_meta_add_string(n, "entity_name", + ipmi_entity_get_entity_id_string(ent)); + plugin_notification_meta_add_signed_int(n, "entity_id", + ipmi_entity_get_entity_id(ent)); + plugin_notification_meta_add_signed_int(n, "entity_instance", + ipmi_entity_get_entity_instance(ent)); + plugin_notification_meta_add_boolean(n, "assert", dir == IPMI_ASSERTION); + + if (event) + plugin_notification_meta_add_signed_int(n, "event_type", + ipmi_event_get_type(event)); +} /* void add_event_sensor_meta_data */ + +static int sensor_threshold_event_handler( + ipmi_sensor_t *sensor, enum ipmi_event_dir_e dir, + enum ipmi_thresh_e threshold, enum ipmi_event_value_dir_e high_low, + enum ipmi_value_present_e value_present, unsigned int raw_value, + double value, void *cb_data, ipmi_event_t *event) { + + c_ipmi_instance_t *st = cb_data; + + /* From the IPMI specification Chapter 2: Events. + * If a callback handles the event, then all future callbacks called due to + * the event will receive a NULL for the event. So be ready to handle a NULL + * event in all your event handlers. A NULL may also be passed to an event + * handler if the callback was not due to an event. */ + if (event == NULL) + return IPMI_EVENT_NOT_HANDLED; + + notification_t n = c_ipmi_notification_init(st, NOTIF_OKAY); + /* offset is a table index and it's represented as enum of strings that are + organized in the way - high and low for each threshold severity level */ + unsigned int offset = (2 * threshold) + high_low; + unsigned int event_type = ipmi_sensor_get_event_reading_type(sensor); + unsigned int sensor_type = ipmi_sensor_get_sensor_type(sensor); + const char *event_state = + ipmi_get_reading_name(event_type, sensor_type, offset); + sensor_get_name(sensor, n.type_instance, sizeof(n.type_instance)); + if (value_present != IPMI_NO_VALUES_PRESENT) + snprintf(n.message, sizeof(n.message), + "sensor %s received event: %s, value is %f", n.type_instance, + event_state, value); + else + snprintf(n.message, sizeof(n.message), + "sensor %s received event: %s, value not provided", + n.type_instance, event_state); + + DEBUG("Threshold event received for sensor %s", n.type_instance); + + sstrncpy(n.type, ipmi_sensor_get_sensor_type_string(sensor), sizeof(n.type)); + n.severity = sensor_convert_threshold_severity(threshold); + n.time = NS_TO_CDTIME_T(ipmi_event_get_timestamp(event)); + + plugin_notification_meta_add_string(&n, "severity", + ipmi_get_threshold_string(threshold)); + plugin_notification_meta_add_string(&n, "direction", + ipmi_get_value_dir_string(high_low)); + + switch (value_present) { + case IPMI_BOTH_VALUES_PRESENT: + plugin_notification_meta_add_double(&n, "val", value); + /* both values present, so fall-through to add raw value too */ + case IPMI_RAW_VALUE_PRESENT: { + char buf[DATA_MAX_NAME_LEN] = {0}; + snprintf(buf, sizeof(buf), "0x%2.2x", raw_value); + plugin_notification_meta_add_string(&n, "raw", buf); + } break; + default: + break; + } /* switch (value_present) */ + + add_event_common_data(&n, sensor, dir, event); + + plugin_dispatch_notification(&n); + plugin_notification_meta_free(n.meta); + + /* Delete handled ipmi event from the list */ + if (st->sel_clear_event) { + ipmi_event_delete(event, NULL, NULL); + return IPMI_EVENT_HANDLED; + } + + return IPMI_EVENT_NOT_HANDLED; +} /* int sensor_threshold_event_handler */ + +static int sensor_discrete_event_handler(ipmi_sensor_t *sensor, + enum ipmi_event_dir_e dir, int offset, + int severity, int prev_severity, + void *cb_data, ipmi_event_t *event) { + + c_ipmi_instance_t *st = cb_data; + + /* From the IPMI specification Chapter 2: Events. + * If a callback handles the event, then all future callbacks called due to + * the event will receive a NULL for the event. So be ready to handle a NULL + * event in all your event handlers. A NULL may also be passed to an event + * handler if the callback was not due to an event. */ + if (event == NULL) + return IPMI_EVENT_NOT_HANDLED; + + notification_t n = c_ipmi_notification_init(st, NOTIF_OKAY); + unsigned int event_type = ipmi_sensor_get_event_reading_type(sensor); + unsigned int sensor_type = ipmi_sensor_get_sensor_type(sensor); + const char *event_state = + ipmi_get_reading_name(event_type, sensor_type, offset); + sensor_get_name(sensor, n.type_instance, sizeof(n.type_instance)); + snprintf(n.message, sizeof(n.message), "sensor %s received event: %s", + n.type_instance, event_state); + + DEBUG("Discrete event received for sensor %s", n.type_instance); + + sstrncpy(n.type, ipmi_sensor_get_sensor_type_string(sensor), sizeof(n.type)); + n.time = NS_TO_CDTIME_T(ipmi_event_get_timestamp(event)); + + plugin_notification_meta_add_signed_int(&n, "offset", offset); + + if (severity != -1) + plugin_notification_meta_add_signed_int(&n, "severity", severity); + + if (prev_severity != -1) + plugin_notification_meta_add_signed_int(&n, "prevseverity", prev_severity); + + add_event_common_data(&n, sensor, dir, event); + + plugin_dispatch_notification(&n); + plugin_notification_meta_free(n.meta); + + /* Delete handled ipmi event from the list */ + if (st->sel_clear_event) { + ipmi_event_delete(event, NULL, NULL); + return IPMI_EVENT_HANDLED; + } + + return IPMI_EVENT_NOT_HANDLED; +} /* int sensor_discrete_event_handler */ + /* * Entity handlers */ -static void entity_sensor_update_handler( - enum ipmi_update_e op, ipmi_entity_t __attribute__((unused)) * entity, - ipmi_sensor_t *sensor, void __attribute__((unused)) * user_data) { - /* TODO: Ignore sensors we cannot read */ +static void +entity_sensor_update_handler(enum ipmi_update_e op, + ipmi_entity_t __attribute__((unused)) * entity, + ipmi_sensor_t *sensor, void *user_data) { + c_ipmi_instance_t *st = user_data; if ((op == IPMI_ADDED) || (op == IPMI_CHANGED)) { /* Will check for duplicate entries.. */ - sensor_list_add(sensor); + sensor_list_add(st, sensor); + + if (st->sel_enabled) { + int status = 0; + /* register threshold event if threshold sensor support events */ + if ((ipmi_sensor_get_event_reading_type(sensor) == + IPMI_EVENT_READING_TYPE_THRESHOLD) && + (ipmi_sensor_get_threshold_access(sensor) != + IPMI_THRESHOLD_ACCESS_SUPPORT_NONE)) + status = ipmi_sensor_add_threshold_event_handler( + sensor, sensor_threshold_event_handler, st); + /* register discrete handler if discrete/specific sensor support events */ + else if (ipmi_sensor_get_event_support(sensor) != IPMI_EVENT_SUPPORT_NONE) + status = ipmi_sensor_add_discrete_event_handler( + sensor, sensor_discrete_event_handler, st); + + if (status) { + char buf[DATA_MAX_NAME_LEN] = {0}; + sensor_get_name(sensor, buf, sizeof(buf)); + ERROR("Unable to add sensor %s event handler, status: %d", buf, status); + } + } } else if (op == IPMI_DELETED) { - sensor_list_remove(sensor); + sensor_list_remove(st, sensor); + + if (st->sel_enabled) { + if (ipmi_sensor_get_event_reading_type(sensor) == + IPMI_EVENT_READING_TYPE_THRESHOLD) + ipmi_sensor_remove_threshold_event_handler( + sensor, sensor_threshold_event_handler, st); + else + ipmi_sensor_remove_discrete_event_handler( + sensor, sensor_discrete_event_handler, st); + } } } /* void entity_sensor_update_handler */ /* * Domain handlers */ -static void domain_entity_update_handler( - enum ipmi_update_e op, ipmi_domain_t __attribute__((unused)) * domain, - ipmi_entity_t *entity, void __attribute__((unused)) * user_data) { +static void +domain_entity_update_handler(enum ipmi_update_e op, + ipmi_domain_t __attribute__((unused)) * domain, + ipmi_entity_t *entity, void *user_data) { int status; + c_ipmi_instance_t *st = user_data; if (op == IPMI_ADDED) { status = ipmi_entity_add_sensor_update_handler( - entity, entity_sensor_update_handler, /* user data = */ NULL); + entity, entity_sensor_update_handler, /* user data = */ (void *)st); if (status != 0) { - c_ipmi_error("ipmi_entity_add_sensor_update_handler", status); + c_ipmi_error(st, "ipmi_entity_add_sensor_update_handler", status); } } else if (op == IPMI_DELETED) { status = ipmi_entity_remove_sensor_update_handler( - entity, entity_sensor_update_handler, /* user data = */ NULL); + entity, entity_sensor_update_handler, /* user data = */ (void *)st); if (status != 0) { - c_ipmi_error("ipmi_entity_remove_sensor_update_handler", status); + c_ipmi_error(st, "ipmi_entity_remove_sensor_update_handler", status); } } } /* void domain_entity_update_handler */ +static void smi_event_handler(ipmi_con_t __attribute__((unused)) * ipmi, + const ipmi_addr_t __attribute__((unused)) * addr, + unsigned int __attribute__((unused)) addr_len, + ipmi_event_t *event, void *cb_data) { + unsigned int type = ipmi_event_get_type(event); + ipmi_domain_t *domain = cb_data; + + DEBUG("%s: Event received: type %u", __FUNCTION__, type); + + if (type != 0x02) + /* It's not a standard IPMI event. */ + return; + + /* force domain to reread SELs */ + ipmi_domain_reread_sels(domain, NULL, NULL); +} + static void domain_connection_change_handler(ipmi_domain_t *domain, int err, unsigned int conn_num, unsigned int port_num, int still_connected, void *user_data) { - int status; DEBUG("domain_connection_change_handler (domain = %p, err = %i, " "conn_num = %u, port_num = %u, still_connected = %i, " - "user_data = %p);\n", + "user_data = %p);", (void *)domain, err, conn_num, port_num, still_connected, user_data); - status = ipmi_domain_add_entity_update_handler( - domain, domain_entity_update_handler, /* user data = */ NULL); - if (status != 0) { - c_ipmi_error("ipmi_domain_add_entity_update_handler", status); + c_ipmi_instance_t *st = user_data; + + if (err != 0) + c_ipmi_error(st, "domain_connection_change_handler", err); + + if (!still_connected) { + + if (st->notify_conn && st->connected && st->init_in_progress == 0) { + notification_t n = c_ipmi_notification_init(st, NOTIF_FAILURE); + + sstrncpy(n.message, "IPMI connection lost", sizeof(n.plugin)); + + plugin_dispatch_notification(&n); + } + + st->connected = 0; + return; } -} /* void domain_connection_change_handler */ -static int thread_init(os_handler_t **ret_os_handler) { - os_handler_t *os_handler; - ipmi_con_t *smi_connection = NULL; - ipmi_domain_id_t domain_id; - int status; + if (st->notify_conn && !st->connected && st->init_in_progress == 0) { + notification_t n = c_ipmi_notification_init(st, NOTIF_OKAY); - os_handler = ipmi_posix_thread_setup_os_handler(SIGIO); - if (os_handler == NULL) { - ERROR("ipmi plugin: ipmi_posix_thread_setup_os_handler failed."); - return -1; + sstrncpy(n.message, "IPMI connection restored", sizeof(n.plugin)); + + plugin_dispatch_notification(&n); } - ipmi_init(os_handler); + st->connected = 1; - status = ipmi_smi_setup_con(/* if_num = */ 0, os_handler, - /* user data = */ NULL, &smi_connection); + int status = ipmi_domain_add_entity_update_handler( + domain, domain_entity_update_handler, /* user data = */ st); if (status != 0) { - c_ipmi_error("ipmi_smi_setup_con", status); - return -1; + c_ipmi_error(st, "ipmi_domain_add_entity_update_handler", status); } - ipmi_open_option_t open_option[1] = {[0] = {.option = IPMI_OPEN_OPTION_ALL, - {.ival = 1}}}; + status = st->connection->add_event_handler(st->connection, smi_event_handler, + (void *)domain); + + if (status != 0) + c_ipmi_error(st, "Failed to register smi event handler", status); +} /* void domain_connection_change_handler */ + +static int c_ipmi_thread_init(c_ipmi_instance_t *st) { + ipmi_domain_id_t domain_id; + int status; + + if (st->connaddr != NULL) { + status = ipmi_ip_setup_con( + &st->connaddr, &(char *){IPMI_LAN_STD_PORT_STR}, 1, st->authtype, + (unsigned int)IPMI_PRIVILEGE_USER, st->username, strlen(st->username), + st->password, strlen(st->password), os_handler, + /* user data = */ NULL, &st->connection); + if (status != 0) { + c_ipmi_error(st, "ipmi_ip_setup_con", status); + return -1; + } + } else { + status = ipmi_smi_setup_con(/* if_num = */ 0, os_handler, + /* user data = */ NULL, &st->connection); + if (status != 0) { + c_ipmi_error(st, "ipmi_smi_setup_con", status); + return -1; + } + } + ipmi_open_option_t opts[] = { + {.option = IPMI_OPEN_OPTION_ALL, {.ival = 1}}, +#ifdef IPMI_OPEN_OPTION_USE_CACHE + /* OpenIPMI-2.0.17 and later: Disable SDR cache in local file */ + {.option = IPMI_OPEN_OPTION_USE_CACHE, {.ival = 0}}, +#endif + }; + + /* + * NOTE: Domain names must be unique. There is static `domains_list` common + * to all threads inside lib/domain.c and some ops are done by name. + */ status = ipmi_open_domain( - "mydomain", &smi_connection, /* num_con = */ 1, - domain_connection_change_handler, /* user data = */ NULL, - /* domain_fully_up_handler = */ NULL, /* user data = */ NULL, open_option, - sizeof(open_option) / sizeof(open_option[0]), &domain_id); + st->name, &st->connection, /* num_con = */ 1, + domain_connection_change_handler, /* user data = */ (void *)st, + /* domain_fully_up_handler = */ NULL, /* user data = */ NULL, opts, + STATIC_ARRAY_SIZE(opts), &domain_id); if (status != 0) { - c_ipmi_error("ipmi_open_domain", status); + c_ipmi_error(st, "ipmi_open_domain", status); return -1; } - *ret_os_handler = os_handler; return 0; -} /* int thread_init */ +} /* int c_ipmi_thread_init */ -static void *thread_main(void __attribute__((unused)) * user_data) { - int status; - os_handler_t *os_handler = NULL; +static void *c_ipmi_thread_main(void *user_data) { + c_ipmi_instance_t *st = user_data; - status = thread_init(&os_handler); + int status = c_ipmi_thread_init(st); if (status != 0) { - ERROR("ipmi plugin: thread_init failed.\n"); + ERROR("ipmi plugin: c_ipmi_thread_init failed."); + st->active = 0; return (void *)-1; } - while (c_ipmi_active != 0) { + while (st->active != 0) { struct timeval tv = {1, 0}; os_handler->perform_one_op(os_handler, &tv); } + return (void *)0; +} /* void *c_ipmi_thread_main */ - ipmi_posix_thread_free_os_handler(os_handler); +static c_ipmi_instance_t *c_ipmi_init_instance() { + c_ipmi_instance_t *st; - return (void *)0; -} /* void *thread_main */ - -static int c_ipmi_config(const char *key, const char *value) { - if (ignorelist == NULL) - ignorelist = ignorelist_create(/* invert = */ 1); - if (ignorelist == NULL) - return 1; - - if (strcasecmp("Sensor", key) == 0) { - ignorelist_add(ignorelist, value); - } else if (strcasecmp("IgnoreSelected", key) == 0) { - int invert = 1; - if (IS_TRUE(value)) - invert = 0; - ignorelist_set_invert(ignorelist, invert); - } else if (strcasecmp("NotifySensorAdd", key) == 0) { - if (IS_TRUE(value)) - c_ipmi_nofiy_add = 1; - } else if (strcasecmp("NotifySensorRemove", key) == 0) { - if (IS_TRUE(value)) - c_ipmi_nofiy_remove = 1; - } else if (strcasecmp("NotifySensorNotPresent", key) == 0) { - if (IS_TRUE(value)) - c_ipmi_nofiy_notpresent = 1; - } else { - return -1; + st = calloc(1, sizeof(*st)); + if (st == NULL) { + ERROR("ipmi plugin: calloc failed."); + return NULL; } - return 0; -} /* int c_ipmi_config */ + st->name = strdup("main"); + if (st->name == NULL) { + sfree(st); + ERROR("ipmi plugin: strdup() failed."); + return NULL; + } -static int c_ipmi_init(void) { - int status; + st->ignorelist = ignorelist_create(/* invert = */ 1); + if (st->ignorelist == NULL) { + sfree(st->name); + sfree(st); + ERROR("ipmi plugin: ignorelist_create() failed."); + return NULL; + } - /* Don't send `ADD' notifications during startup (~ 1 minute) */ - time_t iv = CDTIME_T_TO_TIME_T(plugin_get_interval()); - c_ipmi_init_in_progress = 1 + (60 / iv); + st->sensor_list = NULL; + pthread_mutex_init(&st->sensor_list_lock, /* attr = */ NULL); + + st->host = NULL; + st->connaddr = NULL; + st->username = NULL; + st->password = NULL; + st->authtype = IPMI_AUTHTYPE_DEFAULT; + + st->next = NULL; + + return st; +} /* c_ipmi_instance_t *c_ipmi_init_instance */ + +static void c_ipmi_free_instance(c_ipmi_instance_t *st) { + if (st == NULL) + return; - c_ipmi_active = 1; + assert(st->next == NULL); + + sfree(st->name); + sfree(st->host); + sfree(st->connaddr); + sfree(st->username); + sfree(st->password); + + ignorelist_free(st->ignorelist); + pthread_mutex_destroy(&st->sensor_list_lock); + sfree(st); +} /* void c_ipmi_free_instance */ + +static void c_ipmi_add_instance(c_ipmi_instance_t *instance) { + if (instances == NULL) { + instances = instance; + return; + } + + c_ipmi_instance_t *last = instances; + + while (last->next != NULL) + last = last->next; + + last->next = instance; +} /* void c_ipmi_add_instance */ + +static int c_ipmi_config_add_instance(oconfig_item_t *ci) { + int status = 0; + c_ipmi_instance_t *st = c_ipmi_init_instance(); + if (st == NULL) + return ENOMEM; + + if (strcasecmp(ci->key, "Instance") == 0) + status = cf_util_get_string(ci, &st->name); - status = plugin_thread_create(&thread_id, /* attr = */ NULL, thread_main, - /* user data = */ NULL, "ipmi"); if (status != 0) { - c_ipmi_active = 0; - thread_id = (pthread_t)0; - ERROR("ipmi plugin: pthread_create failed."); - return -1; + c_ipmi_free_instance(st); + return status; + } + + for (int i = 0; i < ci->children_num; i++) { + oconfig_item_t *child = ci->children + i; + + if (strcasecmp("Sensor", child->key) == 0) + ignorelist_add(st->ignorelist, ci->values[0].value.string); + else if (strcasecmp("IgnoreSelected", child->key) == 0) { + _Bool t; + status = cf_util_get_boolean(child, &t); + if (status != 0) + break; + ignorelist_set_invert(st->ignorelist, /* invert = */ !t); + } else if (strcasecmp("NotifyIPMIConnectionState", child->key) == 0) { + status = cf_util_get_boolean(child, &st->notify_conn); + } else if (strcasecmp("NotifySensorAdd", child->key) == 0) { + status = cf_util_get_boolean(child, &st->notify_add); + } else if (strcasecmp("NotifySensorRemove", child->key) == 0) { + status = cf_util_get_boolean(child, &st->notify_remove); + } else if (strcasecmp("NotifySensorNotPresent", child->key) == 0) { + status = cf_util_get_boolean(child, &st->notify_notpresent); + } else if (strcasecmp("SELEnabled", child->key) == 0) { + status = cf_util_get_boolean(child, &st->sel_enabled); + } else if (strcasecmp("SELClearEvent", child->key) == 0) { + status = cf_util_get_boolean(child, &st->sel_clear_event); + } else if (strcasecmp("Host", child->key) == 0) + status = cf_util_get_string(child, &st->host); + else if (strcasecmp("Address", child->key) == 0) + status = cf_util_get_string(child, &st->connaddr); + else if (strcasecmp("Username", child->key) == 0) + status = cf_util_get_string(child, &st->username); + else if (strcasecmp("Password", child->key) == 0) + status = cf_util_get_string(child, &st->password); + else if (strcasecmp("AuthType", child->key) == 0) { + char tmp[8]; + status = cf_util_get_string_buffer(child, tmp, sizeof(tmp)); + if (status != 0) + break; + + if (strcasecmp("MD5", tmp) == 0) + st->authtype = IPMI_AUTHTYPE_MD5; + else if (strcasecmp("rmcp+", tmp) == 0) + st->authtype = IPMI_AUTHTYPE_RMCP_PLUS; + else + WARNING("ipmi plugin: The value \"%s\" is not valid for the " + "\"AuthType\" option.", + tmp); + } else { + WARNING("ipmi plugin: Option `%s' not allowed here.", child->key); + status = -1; + } + + if (status != 0) + break; + } + + if (status != 0) { + c_ipmi_free_instance(st); + return status; } + c_ipmi_add_instance(st); + return 0; -} /* int c_ipmi_init */ +} /* int c_ipmi_config_add_instance */ + +static int c_ipmi_config(oconfig_item_t *ci) { + _Bool have_instance_block = 0; + + for (int i = 0; i < ci->children_num; i++) { + oconfig_item_t *child = ci->children + i; + + if (strcasecmp("Instance", child->key) == 0) { + int status = c_ipmi_config_add_instance(child); + if (status != 0) + return status; + + have_instance_block = 1; + } else if (!have_instance_block) { + /* Non-instance option: Assume legacy configuration (without + * blocks) and call c_ipmi_config_add_instance with the block. + */ + WARNING("ipmi plugin: Legacy configuration found! Please update your " + "config file."); + return c_ipmi_config_add_instance(ci); + } else { + WARNING("ipmi plugin: The configuration option " + "\"%s\" is not allowed here. Did you " + "forget to add an block " + "around the configuration?", + child->key); + return -1; + } + } /* for (ci->children) */ + + return 0; +} /* int c_ipmi_config */ -static int c_ipmi_read(void) { - if ((c_ipmi_active == 0) || (thread_id == (pthread_t)0)) { +static int c_ipmi_read(user_data_t *user_data) { + c_ipmi_instance_t *st = user_data->data; + + if (st->active == 0) { INFO("ipmi plugin: c_ipmi_read: I'm not active, returning false."); return -1; } - sensor_list_read_all(); + if (st->connected == 0) + return 0; + + sensor_list_read_all(st); - if (c_ipmi_init_in_progress > 0) - c_ipmi_init_in_progress--; + if (st->init_in_progress > 0) + st->init_in_progress--; else - c_ipmi_init_in_progress = 0; + st->init_in_progress = 0; return 0; } /* int c_ipmi_read */ +static int c_ipmi_init(void) { + c_ipmi_instance_t *st; + char callback_name[3 * DATA_MAX_NAME_LEN]; + + if (os_handler != NULL) { + return 0; + } + + os_handler = ipmi_posix_thread_setup_os_handler(SIGIO); + if (os_handler == NULL) { + ERROR("ipmi plugin: ipmi_posix_thread_setup_os_handler failed."); + return -1; + } + + os_handler->set_log_handler(os_handler, c_ipmi_log); + + if (ipmi_init(os_handler) != 0) { + ERROR("ipmi plugin: ipmi_init() failed."); + os_handler->free_os_handler(os_handler); + return -1; + }; + + if (instances == NULL) { + /* No instances were configured, let's start a default instance. */ + st = c_ipmi_init_instance(); + if (st == NULL) + return ENOMEM; + + c_ipmi_add_instance(st); + } + + /* Don't send `ADD' notifications during startup (~ 1 minute) */ + int cycles = 1 + (60 / CDTIME_T_TO_TIME_T(plugin_get_interval())); + + st = instances; + while (NULL != st) { + /* The `st->name` is used as "domain name" for ipmi_open_domain(). + * That value should be unique, so we do plugin_register_complex_read() + * at first as it checks the uniqueness. */ + snprintf(callback_name, sizeof(callback_name), "ipmi/%s", st->name); + + user_data_t ud = { + .data = st, + }; + + int status = plugin_register_complex_read( + /* group = */ "ipmi", + /* name = */ callback_name, + /* callback = */ c_ipmi_read, + /* interval = */ 0, + /* user_data = */ &ud); + + if (status != 0) { + st = st->next; + continue; + } + + st->init_in_progress = cycles; + st->active = 1; + + status = plugin_thread_create(&st->thread_id, /* attr = */ NULL, + c_ipmi_thread_main, + /* user data = */ (void *)st, "ipmi"); + + if (status != 0) { + st->active = 0; + st->thread_id = (pthread_t){0}; + + plugin_unregister_read(callback_name); + + ERROR("ipmi plugin: pthread_create failed for `%s`.", callback_name); + } + + st = st->next; + } + + return 0; +} /* int c_ipmi_init */ + static int c_ipmi_shutdown(void) { - c_ipmi_active = 0; + c_ipmi_instance_t *st = instances; + instances = NULL; + + while (st != NULL) { + c_ipmi_instance_t *next = st->next; + + st->next = NULL; + st->active = 0; + + if (!pthread_equal(st->thread_id, (pthread_t){0})) { + pthread_join(st->thread_id, NULL); + st->thread_id = (pthread_t){0}; + } + + sensor_list_remove_all(st); + c_ipmi_free_instance(st); - if (thread_id != (pthread_t)0) { - pthread_join(thread_id, NULL); - thread_id = (pthread_t)0; + st = next; } - sensor_list_remove_all(); + os_handler->free_os_handler(os_handler); + os_handler = NULL; return 0; } /* int c_ipmi_shutdown */ void module_register(void) { - plugin_register_config("ipmi", c_ipmi_config, config_keys, config_keys_num); + plugin_register_complex_config("ipmi", c_ipmi_config); plugin_register_init("ipmi", c_ipmi_init); - plugin_register_read("ipmi", c_ipmi_read); plugin_register_shutdown("ipmi", c_ipmi_shutdown); } /* void module_register */ diff --git a/src/libcollectdclient/client.c b/src/libcollectdclient/client.c index 3ae2e714..d4703245 100644 --- a/src/libcollectdclient/client.c +++ b/src/libcollectdclient/client.c @@ -24,9 +24,7 @@ * Florian octo Forster **/ -#if HAVE_CONFIG_H #include "config.h" -#endif #if !defined(__GNUC__) || !__GNUC__ #define __attribute__(x) /**/ diff --git a/src/libcollectdclient/collectd/client.h b/src/libcollectdclient/collectd/client.h index 8604ff6b..ea812c1b 100644 --- a/src/libcollectdclient/collectd/client.h +++ b/src/libcollectdclient/collectd/client.h @@ -27,7 +27,8 @@ #ifndef LIBCOLLECTD_COLLECTDCLIENT_H #define LIBCOLLECTD_COLLECTDCLIENT_H 1 -#include "lcc_features.h" +#include "collectd/lcc_features.h" +#include "collectd/types.h" /* COLLECTD_TRACE is the environment variable used to control trace output. When * set to something non-zero, all lines sent to / received from the daemon are @@ -39,62 +40,12 @@ /* * Includes (for data types) */ -#include #include +#include #include -/* - * Defines - */ -#define LCC_NAME_LEN 64 -#define LCC_DEFAULT_PORT "25826" - -/* - * Types - */ -#define LCC_TYPE_COUNTER 0 -#define LCC_TYPE_GAUGE 1 -#define LCC_TYPE_DERIVE 2 -#define LCC_TYPE_ABSOLUTE 3 - LCC_BEGIN_DECLS -typedef uint64_t counter_t; -typedef double gauge_t; -typedef uint64_t derive_t; -typedef uint64_t absolute_t; - -union value_u { - counter_t counter; - gauge_t gauge; - derive_t derive; - absolute_t absolute; -}; -typedef union value_u value_t; - -struct lcc_identifier_s { - char host[LCC_NAME_LEN]; - char plugin[LCC_NAME_LEN]; - char plugin_instance[LCC_NAME_LEN]; - char type[LCC_NAME_LEN]; - char type_instance[LCC_NAME_LEN]; -}; -typedef struct lcc_identifier_s lcc_identifier_t; -#define LCC_IDENTIFIER_INIT \ - { "localhost", "", "", "", "" } - -struct lcc_value_list_s { - value_t *values; - int *values_types; - size_t values_len; - double time; - double interval; - lcc_identifier_t identifier; -}; -typedef struct lcc_value_list_s lcc_value_list_t; -#define LCC_VALUE_LIST_INIT \ - { NULL, NULL, 0, 0, 0, LCC_IDENTIFIER_INIT } - struct lcc_connection_s; typedef struct lcc_connection_s lcc_connection_t; diff --git a/src/libcollectdclient/collectd/network.h b/src/libcollectdclient/collectd/network.h index 35fd7d62..c8a5da54 100644 --- a/src/libcollectdclient/collectd/network.h +++ b/src/libcollectdclient/collectd/network.h @@ -27,11 +27,11 @@ #ifndef LIBCOLLECTDCLIENT_NETWORK_H #define LIBCOLLECTDCLIENT_NETWORK_H 1 +#include "collectd/client.h" + #include #include -#include "client.h" - #define NET_DEFAULT_V4_ADDR "239.192.74.66" #define NET_DEFAULT_V6_ADDR "ff18::efc0:4a42" #define NET_DEFAULT_PORT "25826" diff --git a/src/libcollectdclient/collectd/network_buffer.h b/src/libcollectdclient/collectd/network_buffer.h index 9f393103..d66b8155 100644 --- a/src/libcollectdclient/collectd/network_buffer.h +++ b/src/libcollectdclient/collectd/network_buffer.h @@ -27,9 +27,8 @@ #ifndef LIBCOLLECTDCLIENT_NETWORK_BUFFER_H #define LIBCOLLECTDCLIENT_NETWORK_BUFFER_H 1 -/* FIXME */ -#include "client.h" -#include "network.h" +#include "collectd/network.h" /* for lcc_security_level_t */ +#include "collectd/types.h" /* Ethernet frame - (IPv6 header + UDP header) */ #define LCC_NETWORK_BUFFER_SIZE_DEFAULT 1452 diff --git a/src/libcollectdclient/collectd/network_parse.h b/src/libcollectdclient/collectd/network_parse.h new file mode 100644 index 00000000..0cd9a792 --- /dev/null +++ b/src/libcollectdclient/collectd/network_parse.h @@ -0,0 +1,56 @@ +/** + * Copyright 2017 Florian Forster + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * Authors: + * Florian octo Forster + **/ + +#ifndef LIBCOLLECTD_NETWORK_PARSE_H +#define LIBCOLLECTD_NETWORK_PARSE_H 1 + +#include "collectd/lcc_features.h" + +#include "collectd/network.h" /* for lcc_security_level_t */ +#include "collectd/types.h" + +#include + +LCC_BEGIN_DECLS + +typedef struct { + /* writer is the callback used to send incoming lcc_value_list_t to. */ + lcc_value_list_writer_t writer; + + /* password_lookup is used to look up the password for a given username. */ + lcc_password_lookup_t password_lookup; + + /* security_level is the minimal required security level. */ + lcc_security_level_t security_level; +} lcc_network_parse_options_t; + +/* lcc_network_parse parses data received from the network and calls "w" with + * the parsed lcc_value_list_ts. */ +int lcc_network_parse(void *buffer, size_t buffer_size, + lcc_network_parse_options_t opts); + +LCC_END_DECLS + +#endif /* LIBCOLLECTD_NETWORK_PARSE_H */ diff --git a/src/libcollectdclient/collectd/server.h b/src/libcollectdclient/collectd/server.h new file mode 100644 index 00000000..ef6b792b --- /dev/null +++ b/src/libcollectdclient/collectd/server.h @@ -0,0 +1,88 @@ +/** + * Copyright 2017 Florian Forster + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * Authors: + * Florian octo Forster + **/ + +#ifndef LIBCOLLECTD_SERVER_H +#define LIBCOLLECTD_SERVER_H 1 + +#include "collectd/lcc_features.h" + +#include "collectd/network.h" /* for lcc_security_level_t */ +#include "collectd/network_parse.h" /* for lcc_network_parse_options_t */ +#include "collectd/types.h" + +#include + +#ifndef LCC_NETWORK_BUFFER_SIZE +#define LCC_NETWORK_BUFFER_SIZE 1452 +#endif + +LCC_BEGIN_DECLS + +/* lcc_network_parser_t is a callback that parses received network packets. It + * is expected to call lcc_network_parse_options_t.writer with each + * lcc_value_list_t it parses that has the required security level. */ +typedef int (*lcc_network_parser_t)(void *payload, size_t payload_size, + lcc_network_parse_options_t opts); + +/* lcc_listener_t holds parameters for running a collectd server. */ +typedef struct { + /* conn is a UDP socket for the server to listen on. If set to <0 node and + * service will be used to open a new UDP socket. If >=0, it is the caller's + * job to clean up the socket. */ + int conn; + + /* node is the local address to listen on if conn is <0. Defaults to "::" (any + * address). */ + char *node; + + /* service is the local address to listen on if conn is <0. Defaults to + * LCC_DEFAULT_PORT. */ + char *service; + + /* parser is the callback used to parse incoming network packets. Defaults to + * lcc_network_parse() if set to NULL. */ + lcc_network_parser_t parser; + + /* parse_options contains options for parser and is passed on verbatimely. */ + lcc_network_parse_options_t parse_options; + + /* buffer_size determines the maximum packet size to accept. Defaults to + * LCC_NETWORK_BUFFER_SIZE if set to zero. */ + uint16_t buffer_size; + + /* interface is the name of the interface to use when subscribing to a + * multicast group. Has no effect when using unicast. */ + char *interface; +} lcc_listener_t; + +/* lcc_listen_and_write listens on the provided UDP socket (or opens one using + * srv.addr if srv.conn is less than zero), parses the received packets and + * writes them to the provided lcc_value_list_writer_t. Returns non-zero on + * failure and does not return otherwise. */ +int lcc_listen_and_write(lcc_listener_t srv); + +LCC_END_DECLS + +#endif /* LIBCOLLECTD_SERVER_H */ diff --git a/src/libcollectdclient/collectd/types.h b/src/libcollectdclient/collectd/types.h new file mode 100644 index 00000000..7043e677 --- /dev/null +++ b/src/libcollectdclient/collectd/types.h @@ -0,0 +1,97 @@ +/** + * libcollectdclient - src/libcollectdclient/collectd/types.h + * Copyright (C) 2008-2017 Florian octo Forster + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + * + * Authors: + * Florian octo Forster + **/ + +#ifndef LIBCOLLECTD_COLLECTD_TYPES_H +#define LIBCOLLECTD_COLLECTD_TYPES_H 1 + +#include "collectd/lcc_features.h" + +#include /* for uint64_t */ +#include /* for size_t */ + +/* + * Defines + */ +#define LCC_NAME_LEN 64 +#define LCC_DEFAULT_PORT "25826" + +/* + * Types + */ +#define LCC_TYPE_COUNTER 0 +#define LCC_TYPE_GAUGE 1 +#define LCC_TYPE_DERIVE 2 +#define LCC_TYPE_ABSOLUTE 3 + +LCC_BEGIN_DECLS + +typedef uint64_t counter_t; +typedef double gauge_t; +typedef uint64_t derive_t; +typedef uint64_t absolute_t; + +union value_u { + counter_t counter; + gauge_t gauge; + derive_t derive; + absolute_t absolute; +}; +typedef union value_u value_t; + +struct lcc_identifier_s { + char host[LCC_NAME_LEN]; + char plugin[LCC_NAME_LEN]; + char plugin_instance[LCC_NAME_LEN]; + char type[LCC_NAME_LEN]; + char type_instance[LCC_NAME_LEN]; +}; +typedef struct lcc_identifier_s lcc_identifier_t; +#define LCC_IDENTIFIER_INIT \ + { "localhost", "", "", "", "" } + +struct lcc_value_list_s { + value_t *values; + int *values_types; + size_t values_len; + double time; + double interval; + lcc_identifier_t identifier; +}; +typedef struct lcc_value_list_s lcc_value_list_t; +#define LCC_VALUE_LIST_INIT \ + { NULL, NULL, 0, 0, 0, LCC_IDENTIFIER_INIT } + +/* lcc_value_list_writer_t is a write callback to which value lists are + * dispatched. */ +typedef int (*lcc_value_list_writer_t)(lcc_value_list_t const *); + +/* lcc_password_lookup_t is a callback for looking up the password for a given + * user. Must return NULL if the user is not known. */ +typedef char const *(*lcc_password_lookup_t)(char const *); + +LCC_END_DECLS + +#endif /* LIBCOLLECTD_COLLECTD_TYPES_H */ diff --git a/src/libcollectdclient/network_parse.c b/src/libcollectdclient/network_parse.c new file mode 100644 index 00000000..67034116 --- /dev/null +++ b/src/libcollectdclient/network_parse.c @@ -0,0 +1,623 @@ +/** + * Copyright 2017 Florian Forster + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * Authors: + * Florian octo Forster + **/ + +#include "config.h" + +#if !defined(__GNUC__) || !__GNUC__ +#define __attribute__(x) /**/ +#endif + +#include "collectd/lcc_features.h" +#include "collectd/network_parse.h" + +#include +#include +#include +#include +#include + +/* for be{16,64}toh */ +#if HAVE_ENDIAN_H +#include +#elif HAVE_SYS_ENDIAN_H +#include +#endif + +#if HAVE_GCRYPT_H +#define GCRYPT_NO_DEPRECATED +#include +#endif + +#include +#define DEBUG(...) printf(__VA_ARGS__) + +#if HAVE_GCRYPT_H +#if GCRYPT_VERSION_NUMBER < 0x010600 +GCRY_THREAD_OPTION_PTHREAD_IMPL; +#endif +#endif + +/* forward declaration because parse_sign_sha256()/parse_encrypt_aes256() and + * network_parse() need to call each other. */ +static int network_parse(void *data, size_t data_size, lcc_security_level_t sl, + lcc_network_parse_options_t const *opts); + +#if HAVE_GCRYPT_H +static int init_gcrypt() { + /* http://lists.gnupg.org/pipermail/gcrypt-devel/2003-August/000458.html + * Because you can't know in a library whether another library has + * already initialized the library */ + if (gcry_control(GCRYCTL_ANY_INITIALIZATION_P)) + return (0); + +/* http://www.gnupg.org/documentation/manuals/gcrypt/Multi_002dThreading.html + * To ensure thread-safety, it's important to set GCRYCTL_SET_THREAD_CBS + * *before* initalizing Libgcrypt with gcry_check_version(), which itself must + * be called before any other gcry_* function. GCRYCTL_ANY_INITIALIZATION_P + * above doesn't count, as it doesn't implicitly initalize Libgcrypt. + * + * tl;dr: keep all these gry_* statements in this exact order please. */ +#if GCRYPT_VERSION_NUMBER < 0x010600 + if (gcry_control(GCRYCTL_SET_THREAD_CBS, &gcry_threads_pthread)) { + return -1; + } +#endif + + gcry_check_version(NULL); + + if (gcry_control(GCRYCTL_INIT_SECMEM, 32768)) { + return -1; + } + + gcry_control(GCRYCTL_INITIALIZATION_FINISHED); + return 0; +} +#endif + +typedef struct { + uint8_t *data; + size_t len; +} buffer_t; + +static int buffer_next(buffer_t *b, void *out, size_t n) { + if (b->len < n) { + return -1; + } + memmove(out, b->data, n); + + b->data += n; + b->len -= n; + + return 0; +} + +static int buffer_uint16(buffer_t *b, uint16_t *out) { + uint16_t tmp; + if (buffer_next(b, &tmp, sizeof(tmp)) != 0) + return -1; + + *out = be16toh(tmp); + return 0; +} + +#define TYPE_HOST 0x0000 +#define TYPE_TIME 0x0001 +#define TYPE_TIME_HR 0x0008 +#define TYPE_PLUGIN 0x0002 +#define TYPE_PLUGIN_INSTANCE 0x0003 +#define TYPE_TYPE 0x0004 +#define TYPE_TYPE_INSTANCE 0x0005 +#define TYPE_VALUES 0x0006 +#define TYPE_INTERVAL 0x0007 +#define TYPE_INTERVAL_HR 0x0009 +#define TYPE_SIGN_SHA256 0x0200 +#define TYPE_ENCR_AES256 0x0210 + +static int parse_int(void *payload, size_t payload_size, uint64_t *out) { + uint64_t tmp; + + if (payload_size != sizeof(tmp)) + return EINVAL; + + memmove(&tmp, payload, sizeof(tmp)); + *out = be64toh(tmp); + return 0; +} + +static int parse_string(void *payload, size_t payload_size, char *out, + size_t out_size) { + char *in = payload; + + if ((payload_size < 1) || (in[payload_size - 1] != 0) || + (payload_size > out_size)) + return EINVAL; + + strncpy(out, in, out_size); + return 0; +} + +static int parse_identifier(uint16_t type, void *payload, size_t payload_size, + lcc_value_list_t *state) { + char buf[LCC_NAME_LEN]; + + if (parse_string(payload, payload_size, buf, sizeof(buf)) != 0) + return EINVAL; + + switch (type) { + case TYPE_HOST: + memmove(state->identifier.host, buf, LCC_NAME_LEN); + break; + case TYPE_PLUGIN: + memmove(state->identifier.plugin, buf, LCC_NAME_LEN); + break; + case TYPE_PLUGIN_INSTANCE: + memmove(state->identifier.plugin_instance, buf, LCC_NAME_LEN); + break; + case TYPE_TYPE: + memmove(state->identifier.type, buf, LCC_NAME_LEN); + break; + case TYPE_TYPE_INSTANCE: + memmove(state->identifier.type_instance, buf, LCC_NAME_LEN); + break; + default: + return EINVAL; + } + + return 0; +} + +static int parse_time(uint16_t type, void *payload, size_t payload_size, + lcc_value_list_t *state) { + uint64_t tmp = 0; + if (parse_int(payload, payload_size, &tmp)) + return EINVAL; + + double t = (double)tmp; + switch (type) { + case TYPE_INTERVAL: + state->interval = t; + break; + case TYPE_INTERVAL_HR: + state->interval = t / 1073741824.0; + break; + case TYPE_TIME: + state->time = t; + break; + case TYPE_TIME_HR: + state->time = t / 1073741824.0; + break; + default: + return EINVAL; + } + + return 0; +} + +static double ntohd(double val) /* {{{ */ +{ + static int config = 0; + + union { + uint8_t byte[8]; + double floating; + } in = { + .floating = val, + }; + union { + uint8_t byte[8]; + double floating; + } out = { + .byte = {0}, + }; + + if (config == 0) { + double d = 8.642135e130; + uint8_t b[8]; + + memcpy(b, &d, sizeof(b)); + + if ((b[0] == 0x2f) && (b[1] == 0x25) && (b[2] == 0xc0) && (b[3] == 0xc7) && + (b[4] == 0x43) && (b[5] == 0x2b) && (b[6] == 0x1f) && (b[7] == 0x5b)) + config = 1; /* need nothing */ + else if ((b[7] == 0x2f) && (b[6] == 0x25) && (b[5] == 0xc0) && + (b[4] == 0xc7) && (b[3] == 0x43) && (b[2] == 0x2b) && + (b[1] == 0x1f) && (b[0] == 0x5b)) + config = 2; /* endian flip */ + else if ((b[4] == 0x2f) && (b[5] == 0x25) && (b[6] == 0xc0) && + (b[7] == 0xc7) && (b[0] == 0x43) && (b[1] == 0x2b) && + (b[2] == 0x1f) && (b[3] == 0x5b)) + config = 3; /* int swap */ + else + config = 4; + } + + if (memcmp((char[]){0, 0, 0, 0, 0, 0, 0xf8, 0x7f}, in.byte, 8) == 0) { + return NAN; + } else if (config == 1) { + return val; + } else if (config == 2) { + in.floating = val; + out.byte[0] = in.byte[7]; + out.byte[1] = in.byte[6]; + out.byte[2] = in.byte[5]; + out.byte[3] = in.byte[4]; + out.byte[4] = in.byte[3]; + out.byte[5] = in.byte[2]; + out.byte[6] = in.byte[1]; + out.byte[7] = in.byte[0]; + return (out.floating); + } else if (config == 3) { + in.floating = val; + out.byte[0] = in.byte[4]; + out.byte[1] = in.byte[5]; + out.byte[2] = in.byte[6]; + out.byte[3] = in.byte[7]; + out.byte[4] = in.byte[0]; + out.byte[5] = in.byte[1]; + out.byte[6] = in.byte[2]; + out.byte[7] = in.byte[3]; + return out.floating; + } else { + /* If in doubt, just copy the value back to the caller. */ + return val; + } +} /* }}} double ntohd */ + +static int parse_values(void *payload, size_t payload_size, + lcc_value_list_t *state) { + buffer_t *b = &(buffer_t){ + .data = payload, .len = payload_size, + }; + + uint16_t n; + if (buffer_uint16(b, &n)) + return EINVAL; + + if (((size_t)n * 9) != b->len) + return EINVAL; + + state->values_len = (size_t)n; + state->values = calloc(sizeof(*state->values), state->values_len); + state->values_types = calloc(sizeof(*state->values_types), state->values_len); + if ((state->values == NULL) || (state->values_types == NULL)) { + return ENOMEM; + } + + for (uint16_t i = 0; i < n; i++) { + uint8_t tmp; + if (buffer_next(b, &tmp, sizeof(tmp))) + return EINVAL; + state->values_types[i] = (int)tmp; + } + + for (uint16_t i = 0; i < n; i++) { + uint64_t tmp; + if (buffer_next(b, &tmp, sizeof(tmp))) + return EINVAL; + + if (state->values_types[i] == LCC_TYPE_GAUGE) { + union { + uint64_t i; + double d; + } conv = {.i = tmp}; + state->values[i].gauge = ntohd(conv.d); + continue; + } + + tmp = be64toh(tmp); + switch (state->values_types[i]) { + case LCC_TYPE_COUNTER: + state->values[i].counter = (counter_t)tmp; + break; + case LCC_TYPE_DERIVE: + state->values[i].derive = (derive_t)tmp; + break; + case LCC_TYPE_ABSOLUTE: + state->values[i].absolute = (absolute_t)tmp; + break; + default: + return EINVAL; + } + } + + return 0; +} + +#if HAVE_GCRYPT_H +static int verify_sha256(void *payload, size_t payload_size, + char const *username, char const *password, + uint8_t hash_provided[32]) { + gcry_md_hd_t hd = NULL; + + gcry_error_t err = gcry_md_open(&hd, GCRY_MD_SHA256, GCRY_MD_FLAG_HMAC); + if (err != 0) { + return (int)err; + } + + err = gcry_md_setkey(hd, password, strlen(password)); + if (err != 0) { + gcry_md_close(hd); + return (int)err; + } + + gcry_md_write(hd, username, strlen(username)); + gcry_md_write(hd, payload, payload_size); + + unsigned char *hash_calculated = gcry_md_read(hd, GCRY_MD_SHA256); + if (!hash_calculated) { + gcry_md_close(hd); + return -1; + } + + int ret = memcmp(hash_provided, hash_calculated, 32); + + gcry_md_close(hd); + hash_calculated = NULL; + + return !!ret; +} +#else /* !HAVE_GCRYPT_H */ +static int verify_sha256(void *payload, size_t payload_size, + char const *username, char const *password, + uint8_t hash_provided[32]) { + return ENOTSUP; +} +#endif + +static int parse_sign_sha256(void *signature, size_t signature_len, + void *payload, size_t payload_size, + lcc_network_parse_options_t const *opts) { + if (opts->password_lookup == NULL) { + /* The sender signed the packet but we can't verify it. Handle it as if it + * were unsigned, i.e. security level NONE. */ + return network_parse(payload, payload_size, NONE, opts); + } + + buffer_t *b = &(buffer_t){ + .data = signature, .len = signature_len, + }; + + uint8_t hash[32]; + if (buffer_next(b, hash, sizeof(hash))) + return EINVAL; + + char username[b->len + 1]; + memset(username, 0, sizeof(username)); + if (buffer_next(b, username, sizeof(username) - 1)) { + return EINVAL; + } + + char const *password = opts->password_lookup(username); + if (!password) + return network_parse(payload, payload_size, NONE, opts); + + int status = verify_sha256(payload, payload_size, username, password, hash); + if (status != 0) + return status; + + return network_parse(payload, payload_size, SIGN, opts); +} + +#if HAVE_GCRYPT_H +static int decrypt_aes256(buffer_t *b, void *iv, size_t iv_size, + char const *password) { + gcry_cipher_hd_t cipher = NULL; + + if (gcry_cipher_open(&cipher, GCRY_CIPHER_AES256, GCRY_CIPHER_MODE_OFB, + /* flags = */ 0)) + return -1; + + uint8_t pwhash[32] = {0}; + gcry_md_hash_buffer(GCRY_MD_SHA256, pwhash, password, strlen(password)); + + fprintf(stderr, "sizeof(iv) = %zu\n", sizeof(iv)); + if (gcry_cipher_setkey(cipher, pwhash, sizeof(pwhash)) || + gcry_cipher_setiv(cipher, iv, iv_size) || + gcry_cipher_decrypt(cipher, b->data, b->len, /* in = */ NULL, + /* in_size = */ 0)) { + gcry_cipher_close(cipher); + return -1; + } + + gcry_cipher_close(cipher); + return 0; +} + +static int parse_encrypt_aes256(void *data, size_t data_size, + lcc_network_parse_options_t const *opts) { + if (opts->password_lookup == NULL) { + /* Without a password source it's (hopefully) impossible to decrypt the + * network packet. */ + return ENOENT; + } + + buffer_t *b = &(buffer_t){ + .data = data, .len = data_size, + }; + + uint16_t username_len; + if (buffer_uint16(b, &username_len)) + return EINVAL; + if ((size_t)username_len > data_size) + return ENOMEM; + char username[((size_t)username_len) + 1]; + memset(username, 0, sizeof(username)); + if (buffer_next(b, username, (size_t)username_len)) + return EINVAL; + + char const *password = opts->password_lookup(username); + if (!password) + return ENOENT; + + uint8_t iv[16]; + if (buffer_next(b, iv, sizeof(iv))) + return EINVAL; + + int status = decrypt_aes256(b, iv, sizeof(iv), password); + if (status != 0) + return status; + + uint8_t hash_provided[20]; + if (buffer_next(b, hash_provided, sizeof(hash_provided))) { + return -1; + } + + uint8_t hash_calculated[20]; + gcry_md_hash_buffer(GCRY_MD_SHA1, hash_calculated, b->data, b->len); + + if (memcmp(hash_provided, hash_calculated, sizeof(hash_provided)) != 0) { + return -1; + } + + return network_parse(b->data, b->len, ENCRYPT, opts); +} +#else /* !HAVE_GCRYPT_H */ +static int parse_encrypt_aes256(void *data, size_t data_size, + lcc_network_parse_options_t const *opts) { + return ENOTSUP; +} +#endif + +static int network_parse(void *data, size_t data_size, lcc_security_level_t sl, + lcc_network_parse_options_t const *opts) { + buffer_t *b = &(buffer_t){ + .data = data, .len = data_size, + }; + + lcc_value_list_t state = {0}; + + while (b->len > 0) { + uint16_t type = 0, sz = 0; + if (buffer_uint16(b, &type) || buffer_uint16(b, &sz)) { + DEBUG("lcc_network_parse(): reading type and/or length failed.\n"); + return EINVAL; + } + + if ((sz < 5) || (((size_t)sz - 4) > b->len)) { + DEBUG("lcc_network_parse(): invalid 'sz' field: sz = %" PRIu16 + ", b->len = %zu\n", + sz, b->len); + return EINVAL; + } + sz -= 4; + + uint8_t payload[sz]; + if (buffer_next(b, payload, sizeof(payload))) + return EINVAL; + + switch (type) { + case TYPE_HOST: + case TYPE_PLUGIN: + case TYPE_PLUGIN_INSTANCE: + case TYPE_TYPE: + case TYPE_TYPE_INSTANCE: { + if (parse_identifier(type, payload, sizeof(payload), &state)) { + DEBUG("lcc_network_parse(): parse_identifier failed.\n"); + return EINVAL; + } + break; + } + + case TYPE_INTERVAL: + case TYPE_INTERVAL_HR: + case TYPE_TIME: + case TYPE_TIME_HR: { + if (parse_time(type, payload, sizeof(payload), &state)) { + DEBUG("lcc_network_parse(): parse_time failed.\n"); + return EINVAL; + } + break; + } + + case TYPE_VALUES: { + lcc_value_list_t vl = state; + if (parse_values(payload, sizeof(payload), &vl)) { + free(vl.values); + free(vl.values_types); + DEBUG("lcc_network_parse(): parse_values failed.\n"); + return EINVAL; + } + + int status = 0; + + /* Write metrics if they have the required security level. */ + if (sl >= opts->security_level) + status = opts->writer(&vl); + + free(vl.values); + free(vl.values_types); + + if (status != 0) + return status; + break; + } + + case TYPE_SIGN_SHA256: { + int status = + parse_sign_sha256(payload, sizeof(payload), b->data, b->len, opts); + if (status != 0) { + DEBUG("lcc_network_parse(): parse_sign_sha256() = %d\n", status); + return -1; + } + /* parse_sign_sha256, if successful, consumes all remaining data. */ + b->data = NULL; + b->len = 0; + break; + } + + case TYPE_ENCR_AES256: { + int status = parse_encrypt_aes256(payload, sizeof(payload), opts); + if (status != 0) { + DEBUG("lcc_network_parse(): parse_encrypt_aes256() = %d\n", status); + return -1; + } + break; + } + + default: { + DEBUG("lcc_network_parse(): ignoring unknown type %" PRIu16 "\n", type); + return EINVAL; + } + } + } + + return 0; +} + +int lcc_network_parse(void *data, size_t data_size, + lcc_network_parse_options_t opts) { + if (opts.password_lookup) { +#if HAVE_GCRYPT_H + int status; + if ((status = init_gcrypt())) { + return status; + } +#else + return ENOTSUP; +#endif + } + + return network_parse(data, data_size, NONE, &opts); +} diff --git a/src/libcollectdclient/network_parse_test.c b/src/libcollectdclient/network_parse_test.c new file mode 100644 index 00000000..9efd4bd8 --- /dev/null +++ b/src/libcollectdclient/network_parse_test.c @@ -0,0 +1,511 @@ +/** + * Copyright 2017 Florian Forster + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * Authors: + * Florian octo Forster + **/ + +#include "collectd/lcc_features.h" + +#include "collectd/network_buffer.h" /* for LCC_NETWORK_BUFFER_SIZE_DEFAULT */ + +#include + +#include "network_parse.c" /* sic */ + +char *raw_packet_data[] = { + "0000000e6c6f63616c686f7374000008000c1513676ac3a6e0970009000c00000002800000" + "000002000973776170000004000973776170000005000966726565000006000f0001010000" + "0080ff610f420008000c1513676ac3a8fc120004000c737761705f696f0000050007696e00" + "0006000f00010200000000000000000008000c1513676ac3a9077d000500086f7574000006" + "000f00010200000000000000000008000c1513676ac3bd2a8c0002000e696e746572666163" + "65000003000965746830000004000e69665f6f637465747300000500050000060018000202" + "02000000000000000000000000000000000008000c1513676ac3bd5a970004000e69665f65" + "72726f7273000006001800020202000000000000000000000000000000000008000c151367" + "6ac3bd7fea000300076c6f000004000e69665f6f6374657473000006001800020202000000" + "000009e79c000000000009e79c0008000c1513676ac3bdaae60003000a776c616e30000006" + "001800020202000000001009fa5400000000011cf6670008000c1513676ac3bdb0e0000400" + "0e69665f6572726f7273000006001800020202000000000000000000000000000000000008" + "000c1513676ac3bd3d6d0003000965746830000004000f69665f7061636b65747300000600" + "1800020202000000000000000000000000000000000008000c1513676ac3bdae290003000a" + "776c616e300000060018000202020000000000032f8f00000000000205e50008000c151367" + "6ac3bdbb7b0003000c646f636b657230000006001800020202000000000000000000000000" + "000000000008000c1513676ac3bda0db000300076c6f000004000e69665f6572726f727300" + "0006001800020202000000000000000000000000000000000008000c1513676ac3bdbde800" + "03000c646f636b657230000006001800020202000000000000000000000000000000000008" + "000c1513676ac3bd8d8e000300076c6f000004000f69665f7061636b657473000006001800" + "0202020000000000000c9c0000000000000c9c0008000c1513676ac3bdb90b0003000c646f" + "636b657230000004000e69665f6f6374657473000006001800020202000000000000000000" + "000000000000000008000c1513676ac469b10f0002000e70726f6365737365730000030005" + "000004000d70735f7374617465000005000c7a6f6d62696573000006000f00010100000000" + "000000000008000c1513676ac469a4a30005000d736c656570696e67000006000f00010100" + "00000000006e400008000c1513676ac469c6320005000b706167696e67000006000f000101" + "00000000000000000008000c1513676ac469f06e0005000c626c6f636b6564000006000f00" + "010100000000000000000008000c1513676ac4698af40005000c72756e6e696e6700000600" + "0f00010100000000000000000008000c1513676ac469bbe10005000c73746f707065640000" + "06000f00010100000000000000000008000c1513676ac46b8e710004000e666f726b5f7261" + "74650000050005000006000f0001020000000000001bcf0008000c1513676d437f12960002" + "00086370750000030006300000040008637075000005000b73797374656d000006000f0001" + "0200000000000021870008000c1513676d437f36020005000969646c65000006000f000102" + "000000000005847a0008000c1513676d437f979b0005000977616974000006000f00010200" + "000000000005210008000c1513676d43802ff60005000c736f6674697271000006000f0001" + "02000000000000001f0008000c1513676d43803b3a0005000a737465616c000006000f0001" + "020000000000000000", + "0000000e6c6f63616c686f7374000008000c1513676d4380551f0009000c00000002800000" + "00000200086370750000030006310000040008637075000005000975736572000006000f00" + "01020000000000007cad0008000c1513676d43805dbe000500096e696365000006000f0001" + "0200000000000001de0008000c1513676d4380697d0005000b73797374656d000006000f00" + "01020000000000001ce80008000c1513676d438072bd0005000969646c65000006000f0001" + "02000000000005931c0008000c1513676d43807c430005000977616974000006000f000102" + "000000000000094b0008000c1513676d43808cee0005000c736f6674697271000006000f00" + "010200000000000000120008000c1513676d4380843a0005000e696e746572727570740000" + "06000f00010200000000000000000008000c1513676d438096230005000a737465616c0000" + "06000f00010200000000000000000008000c1513676d4380aa9c0003000632000005000975" + "736572000006000f00010200000000000089580008000c1513676d4380b29f000500096e69" + "6365000006000f00010200000000000003610008000c1513676d4380c44c0005000969646c" + "65000006000f000102000000000005873d0008000c1513676d4380bc0f0005000b73797374" + "656d000006000f000102000000000000201d0008000c1513676d4380cea400050009776169" + "74000006000f00010200000000000005810008000c1513676d4380d7370005000e696e7465" + "7272757074000006000f00010200000000000000000008000c1513676d4380ea830005000a" + "737465616c000006000f00010200000000000000000008000c1513676d437eef6200030006" + "3000000500096e696365000006000f00010200000000000003920008000c1513676d4380e0" + "260003000632000005000c736f6674697271000006000f0001020000000000000016000800" + "0c1513676d438101410003000633000005000975736572000006000f000102000000000000" + "7d8a0008000c1513676d438109f5000500096e696365000006000f00010200000000000004" + "350008000c1513676d4380244b0003000630000005000e696e74657272757074000006000f" + "00010200000000000000000008000c1513676d438122070003000633000005000969646c65" + "000006000f0001020000000000058eb60008000c1513676d43812e83000500097761697400" + "0006000f0001020000000000000ca80008000c1513676d438141480005000c736f66746972" + "71000006000f000102000000000000001e0008000c1513676d43814a5d0005000a73746561" + "6c000006000f00010200000000000000000008000c1513676d4381149e0005000b73797374" + "656d000006000f0001020000000000001b9a0008000c1513676d437ea86000030006300000" + "05000975736572000006000f00010200000000000089a80008000c1513676d438138190003" + "000633000005000e696e74657272757074000006000f00010200000000000000000008000c" + "1513676d438a9ca00002000e696e74657266616365000003000965746830000004000e6966" + "5f6f6374657473000005000500000600180002020200000000000000000000000000000000" + "0008000c1513676d438aea760004000f69665f7061636b6574730000060018000202020000" + "00000000000000000000000000000008000c1513676d438b214d0004000e69665f6572726f" + "727300000600180002020200000000000000000000000000000000", + "0000000e6c6f63616c686f7374000008000c1513676d438aac590009000c00000002800000" + "000002000764660000030009726f6f74000004000f64665f636f6d706c6578000005000966" + "726565000006000f0001010000004c077e57420008000c1513676d438b6ada0005000d7265" + "736572766564000006000f00010100000000338116420008000c1513676d438b7a17000200" + "0e696e7465726661636500000300076c6f000004000e69665f6f6374657473000005000500" + "0006001800020202000000000009ecf5000000000009ecf50008000c1513676d438b757800" + "02000764660000030009726f6f74000004000f64665f636f6d706c65780000050009757365" + "64000006000f000101000000e0a41b26420008000c1513676d438b8ed20002000e696e7465" + "726661636500000300076c6f000004000e69665f6572726f72730000050005000006001800" + "020202000000000000000000000000000000000008000c1513676d438b86bf0004000f6966" + "5f7061636b6574730000060018000202020000000000000c9d0000000000000c9d0008000c" + "1513676d438bb3e60003000a776c616e300000060018000202020000000000032fab000000" + "00000205ed0008000c1513676d438bd62e0003000c646f636b657230000004000e69665f6f" + "6374657473000006001800020202000000000000000000000000000000000008000c151367" + "6d438bbc8f0003000a776c616e30000004000e69665f6572726f7273000006001800020202" + "000000000000000000000000000000000008000c1513676d438bdf030003000c646f636b65" + "7230000004000f69665f7061636b6574730000060018000202020000000000000000000000" + "00000000000008000c1513676d438baaf10003000a776c616e30000004000e69665f6f6374" + "65747300000600180002020200000000100a042300000000011cfa460008000c1513676d43" + "8c5f100002000764660000030009626f6f74000004000f64665f636f6d706c657800000500" + "0966726565000006000f0001010000000010e198410008000c1513676d438c689c0005000d" + "7265736572766564000006000f00010100000000804c68410008000c1513676d438c70ce00" + "05000975736564000006000f0001010000000020ea9e410008000c1513676d438be7bc0002" + "000e696e74657266616365000003000c646f636b657230000004000e69665f6572726f7273" + "0000050005000006001800020202000000000000000000000000000000000008000c151367" + "6d43beca8c0002000c656e74726f70790000030005000004000c656e74726f707900000600" + "0f0001010000000000088f400008000c1513676d43bf1d13000200096c6f61640000040009" + "6c6f6164000006002100030101019a9999999999a93f666666666666d63f5c8fc2f5285cdf" + "3f0008000c1513676d43c02b85000200096469736b00000300087364610000040010646973" + "6b5f6f63746574730000060018000202020000000075887800000000005b6f3c000008000c" + "1513676d43c06d1f0004000d6469736b5f6f7073000006001800020202000000000003cbbd" + "000000000001c0510008000c1513676d43c08b6a0004000e6469736b5f74696d6500000600" + "1800020202000000000000003f00000000000001720008000c1513676d43c0a5fb00040010" + "6469736b5f6d65726765640000060018000202020000000000001285000000000000f80100" + "08000c1513676d43c0c8b4000300097364613100000400106469736b5f6f63746574730000" + "060018000202020000000001107c000000000000003c00", + "0000000e6c6f63616c686f7374000008000c1513676d43c0d00a0009000c00000002800000" + "00000200096469736b000003000973646131000004000d6469736b5f6f7073000006001800" + "020202000000000000029b00000000000000080008000c1513676d43c0d7b20004000e6469" + "736b5f74696d650000060018000202020000000000000004000000000000000f0008000c15" + "13676d43c0df73000400106469736b5f6d6572676564000006001800020202000000000000" + "0fb400000000000000010008000c1513676d43c0f87c000300097364613200000400106469" + "736b5f6f637465747300000600180002020200000000000008000000000000000000000800" + "0c1513676d43c1003e0004000d6469736b5f6f707300000600180002020200000000000000" + "0200000000000000000008000c1513676d43c107bf000400106469736b5f6d657267656400" + "0006001800020202000000000000000000000000000000000008000c1513676d43c12fa400" + "03000973646135000004000d6469736b5f6f7073000006001800020202000000000003c867" + "000000000001aef20008000c1513676d43c13d5e000400106469736b5f6d65726765640000" + "0600180002020200000000000002d1000000000000f8000008000c1513676d43c136a90004" + "000e6469736b5f74696d65000006001800020202000000000000003f000000000000011c00" + "08000c1513676d43c1740500030009646d2d3000000400106469736b5f6f63746574730000" + "060018000202020000000074596400000000005b6f00000008000c1513676d43c179c70004" + "000d6469736b5f6f7073000006001800020202000000000003cae4000000000002b0f30008" + "000c1513676d43c18abe000400106469736b5f6d6572676564000006001800020202000000" + "000000000000000000000000000008000c1513676d43c181b90004000e6469736b5f74696d" + "650000060018000202020000000000000040000000000000013e0008000c1513676d43c1a9" + "5e00030009646d2d3100000400106469736b5f6f6374657473000006001800020202000000" + "00000e000000000000000000000008000c1513676d43c1b7ea0004000e6469736b5f74696d" + "65000006001800020202000000000000000200000000000000000008000c1513676d43c1b0" + "3e0004000d6469736b5f6f707300000600180002020200000000000000e000000000000000" + "000008000c1513676d43c1c00d000400106469736b5f6d6572676564000006001800020202" + "000000000000000000000000000000000008000c1513676d43c12818000300097364613500" + "000400106469736b5f6f637465747300000600180002020200000000746c6400000000005b" + "6f00000008000c1513676d43d320a80002000c62617474657279000003000630000004000b" + "636861726765000006000f0001018fc2f5285c2f58400008000c1513676d43d36fd6000400" + "0c63757272656e74000006000f00010100000000000000800008000c1513676d43d3cdb600" + "04000c766f6c74616765000006000f000101736891ed7cbf28400008000c1513676d43d59d" + "d60002000869727100000300050000040008697271000005000630000006000f0001020000" + "0000000000110008000c1513676d43d5d2cf0005000631000006000f000102000000000000" + "00100008000c1513676d43d5fe820005000638000006000f00010200000000000000010008" + "000c1513676d43d635440005000639000006000f00010200000000000035210008000c1513" + "676d43d66265000500073132000006000f0001020000000000000790", + "0000000e6c6f63616c686f7374000008000c1513676d43d68e940009000c00000002800000" + "0000020008697271000004000869727100000500073136000006000f000102000000000000" + "00210008000c1513676d43d69be20002000a7573657273000004000a757365727300000500" + "05000006000f00010100000000000010400008000c1513676d43d6aa5d0002000869727100" + "0004000869727100000500073233000006000f00010200000000000000250008000c151367" + "6d43d6c7dc000500073431000006000f000102000000000000ff7d0008000c1513676d43d6" + "e23d000500073432000006000f00010200000000000008070008000c1513676d43d9aa3a00" + "0500073437000006000f0001020000000000079a260008000c1513676d43d9cca900050007" + "3438000006000f00010200000000000000c70008000c1513676d43d9ea5d00050007343900" + "0006000f00010200000000000004c20008000c1513676d43da050e00050007353000000600" + "0f000102000000000000001c0008000c1513676d43da1efa000500084e4d49000006000f00" + "010200000000000000000008000c1513676d43da3c82000500084c4f43000006000f000102" + "000000000018d3080008000c1513676d43da544e00050008535055000006000f0001020000" + "0000000000000008000c1513676d43da6cca00050008504d49000006000f00010200000000" + "000000000008000c1513676d43da885400050008495749000006000f000102000000000000" + "a9da0008000c1513676d43daa23a00050008525452000006000f0001020000000000000003" + "0008000c1513676d43dabaed00050008524553000006000f00010200000000000ac8360008" + "000c1513676d43dad4150005000843414c000006000f000102000000000000191f0008000c" + "1513676d43daeef300050008544c42000006000f000102000000000003dbdc0008000c1513" + "676d43db11410005000854524d000006000f00010200000000000000000008000c1513676d" + "43db292c00050008544852000006000f00010200000000000000000008000c1513676d43db" + "411d000500084d4345000006000f00010200000000000000000008000c1513676d43db5b59" + "000500084d4350000006000f000102000000000000003c0008000c1513676d43db68010005" + "0008455252000006000f00010200000000000000000008000c1513676d43db758a00050008" + "4d4953000006000f00010200000000000000000008000c1513676d43dd2e800002000b6d65" + "6d6f7279000004000b6d656d6f7279000005000975736564000006000f00010100000000fe" + "bbe0410008000c1513676d43dd3f4b0005000d6275666665726564000006000f0001010000" + "000070fbc8410008000c1513676d43dd48700005000b636163686564000006000f00010100" + "000000c008df410008000c1513676d43dd51c60005000966726565000006000f0001010000" + "0080481d05420008000c1513676d43dec7e300020009737761700000040009737761700000" + "05000975736564000006000f00010100000000000000000008000c1513676d43ded4490005" + "000966726565000006000f00010100000080ff610f420008000c1513676d43dedcfd000500" + "0b636163686564000006000f00010100000000000000000008000c1513676d43d715e30002" + "0008697271000004000869727100000500073434000006000f0001020000000000031b6100" + "08000c1513676d43d73116000500073435000006000f00010200000000000000180008000c" + "1513676d43ee00150002000973776170000004000c737761705f696f0000050007696e0000" + "06000f0001020000000000000000", +}; + +static int decode_string(char const *in, uint8_t *out, size_t *out_size) { + size_t in_size = strlen(in); + if (*out_size < (in_size / 2)) + return -1; + *out_size = in_size / 2; + + for (size_t i = 0; i < *out_size; i++) { + char tmp[] = {in[2 * i], in[2 * i + 1], 0}; + out[i] = (uint8_t)strtoul(tmp, NULL, 16); + } + + return 0; +} + +static int nop_writer(lcc_value_list_t const *vl) { + if (!strlen(vl->identifier.host) || !strlen(vl->identifier.plugin) || + !strlen(vl->identifier.type)) { + return EINVAL; + } + return 0; +} + +static int test_network_parse() { + int ret = 0; + + for (size_t i = 0; i < sizeof(raw_packet_data) / sizeof(raw_packet_data[0]); + i++) { + uint8_t buffer[LCC_NETWORK_BUFFER_SIZE_DEFAULT]; + size_t buffer_size = sizeof(buffer); + if (decode_string(raw_packet_data[i], buffer, &buffer_size)) { + fprintf( + stderr, + "lcc_network_parse(raw_packet_data[%zu]): decoding string failed\n", + i); + return -1; + } + + int status = + lcc_network_parse(buffer, buffer_size, (lcc_network_parse_options_t){ + .writer = nop_writer, + }); + if (status != 0) { + fprintf(stderr, "lcc_network_parse(raw_packet_data[%zu]) = %d, want 0\n", + i, status); + ret = status; + } + + printf("ok - lcc_network_parse(raw_packet_data[%zu])\n", i); + } + + return ret; +} + +static int test_parse_time() { + int ret = 0; + + struct { + uint64_t in; + double want; + } cases[] = { + {1439980823, 1439980823.0}, + {1439981005, 1439981005.0}, + {1439981150, 1439981150.0}, + }; + + for (size_t i = 0; i < sizeof(cases) / sizeof(cases[0]); i++) { + lcc_value_list_t vl = LCC_VALUE_LIST_INIT; + + uint64_t be = htobe64(cases[i].in); + int status = parse_time(TYPE_TIME, &be, sizeof(be), &vl); + if ((status != 0) || (vl.time != cases[i].want)) { + fprintf(stderr, "parse_time(%" PRIu64 ") = (%.0f, %d), want (%.0f, 0)\n", + cases[i].in, vl.time, status, cases[i].want); + ret = -1; + } + } + + struct { + uint64_t in; + double want; + } cases_hr[] = { + {1546167635576736987, 1439980823.152453627}, + {1546167831554815222, 1439981005.671262017}, + {1546167986577716567, 1439981150.047589622}, + }; + + for (size_t i = 0; i < sizeof(cases_hr) / sizeof(cases_hr[0]); i++) { + lcc_value_list_t vl = LCC_VALUE_LIST_INIT; + + uint64_t be = htobe64(cases_hr[i].in); + int status = parse_time(TYPE_TIME_HR, &be, sizeof(be), &vl); + if ((status != 0) || (vl.time != cases_hr[i].want)) { + fprintf(stderr, "parse_time(%" PRIu64 ") = (%.9f, %d), want (%.9f, 0)\n", + cases_hr[i].in, vl.time, status, cases_hr[i].want); + ret = -1; + } + } + + return ret; +} + +static int test_parse_string() { + int ret = 0; + + struct { + uint8_t *in; + size_t in_len; + char *want; + } cases[] = { + {(uint8_t[]){0}, 1, ""}, + {(uint8_t[]){'t', 'e', 's', 't', 0}, 5, "test"}, + {(uint8_t[]){'t', 'e', 's', 't'}, 4, NULL}, // null byte missing + {(uint8_t[]){'t', 'e', 's', 't', 'x', 0}, 6, + NULL}, // output buffer too small + }; + + for (size_t i = 0; i < sizeof(cases) / sizeof(cases[0]); i++) { + char got[5] = {0}; + + int status = parse_string(cases[i].in, cases[i].in_len, got, sizeof(got)); + if (cases[i].want == NULL) { + if (status == 0) { + fprintf(stderr, "parse_string() = (\"%s\", 0), want error\n", got); + ret = -1; + } + } else /* if cases[i].want != NULL */ { + if (status != 0) { + fprintf(stderr, "parse_string() = %d, want 0\n", status); + ret = -1; + } else if (strcmp(got, cases[i].want) != 0) { + fprintf(stderr, "parse_string() = (\"%s\", 0), want (\"%s\", 0)\n", got, + cases[i].want); + ret = -1; + } + } + } + + return ret; +} + +static int test_parse_values() { + int ret = 0; + + uint8_t testcase[] = { + // 0, 6, // pkg type + // 0, 33, // pkg len + 0, 3, // num values + 1, 2, 1, // gauge, derive, gauge + 0, 0, 0, 0, 0, 0, 0x45, 0x40, // 42.0 + 0, 0, 0, 0, 0, 0, 0x7a, 0x69, // 31337 + 0, 0, 0, 0, 0, 0, 0xf8, 0x7f, // NaN + }; + + lcc_value_list_t vl = LCC_VALUE_LIST_INIT; + int status = parse_values(testcase, sizeof(testcase), &vl); + if (status != 0) { + fprintf(stderr, "parse_values() = %d, want 0\n", status); + return -1; + } + + if (vl.values_len != 3) { + fprintf(stderr, "parse_values(): vl.values_len = %zu, want 3\n", + vl.values_len); + return -1; + } + + int want_types[] = {LCC_TYPE_GAUGE, LCC_TYPE_DERIVE, LCC_TYPE_GAUGE}; + for (size_t i = 0; i < sizeof(want_types) / sizeof(want_types[0]); i++) { + if (vl.values_types[i] != want_types[i]) { + fprintf(stderr, "parse_values(): vl.values_types[%zu] = %d, want %d\n", i, + vl.values_types[i], want_types[i]); + ret = -1; + } + } + + if (vl.values[0].gauge != 42.0) { + fprintf(stderr, "parse_values(): vl.values[0] = %g, want 42\n", + vl.values[0].gauge); + ret = -1; + } + if (vl.values[1].derive != 31337) { + fprintf(stderr, "parse_values(): vl.values[1] = %" PRIu64 ", want 31337\n", + vl.values[1].derive); + ret = -1; + } + if (!isnan(vl.values[2].gauge)) { + fprintf(stderr, "parse_values(): vl.values[2] = %g, want NaN\n", + vl.values[2].gauge); + ret = -1; + } + + free(vl.values); + free(vl.values_types); + + return ret; +} + +static int test_verify_sha256() { + int ret = 0; + + int status = verify_sha256( + (char[]){'c', 'o', 'l', 'l', 'e', 'c', 't', 'd'}, 8, "admin", "admin", + (uint8_t[]){ + 0xcd, 0xa5, 0x9a, 0x37, 0xb0, 0x81, 0xc2, 0x31, 0x24, 0x2a, 0x6d, + 0xbd, 0xfb, 0x44, 0xdb, 0xd7, 0x41, 0x2a, 0xf4, 0x29, 0x83, 0xde, + 0xa5, 0x11, 0x96, 0xd2, 0xe9, 0x30, 0x21, 0xae, 0xc5, 0x45, + }); + if (status != 0) { + fprintf(stderr, "verify_sha256() = %d, want 0\n", status); + ret = -1; + } + + status = verify_sha256( + (char[]){'c', 'o', 'l', 'l', 'E', 'c', 't', 'd'}, 8, "admin", "admin", + (uint8_t[]){ + 0xcd, 0xa5, 0x9a, 0x37, 0xb0, 0x81, 0xc2, 0x31, 0x24, 0x2a, 0x6d, + 0xbd, 0xfb, 0x44, 0xdb, 0xd7, 0x41, 0x2a, 0xf4, 0x29, 0x83, 0xde, + 0xa5, 0x11, 0x96, 0xd2, 0xe9, 0x30, 0x21, 0xae, 0xc5, 0x45, + }); + if (status != 1) { + fprintf(stderr, "verify_sha256() = %d, want 1\n", status); + ret = -1; + } + + return ret; +} + +static int test_decrypt_aes256() { + char const *iv_str = "4cbe2a747c9f9dcfa0e66f0c2fa74875"; + uint8_t iv[16] = {0}; + size_t iv_len = sizeof(iv); + + char const *ciphertext_str = + "8f023b0b15178f8428da1221a5f653e840f065db4aff032c22e5a3df"; + uint8_t ciphertext[28] = {0}; + size_t ciphertext_len = sizeof(ciphertext); + + if (decode_string(iv_str, iv, &iv_len) || + decode_string(ciphertext_str, ciphertext, &ciphertext_len)) { + fprintf(stderr, "test_decrypt_aes256: decode_string failed.\n"); + return -1; + } + assert(iv_len == sizeof(iv)); + assert(ciphertext_len == sizeof(ciphertext)); + + int status = decrypt_aes256( + &(buffer_t){ + .data = ciphertext, .len = ciphertext_len, + }, + iv, iv_len, "admin"); + if (status != 0) { + fprintf(stderr, "decrypt_aes256() = %d, want 0\n", status); + return -1; + } + + char const *want = "collectd"; + char got[9] = {0}; + memmove(got, &ciphertext[20], sizeof(got) - 1); + if (strcmp(got, want) != 0) { + fprintf(stderr, "decrypt_aes256() = \"%s\", want \"%s\"\n", got, want); + return -1; + } + + return 0; +} + +int main(void) { + int ret = 0; + + printf("libcollectdclient/server_test.c\n"); + + int status; + if ((status = test_network_parse())) { + ret = status; + } + if ((status = test_parse_time())) { + ret = status; + } + if ((status = test_parse_string())) { + ret = status; + } + if ((status = test_parse_values())) { + ret = status; + } + + if ((status = test_verify_sha256())) { + ret = status; + } + if ((status = test_decrypt_aes256())) { + ret = status; + } + + return ret; +} diff --git a/src/libcollectdclient/server.c b/src/libcollectdclient/server.c new file mode 100644 index 00000000..d18cc7da --- /dev/null +++ b/src/libcollectdclient/server.c @@ -0,0 +1,213 @@ +/** + * Copyright 2017 Florian Forster + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * Authors: + * Florian octo Forster + **/ + +#include "config.h" + +#if !defined(__GNUC__) || !__GNUC__ +#define __attribute__(x) /**/ +#endif + +#include "collectd/lcc_features.h" +#include "collectd/network_parse.h" /* for lcc_network_parse_options_t */ +#include "collectd/server.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#define DEBUG(...) printf(__VA_ARGS__) + +static _Bool is_multicast(struct addrinfo const *ai) { + if (ai->ai_family == AF_INET) { + struct sockaddr_in *addr = (struct sockaddr_in *)ai->ai_addr; + return IN_MULTICAST(ntohl(addr->sin_addr.s_addr)); + } else if (ai->ai_family == AF_INET6) { + struct sockaddr_in6 *addr = (struct sockaddr_in6 *)ai->ai_addr; + return IN6_IS_ADDR_MULTICAST(&addr->sin6_addr); + } + return 0; +} + +static int server_multicast_join(lcc_listener_t *srv, + struct sockaddr_storage *group, int loop_back, + int ttl) { + if (group->ss_family == AF_INET) { + struct sockaddr_in *sa = (struct sockaddr_in *)group; + + int status = setsockopt(srv->conn, IPPROTO_IP, IP_MULTICAST_LOOP, + &loop_back, sizeof(loop_back)); + if (status == -1) { + DEBUG("setsockopt(IP_MULTICAST_LOOP, %d) = %d\n", loop_back, errno); + return errno; + } + + status = + setsockopt(srv->conn, IPPROTO_IP, IP_MULTICAST_TTL, &ttl, sizeof(ttl)); + if (status == -1) + return errno; + +#if HAVE_STRUCT_IP_MREQN_IMR_IFINDEX + struct ip_mreqn mreq = { + .imr_address.s_addr = INADDR_ANY, + .imr_multiaddr.s_addr = sa->sin_addr.s_addr, + .imr_ifindex = if_nametoindex(srv->interface), + }; +#else + struct ip_mreq mreq = { + .imr_address.s_addr = INADDR_ANY, .imr_multiaddr.s_addr = sa->s_addr, + }; +#endif + status = setsockopt(srv->conn, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, + sizeof(mreq)); + if (status == -1) + return errno; + } else if (group->ss_family == AF_INET6) { + struct sockaddr_in6 *sa = (struct sockaddr_in6 *)group; + + int status = setsockopt(srv->conn, IPPROTO_IPV6, IPV6_MULTICAST_LOOP, + &loop_back, sizeof(loop_back)); + if (status == -1) + return errno; + + status = setsockopt(srv->conn, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, &ttl, + sizeof(ttl)); + if (status == -1) + return errno; + + struct ipv6_mreq mreq6 = { + .ipv6mr_interface = if_nametoindex(srv->interface), + }; + memmove(&mreq6.ipv6mr_multiaddr, &sa->sin6_addr, sizeof(struct in6_addr)); + + status = setsockopt(srv->conn, IPPROTO_IPV6, IPV6_JOIN_GROUP, &mreq6, + sizeof(mreq6)); + if (status == -1) + return errno; + } else { + return EINVAL; + } + + return 0; +} + +static int server_bind_socket(lcc_listener_t *srv, struct addrinfo const *ai) { + /* allow multiple sockets to use the same PORT number */ + if (setsockopt(srv->conn, SOL_SOCKET, SO_REUSEADDR, &(int){1}, sizeof(int)) == + -1) { + return errno; + } + + if (bind(srv->conn, ai->ai_addr, ai->ai_addrlen) == -1) { + return -1; + } + + if (is_multicast(ai)) { + int status = server_multicast_join(srv, (void *)ai->ai_addr, /* loop = */ 1, + /* ttl = */ 16); + if (status != 0) + return status; + } + + return 0; +} + +static int server_open(lcc_listener_t *srv) { + struct addrinfo *res = NULL; + int status = getaddrinfo(srv->node ? srv->node : "::", + srv->service ? srv->service : LCC_DEFAULT_PORT, + &(struct addrinfo){ + .ai_flags = AI_ADDRCONFIG, + .ai_family = AF_UNSPEC, + .ai_socktype = SOCK_DGRAM, + }, + &res); + if (status != 0) + return status; + + for (struct addrinfo *ai = res; ai != NULL; ai = ai->ai_next) { + srv->conn = socket(ai->ai_family, ai->ai_socktype, 0); + if (srv->conn == -1) + continue; + + status = server_bind_socket(srv, ai); + if (status != 0) { + close(srv->conn); + srv->conn = -1; + continue; + } + + break; + } + + freeaddrinfo(res); + + if (srv->conn >= 0) + return 0; + return status != 0 ? status : -1; +} + +int lcc_listen_and_write(lcc_listener_t srv) { + _Bool close_socket = 0; + + if (srv.conn < 0) { + int status = server_open(&srv); + if (status != 0) + return status; + close_socket = 1; + } + + if (srv.buffer_size == 0) + srv.buffer_size = LCC_NETWORK_BUFFER_SIZE; + + if (srv.parser == NULL) + srv.parser = lcc_network_parse; + + int ret = 0; + while (42) { + char buffer[srv.buffer_size]; + ssize_t len = recv(srv.conn, buffer, sizeof(buffer), /* flags = */ 0); + if (len == -1) { + ret = errno; + break; + } else if (len == 0) { + break; + } + + (void)srv.parser(buffer, (size_t)len, srv.parse_options); + } + + if (close_socket) { + close(srv.conn); + srv.conn = -1; + } + + return ret; +} diff --git a/src/mcelog.c b/src/mcelog.c index b8731c73..ae5a7f54 100644 --- a/src/mcelog.c +++ b/src/mcelog.c @@ -2,7 +2,7 @@ * collectd - src/mcelog.c * MIT License * - * Copyright(c) 2016 Intel Corporation. All rights reserved. + * Copyright(c) 2016-2017 Intel Corporation. All rights reserved. * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), @@ -29,9 +29,11 @@ * Krzysztof Matczak */ -#include "common.h" #include "collectd.h" +#include "common.h" +#include "utils_llist.h" + #include #include #include @@ -44,10 +46,17 @@ #define MCELOG_DIMM_NAME "DMI_NAME" #define MCELOG_CORRECTED_ERR "corrected memory errors" #define MCELOG_UNCORRECTED_ERR "uncorrected memory errors" +#define MCELOG_CORRECTED_ERR_TIMED "corrected memory timed errors" +#define MCELOG_UNCORRECTED_ERR_TIMED "uncorrected memory timed errors" +#define MCELOG_CORRECTED_ERR_TYPE_INS "corrected_memory_errors" +#define MCELOG_UNCORRECTED_ERR_TYPE_INS "uncorrected_memory_errors" typedef struct mcelog_config_s { char logfile[PATH_MAX]; /* mcelog logfile */ pthread_t tid; /* poll thread id */ + llist_t *dimms_list; /* DIMMs list */ + pthread_mutex_t dimms_lock; /* lock for dimms cache */ + _Bool persist; } mcelog_config_t; typedef struct socket_adapter_s socket_adapter_t; @@ -80,7 +89,9 @@ static int socket_write(socket_adapter_t *self, const char *msg, static int socket_reinit(socket_adapter_t *self); static int socket_receive(socket_adapter_t *self, FILE **p_file); -static mcelog_config_t g_mcelog_config = {.logfile = "/var/log/mcelog"}; +static mcelog_config_t g_mcelog_config = { + .logfile = "/var/log/mcelog", .persist = 0, +}; static socket_adapter_t socket_adapter = { .sock_fd = -1, @@ -96,31 +107,129 @@ static socket_adapter_t socket_adapter = { }; static _Bool mcelog_thread_running; +static _Bool mcelog_apply_defaults; + +static void mcelog_free_dimms_list_records(llist_t *dimms_list) { + + for (llentry_t *e = llist_head(dimms_list); e != NULL; e = e->next) { + sfree(e->key); + sfree(e->value); + } +} + +/* Create or get dimm by dimm name/location */ +static llentry_t *mcelog_dimm(const mcelog_memory_rec_t *rec, + llist_t *dimms_list) { + + char dimm_name[DATA_MAX_NAME_LEN]; + + if (strlen(rec->dimm_name) > 0) { + snprintf(dimm_name, sizeof(dimm_name), "%s_%s", rec->location, + rec->dimm_name); + } else + sstrncpy(dimm_name, rec->location, sizeof(dimm_name)); + + llentry_t *dimm_le = llist_search(g_mcelog_config.dimms_list, dimm_name); + + if (dimm_le != NULL) + return dimm_le; + + /* allocate new linked list entry */ + mcelog_memory_rec_t *dimm_mr = calloc(1, sizeof(*dimm_mr)); + if (dimm_mr == NULL) { + ERROR(MCELOG_PLUGIN ": Error allocating dimm memory item"); + return NULL; + } + char *p_name = strdup(dimm_name); + if (p_name == NULL) { + ERROR(MCELOG_PLUGIN ": strdup: error"); + free(dimm_mr); + return NULL; + } + + /* add new dimm */ + dimm_le = llentry_create(p_name, dimm_mr); + if (dimm_le == NULL) { + ERROR(MCELOG_PLUGIN ": llentry_create(): error"); + free(dimm_mr); + free(p_name); + return NULL; + } + pthread_mutex_lock(&g_mcelog_config.dimms_lock); + llist_append(g_mcelog_config.dimms_list, dimm_le); + pthread_mutex_unlock(&g_mcelog_config.dimms_lock); + + return dimm_le; +} + +static void mcelog_update_dimm_stats(llentry_t *dimm, + const mcelog_memory_rec_t *rec) { + pthread_mutex_lock(&g_mcelog_config.dimms_lock); + memcpy(dimm->value, rec, sizeof(mcelog_memory_rec_t)); + pthread_mutex_unlock(&g_mcelog_config.dimms_lock); +} static int mcelog_config(oconfig_item_t *ci) { + int use_logfile = 0, use_memory = 0; for (int i = 0; i < ci->children_num; i++) { oconfig_item_t *child = ci->children + i; - if (strcasecmp("McelogClientSocket", child->key) == 0) { - if (cf_util_get_string_buffer(child, socket_adapter.unix_sock.sun_path, - sizeof(socket_adapter.unix_sock.sun_path)) < - 0) { - ERROR(MCELOG_PLUGIN ": Invalid configuration option: \"%s\".", + if (strcasecmp("McelogLogfile", child->key) == 0) { + use_logfile = 1; + if (use_memory) { + ERROR(MCELOG_PLUGIN ": Invalid configuration option: \"%s\", Memory " + "option is already configured.", child->key); return -1; } - } else if (strcasecmp("McelogLogfile", child->key) == 0) { if (cf_util_get_string_buffer(child, g_mcelog_config.logfile, sizeof(g_mcelog_config.logfile)) < 0) { ERROR(MCELOG_PLUGIN ": Invalid configuration option: \"%s\".", child->key); return -1; } + memset(socket_adapter.unix_sock.sun_path, 0, + sizeof(socket_adapter.unix_sock.sun_path)); + } else if (strcasecmp("Memory", child->key) == 0) { + if (use_logfile) { + ERROR(MCELOG_PLUGIN ": Invalid configuration option: \"%s\", Logfile " + "option is already configured.", + child->key); + return -1; + } + use_memory = 1; + for (int j = 0; j < child->children_num; j++) { + oconfig_item_t *mem_child = child->children + j; + if (strcasecmp("McelogClientSocket", mem_child->key) == 0) { + if (cf_util_get_string_buffer( + mem_child, socket_adapter.unix_sock.sun_path, + sizeof(socket_adapter.unix_sock.sun_path)) < 0) { + ERROR(MCELOG_PLUGIN ": Invalid configuration option: \"%s\".", + mem_child->key); + return -1; + } + } else if (strcasecmp("PersistentNotification", mem_child->key) == 0) { + if (cf_util_get_boolean(mem_child, &g_mcelog_config.persist) < 0) { + ERROR(MCELOG_PLUGIN ": Invalid configuration option: \"%s\".", + mem_child->key); + return -1; + } + } else { + ERROR(MCELOG_PLUGIN ": Invalid Memory configuration option: \"%s\".", + mem_child->key); + return -1; + } + } + memset(g_mcelog_config.logfile, 0, sizeof(g_mcelog_config.logfile)); } else { ERROR(MCELOG_PLUGIN ": Invalid configuration option: \"%s\".", child->key); return -1; } } + + if (!use_logfile && !use_memory) + mcelog_apply_defaults = 1; + return 0; } @@ -148,7 +257,7 @@ static int socket_write(socket_adapter_t *self, const char *msg, const size_t len) { int ret = 0; pthread_rwlock_rdlock(&self->lock); - if (swrite(self->sock_fd, msg, len) < 0) + if (swrite(self->sock_fd, msg, len) != 0) ret = -1; pthread_rwlock_unlock(&self->lock); return ret; @@ -212,64 +321,82 @@ static int socket_reinit(socket_adapter_t *self) { return ret; } -static int mcelog_prepare_notification(notification_t *n, - const mcelog_memory_rec_t *mr) { - if (n == NULL || mr == NULL) - return -1; +static int mcelog_dispatch_mem_notifications(const mcelog_memory_rec_t *mr) { + notification_t n = {.severity = NOTIF_WARNING, + .time = cdtime(), + .plugin = MCELOG_PLUGIN, + .type = "errors"}; - if ((mr->location[0] != '\0') && - (plugin_notification_meta_add_string(n, MCELOG_SOCKET_STR, mr->location) < - 0)) { - ERROR(MCELOG_PLUGIN ": add memory location meta data failed"); - return -1; - } - if ((mr->dimm_name[0] != '\0') && - (plugin_notification_meta_add_string(n, MCELOG_DIMM_NAME, mr->dimm_name) < - 0)) { - ERROR(MCELOG_PLUGIN ": add DIMM name meta data failed"); - plugin_notification_meta_free(n->meta); - return -1; - } - if (plugin_notification_meta_add_signed_int(n, MCELOG_CORRECTED_ERR, - mr->corrected_err_total) < 0) { - ERROR(MCELOG_PLUGIN ": add corrected errors meta data failed"); - plugin_notification_meta_free(n->meta); - return -1; - } - if (plugin_notification_meta_add_signed_int( - n, "corrected memory timed errors", mr->corrected_err_timed) < 0) { - ERROR(MCELOG_PLUGIN ": add corrected timed errors meta data failed"); - plugin_notification_meta_free(n->meta); + int dispatch_corrected_notifs = 0, dispatch_uncorrected_notifs = 0; + + if (mr == NULL) return -1; - } - if ((mr->corrected_err_timed_period[0] != '\0') && - (plugin_notification_meta_add_string(n, "corrected errors time period", - mr->corrected_err_timed_period) < - 0)) { - ERROR(MCELOG_PLUGIN ": add corrected errors period meta data failed"); - plugin_notification_meta_free(n->meta); + + llentry_t *dimm = mcelog_dimm(mr, g_mcelog_config.dimms_list); + if (dimm == NULL) { + ERROR(MCELOG_PLUGIN + ": Error adding/getting dimm memory item to/from cache"); return -1; } - if (plugin_notification_meta_add_signed_int(n, MCELOG_UNCORRECTED_ERR, - mr->uncorrected_err_total) < 0) { - ERROR(MCELOG_PLUGIN ": add corrected errors meta data failed"); - plugin_notification_meta_free(n->meta); - return -1; + mcelog_memory_rec_t *mr_old = dimm->value; + if (!g_mcelog_config.persist) { + + if (mr_old->corrected_err_total != mr->corrected_err_total || + mr_old->corrected_err_timed != mr->corrected_err_timed) + dispatch_corrected_notifs = 1; + + if (mr_old->uncorrected_err_total != mr->uncorrected_err_total || + mr_old->uncorrected_err_timed != mr->uncorrected_err_timed) + dispatch_uncorrected_notifs = 1; + + if (!dispatch_corrected_notifs && !dispatch_uncorrected_notifs) { + DEBUG("%s: No new notifications to dispatch", MCELOG_PLUGIN); + return 0; + } + } else { + dispatch_corrected_notifs = 1; + dispatch_uncorrected_notifs = 1; } - if (plugin_notification_meta_add_signed_int(n, - "uncorrected memory timed errors", - mr->uncorrected_err_timed) < 0) { - ERROR(MCELOG_PLUGIN ": add corrected timed errors meta data failed"); - plugin_notification_meta_free(n->meta); - return -1; + + sstrncpy(n.host, hostname_g, sizeof(n.host)); + + if (mr->dimm_name[0] != '\0') + snprintf(n.plugin_instance, sizeof(n.plugin_instance), "%s_%s", + mr->location, mr->dimm_name); + else + sstrncpy(n.plugin_instance, mr->location, sizeof(n.plugin_instance)); + + if (dispatch_corrected_notifs && + (mr->corrected_err_total > 0 || mr->corrected_err_timed > 0)) { + /* Corrected Error Notifications */ + plugin_notification_meta_add_signed_int(&n, MCELOG_CORRECTED_ERR, + mr->corrected_err_total); + plugin_notification_meta_add_signed_int(&n, MCELOG_CORRECTED_ERR_TIMED, + mr->corrected_err_timed); + snprintf(n.message, sizeof(n.message), MCELOG_CORRECTED_ERR); + sstrncpy(n.type_instance, MCELOG_CORRECTED_ERR_TYPE_INS, + sizeof(n.type_instance)); + plugin_dispatch_notification(&n); + if (n.meta) + plugin_notification_meta_free(n.meta); + n.meta = NULL; } - if ((mr->uncorrected_err_timed_period[0] != '\0') && - (plugin_notification_meta_add_string(n, "uncorrected errors time period", - mr->uncorrected_err_timed_period) < - 0)) { - ERROR(MCELOG_PLUGIN ": add corrected errors period meta data failed"); - plugin_notification_meta_free(n->meta); - return -1; + + if (dispatch_uncorrected_notifs && + (mr->uncorrected_err_total > 0 || mr->uncorrected_err_timed > 0)) { + /* Uncorrected Error Notifications */ + plugin_notification_meta_add_signed_int(&n, MCELOG_UNCORRECTED_ERR, + mr->uncorrected_err_total); + plugin_notification_meta_add_signed_int(&n, MCELOG_UNCORRECTED_ERR_TIMED, + mr->uncorrected_err_timed); + snprintf(n.message, sizeof(n.message), MCELOG_UNCORRECTED_ERR); + sstrncpy(n.type_instance, MCELOG_UNCORRECTED_ERR_TYPE_INS, + sizeof(n.type_instance)); + n.severity = NOTIF_FAILURE; + plugin_dispatch_notification(&n); + if (n.meta) + plugin_notification_meta_free(n.meta); + n.meta = NULL; } return 0; @@ -282,13 +409,22 @@ static int mcelog_submit(const mcelog_memory_rec_t *mr) { return -1; } + llentry_t *dimm = mcelog_dimm(mr, g_mcelog_config.dimms_list); + if (dimm == NULL) { + ERROR(MCELOG_PLUGIN + ": Error adding/getting dimm memory item to/from cache"); + return -1; + } + value_list_t vl = { .values_len = 1, .values = &(value_t){.derive = (derive_t)mr->corrected_err_total}, .time = cdtime(), .plugin = MCELOG_PLUGIN, .type = "errors", - .type_instance = "corrected_memory_errors"}; + .type_instance = MCELOG_CORRECTED_ERR_TYPE_INS}; + + mcelog_update_dimm_stats(dimm, mr); if (mr->dimm_name[0] != '\0') snprintf(vl.plugin_instance, sizeof(vl.plugin_instance), "%s_%s", @@ -303,7 +439,7 @@ static int mcelog_submit(const mcelog_memory_rec_t *mr) { vl.values = &(value_t){.derive = (derive_t)mr->corrected_err_timed}; plugin_dispatch_values(&vl); - sstrncpy(vl.type_instance, "uncorrected_memory_errors", + sstrncpy(vl.type_instance, MCELOG_UNCORRECTED_ERR_TYPE_INS, sizeof(vl.type_instance)); vl.values = &(value_t){.derive = (derive_t)mr->uncorrected_err_total}; plugin_dispatch_values(&vl); @@ -472,14 +608,8 @@ static void *poll_worker(__attribute__((unused)) void *arg) { continue; } - notification_t n = {.severity = NOTIF_OKAY, - .time = cdtime(), - .message = "Got memory errors info.", - .plugin = MCELOG_PLUGIN, - .type_instance = "memory_erros"}; - - if (mcelog_prepare_notification(&n, &memory_record) == 0) - mcelog_dispatch_notification(&n); + if (mcelog_dispatch_mem_notifications(&memory_record) != 0) + ERROR(MCELOG_PLUGIN ": Failed to submit memory errors notification"); if (mcelog_submit(&memory_record) != 0) ERROR(MCELOG_PLUGIN ": Failed to submit memory errors"); memset(&memory_record, 0, sizeof(memory_record)); @@ -495,15 +625,29 @@ static void *poll_worker(__attribute__((unused)) void *arg) { } static int mcelog_init(void) { + if (mcelog_apply_defaults) { + INFO(MCELOG_PLUGIN + ": No configuration selected defaulting to memory errors."); + memset(g_mcelog_config.logfile, 0, sizeof(g_mcelog_config.logfile)); + } + g_mcelog_config.dimms_list = llist_create(); + int err = pthread_mutex_init(&g_mcelog_config.dimms_lock, NULL); + if (err < 0) { + ERROR(MCELOG_PLUGIN ": plugin: failed to initialize cache lock"); + return -1; + } + if (socket_adapter.reinit(&socket_adapter) != 0) { ERROR(MCELOG_PLUGIN ": Cannot connect to client socket"); return -1; } - if (plugin_thread_create(&g_mcelog_config.tid, NULL, poll_worker, NULL, - NULL) != 0) { - ERROR(MCELOG_PLUGIN ": Error creating poll thread."); - return -1; + if (strlen(socket_adapter.unix_sock.sun_path)) { + if (plugin_thread_create(&g_mcelog_config.tid, NULL, poll_worker, NULL, + NULL) != 0) { + ERROR(MCELOG_PLUGIN ": Error creating poll thread."); + return -1; + } } return 0; } @@ -536,7 +680,12 @@ static int mcelog_shutdown(void) { ret = -1; } } - + pthread_mutex_lock(&g_mcelog_config.dimms_lock); + mcelog_free_dimms_list_records(g_mcelog_config.dimms_list); + llist_destroy(g_mcelog_config.dimms_list); + g_mcelog_config.dimms_list = NULL; + pthread_mutex_unlock(&g_mcelog_config.dimms_lock); + pthread_mutex_destroy(&g_mcelog_config.dimms_lock); ret = socket_adapter.close(&socket_adapter) || ret; pthread_rwlock_destroy(&(socket_adapter.lock)); return -ret; diff --git a/src/memcachec.c b/src/memcachec.c index c2147fd5..bd088ecd 100644 --- a/src/memcachec.c +++ b/src/memcachec.c @@ -51,6 +51,7 @@ struct web_page_s; typedef struct web_page_s web_page_t; struct web_page_s /* {{{ */ { + char *plugin_name; char *instance; char *server; @@ -94,6 +95,7 @@ static void cmc_web_page_free(web_page_t *wp) /* {{{ */ memcached_free(wp->memc); wp->memc = NULL; + sfree(wp->plugin_name); sfree(wp->instance); sfree(wp->server); sfree(wp->key); @@ -302,6 +304,8 @@ static int cmc_config_add_page(oconfig_item_t *ci) /* {{{ */ status = cmc_config_add_string("Server", &page->server, child); else if (strcasecmp("Key", child->key) == 0) status = cmc_config_add_string("Key", &page->key, child); + else if (strcasecmp("Plugin", child->key) == 0) + status = cmc_config_add_string("Plugin", &page->plugin_name, child); else if (strcasecmp("Match", child->key) == 0) /* Be liberal with failing matches => don't set `status'. */ cmc_config_add_match(page, child); @@ -407,7 +411,8 @@ static void cmc_submit(const web_page_t *wp, const web_match_t *wm, /* {{{ */ vl.values = &value; vl.values_len = 1; - sstrncpy(vl.plugin, "memcachec", sizeof(vl.plugin)); + sstrncpy(vl.plugin, (wp->plugin_name != NULL) ? wp->plugin_name : "memcachec", + sizeof (vl.plugin)); sstrncpy(vl.plugin_instance, wp->instance, sizeof(vl.plugin_instance)); sstrncpy(vl.type, wm->type, sizeof(vl.type)); sstrncpy(vl.type_instance, wm->instance, sizeof(vl.type_instance)); diff --git a/src/memcached.c b/src/memcached.c index e4ccbce6..85794a5c 100644 --- a/src/memcached.c +++ b/src/memcached.c @@ -5,6 +5,7 @@ * Copyright (C) 2009 Doug MacEachern * Copyright (C) 2009 Franck Lombardi * Copyright (C) 2012 Nicolas Szalay + * Copyright (C) 2017 Pavel Rochnyak * * 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 @@ -26,6 +27,7 @@ * Doug MacEachern * Franck Lombardi * Nicolas Szalay + * Pavel Rochnyak **/ #include "collectd.h" @@ -38,8 +40,23 @@ #include #include +#include + #define MEMCACHED_DEF_HOST "127.0.0.1" #define MEMCACHED_DEF_PORT "11211" +#define MEMCACHED_CONNECT_TIMEOUT 10000 +#define MEMCACHED_IO_TIMEOUT 5000 + +struct prev_s { + derive_t hits; + derive_t gets; + derive_t incr_hits; + derive_t incr_misses; + derive_t decr_hits; + derive_t decr_misses; +}; + +typedef struct prev_s prev_t; struct memcached_s { char *name; @@ -47,6 +64,8 @@ struct memcached_s { char *socket; char *connhost; char *connport; + int fd; + prev_t prev; }; typedef struct memcached_s memcached_t; @@ -57,6 +76,12 @@ static void memcached_free(void *arg) { if (st == NULL) return; + if (st->fd >= 0) { + shutdown(st->fd, SHUT_RDWR); + close(st->fd); + st->fd = -1; + } + sfree(st->name); sfree(st->host); sfree(st->socket); @@ -67,13 +92,12 @@ static void memcached_free(void *arg) { static int memcached_connect_unix(memcached_t *st) { struct sockaddr_un serv_addr = {0}; - int fd; serv_addr.sun_family = AF_UNIX; sstrncpy(serv_addr.sun_path, st->socket, sizeof(serv_addr.sun_path)); /* create our socket descriptor */ - fd = socket(AF_UNIX, SOCK_STREAM, 0); + int fd = socket(AF_UNIX, SOCK_STREAM, 0); if (fd < 0) { char errbuf[1024]; ERROR("memcached plugin: memcached_connect_unix: socket(2) failed: %s", @@ -86,7 +110,15 @@ static int memcached_connect_unix(memcached_t *st) { if (status != 0) { shutdown(fd, SHUT_RDWR); close(fd); - fd = -1; + return -1; + } + + /* switch to non-blocking mode */ + int flags = fcntl(fd, F_GETFL); + status = fcntl(fd, F_SETFL, flags | O_NONBLOCK); + if (status != 0) { + close(fd); + return -1; } return fd; @@ -94,14 +126,13 @@ static int memcached_connect_unix(memcached_t *st) { static int memcached_connect_inet(memcached_t *st) { struct addrinfo *ai_list; - int status; int fd = -1; struct addrinfo ai_hints = {.ai_family = AF_UNSPEC, .ai_flags = AI_ADDRCONFIG, .ai_socktype = SOCK_STREAM}; - status = getaddrinfo(st->connhost, st->connport, &ai_hints, &ai_list); + int status = getaddrinfo(st->connhost, st->connport, &ai_hints, &ai_list); if (status != 0) { char errbuf[1024]; ERROR("memcached plugin: memcached_connect_inet: " @@ -124,16 +155,47 @@ static int memcached_connect_inet(memcached_t *st) { continue; } + /* switch socket to non-blocking mode */ + int flags = fcntl(fd, F_GETFL); + status = fcntl(fd, F_SETFL, flags | O_NONBLOCK); + if (status != 0) { + close(fd); + fd = -1; + continue; + } + /* connect to the memcached daemon */ status = (int)connect(fd, ai_ptr->ai_addr, ai_ptr->ai_addrlen); - if (status != 0) { + if (status != 0 && errno != EINPROGRESS) { shutdown(fd, SHUT_RDWR); close(fd); fd = -1; continue; } - /* A socket could be opened and connecting succeeded. We're done. */ + /* Wait until connection establishes */ + struct pollfd pollfd = { + .fd = fd, .events = POLLOUT, + }; + do + status = poll(&pollfd, 1, MEMCACHED_CONNECT_TIMEOUT); + while (status < 0 && errno == EINTR); + if (status <= 0) { + close(fd); + fd = -1; + continue; + } + + /* Check if all is good */ + int socket_error; + status = getsockopt(fd, SOL_SOCKET, SO_ERROR, (void *)&socket_error, + &(socklen_t){sizeof(socket_error)}); + if (status != 0 || socket_error != 0) { + close(fd); + fd = -1; + continue; + } + /* A socket is opened and connection succeeded. We're done. */ break; } @@ -141,32 +203,55 @@ static int memcached_connect_inet(memcached_t *st) { return fd; } /* int memcached_connect_inet */ -static int memcached_connect(memcached_t *st) { +static void memcached_connect(memcached_t *st) { + if (st->fd >= 0) + return; + if (st->socket != NULL) - return memcached_connect_unix(st); + st->fd = memcached_connect_unix(st); else - return memcached_connect_inet(st); + st->fd = memcached_connect_inet(st); + + if (st->fd >= 0) + INFO("memcached plugin: Instance \"%s\": connection established.", + st->name); } static int memcached_query_daemon(char *buffer, size_t buffer_size, memcached_t *st) { - int fd, status; + int status; size_t buffer_fill; - fd = memcached_connect(st); - if (fd < 0) { + memcached_connect(st); + if (st->fd < 0) { ERROR("memcached plugin: Instance \"%s\" could not connect to daemon.", st->name); return -1; } - status = (int)swrite(fd, "stats\r\n", strlen("stats\r\n")); + struct pollfd pollfd = { + .fd = st->fd, .events = POLLOUT, + }; + + do + status = poll(&pollfd, 1, MEMCACHED_IO_TIMEOUT); + while (status < 0 && errno == EINTR); + + if (status <= 0) { + ERROR("memcached plugin: poll() failed for write() call."); + close(st->fd); + st->fd = -1; + return -1; + } + + status = (int)swrite(st->fd, "stats\r\n", strlen("stats\r\n")); if (status != 0) { char errbuf[1024]; - ERROR("memcached plugin: write(2) failed: %s", + ERROR("memcached plugin: Instance \"%s\": write(2) failed: %s", st->name, sstrerror(errno, errbuf, sizeof(errbuf))); - shutdown(fd, SHUT_RDWR); - close(fd); + shutdown(st->fd, SHUT_RDWR); + close(st->fd); + st->fd = -1; return -1; } @@ -174,27 +259,48 @@ static int memcached_query_daemon(char *buffer, size_t buffer_size, memset(buffer, 0, buffer_size); buffer_fill = 0; - while ((status = (int)recv(fd, buffer + buffer_fill, - buffer_size - buffer_fill, /* flags = */ 0)) != - 0) { + pollfd.events = POLLIN; + while (1) { + do + status = poll(&pollfd, 1, MEMCACHED_IO_TIMEOUT); + while (status < 0 && errno == EINTR); + + if (status <= 0) { + ERROR("memcached plugin: Instance \"%s\": Timeout reading from socket", + st->name); + close(st->fd); + st->fd = -1; + return -1; + } + + do + status = (int)recv(st->fd, buffer + buffer_fill, + buffer_size - buffer_fill, /* flags = */ 0); + while (status < 0 && errno == EINTR); + char const end_token[5] = {'E', 'N', 'D', '\r', '\n'}; if (status < 0) { char errbuf[1024]; - if ((errno == EAGAIN) || (errno == EINTR)) + if ((errno == EAGAIN) || (errno == EWOULDBLOCK)) continue; - ERROR("memcached: Error reading from socket: %s", - sstrerror(errno, errbuf, sizeof(errbuf))); - shutdown(fd, SHUT_RDWR); - close(fd); + ERROR("memcached plugin: Instance \"%s\": Error reading from socket: %s", + st->name, sstrerror(errno, errbuf, sizeof(errbuf))); + shutdown(st->fd, SHUT_RDWR); + close(st->fd); + st->fd = -1; return -1; } buffer_fill += (size_t)status; if (buffer_fill > buffer_size) { buffer_fill = buffer_size; - WARNING("memcached plugin: Message was truncated."); + WARNING("memcached plugin: Instance \"%s\": Message was truncated.", + st->name); + shutdown(st->fd, SHUT_RDWR); + close(st->fd); + st->fd = -1; break; } @@ -206,12 +312,11 @@ static int memcached_query_daemon(char *buffer, size_t buffer_size, status = 0; if (buffer_fill == 0) { - WARNING("memcached plugin: No data returned by memcached."); + WARNING("memcached plugin: Instance \"%s\": No data returned by memcached.", + st->name); status = -1; } - shutdown(fd, SHUT_RDWR); - close(fd); return status; } /* int memcached_query_daemon */ @@ -285,29 +390,74 @@ static void submit_gauge2(const char *type, const char *type_inst, plugin_dispatch_values(&vl); } +static gauge_t calculate_ratio_percent(derive_t part, derive_t total, + derive_t *prev_part, + derive_t *prev_total) { + if ((*prev_part == 0) || (*prev_total == 0) || (part < *prev_part) || + (total < *prev_total)) { + *prev_part = part; + *prev_total = total; + return NAN; + } + + derive_t num = part - *prev_part; + derive_t denom = total - *prev_total; + + *prev_part = part; + *prev_total = total; + + if (denom == 0) + return NAN; + + if (num == 0) + return 0; + + return 100.0 * (gauge_t)num / (gauge_t)denom; +} + +static gauge_t calculate_ratio_percent2(derive_t part1, derive_t part2, + derive_t *prev1, derive_t *prev2) { + if ((*prev1 == 0) || (*prev2 == 0) || (part1 < *prev1) || (part2 < *prev2)) { + *prev1 = part1; + *prev2 = part2; + return NAN; + } + + derive_t num = part1 - *prev1; + derive_t denom = part2 - *prev2 + num; + + *prev1 = part1; + *prev2 = part2; + + if (denom == 0) + return NAN; + + if (num == 0) + return 0; + + return 100.0 * (gauge_t)num / (gauge_t)denom; +} + static int memcached_read(user_data_t *user_data) { char buf[4096]; char *fields[3]; - char *ptr; char *line; - char *saveptr; - int fields_num; - - gauge_t bytes_used = NAN; - gauge_t bytes_total = NAN; - gauge_t hits = NAN; - gauge_t gets = NAN; - gauge_t incr_hits = NAN; - derive_t incr = 0; - gauge_t decr_hits = NAN; - derive_t decr = 0; + + derive_t bytes_used = 0; + derive_t bytes_total = 0; + derive_t get_hits = 0; + derive_t cmd_get = 0; + derive_t incr_hits = 0; + derive_t incr_misses = 0; + derive_t decr_hits = 0; + derive_t decr_misses = 0; derive_t rusage_user = 0; derive_t rusage_syst = 0; derive_t octets_rx = 0; derive_t octets_tx = 0; - memcached_t *st; - st = user_data->data; + memcached_t *st = user_data->data; + prev_t *prev = &st->prev; /* get data from daemon */ if (memcached_query_daemon(buf, sizeof(buf), st) < 0) { @@ -317,18 +467,15 @@ static int memcached_read(user_data_t *user_data) { #define FIELD_IS(cnst) \ (((sizeof(cnst) - 1) == name_len) && (strcmp(cnst, fields[1]) == 0)) - ptr = buf; - saveptr = NULL; + char *ptr = buf; + char *saveptr = NULL; while ((line = strtok_r(ptr, "\n\r", &saveptr)) != NULL) { - int name_len; - ptr = NULL; - fields_num = strsplit(line, fields, 3); - if (fields_num != 3) + if (strsplit(line, fields, 3) != 3) continue; - name_len = strlen(fields[1]); + int name_len = strlen(fields[1]); if (name_len == 0) continue; @@ -341,9 +488,10 @@ static int memcached_read(user_data_t *user_data) { * CPU time consumed by the memcached process */ if (FIELD_IS("rusage_user")) { - rusage_user = atoll(fields[2]); + /* Convert to useconds */ + rusage_user = atof(fields[2]) * 1000000; } else if (FIELD_IS("rusage_system")) { - rusage_syst = atoll(fields[2]); + rusage_syst = atof(fields[2]) * 1000000; } /* @@ -364,9 +512,9 @@ static int memcached_read(user_data_t *user_data) { * Number of bytes used and available (total - used) */ else if (FIELD_IS("bytes")) { - bytes_used = atof(fields[2]); + bytes_used = atoll(fields[2]); } else if (FIELD_IS("limit_maxbytes")) { - bytes_total = atof(fields[2]); + bytes_total = atoll(fields[2]); } /* @@ -375,7 +523,14 @@ static int memcached_read(user_data_t *user_data) { else if (FIELD_IS("curr_connections")) { submit_gauge("memcached_connections", "current", atof(fields[2]), st); } else if (FIELD_IS("listen_disabled_num")) { - submit_derive("connections", "listen_disabled", atof(fields[2]), st); + submit_derive("connections", "listen_disabled", atoll(fields[2]), st); + } + /* + * Total number of connections opened since the server started running + * Report this as connection rate. + */ + else if (FIELD_IS("total_connections")) { + submit_derive("connections", "opened", atoll(fields[2]), st); } /* @@ -385,30 +540,24 @@ static int memcached_read(user_data_t *user_data) { const char *name = fields[1] + 4; submit_derive("memcached_command", name, atoll(fields[2]), st); if (strcmp(name, "get") == 0) - gets = atof(fields[2]); + cmd_get = atoll(fields[2]); } /* * Increment/Decrement */ else if (FIELD_IS("incr_misses")) { - derive_t incr_count = atoll(fields[2]); - submit_derive("memcached_ops", "incr_misses", incr_count, st); - incr += incr_count; + incr_misses = atoll(fields[2]); + submit_derive("memcached_ops", "incr_misses", incr_misses, st); } else if (FIELD_IS("incr_hits")) { - derive_t incr_count = atoll(fields[2]); - submit_derive("memcached_ops", "incr_hits", incr_count, st); - incr_hits = atof(fields[2]); - incr += incr_count; + incr_hits = atoll(fields[2]); + submit_derive("memcached_ops", "incr_hits", incr_hits, st); } else if (FIELD_IS("decr_misses")) { - derive_t decr_count = atoll(fields[2]); - submit_derive("memcached_ops", "decr_misses", decr_count, st); - decr += decr_count; + decr_misses = atoll(fields[2]); + submit_derive("memcached_ops", "decr_misses", decr_misses, st); } else if (FIELD_IS("decr_hits")) { - derive_t decr_count = atoll(fields[2]); - submit_derive("memcached_ops", "decr_hits", decr_count, st); - decr_hits = atof(fields[2]); - decr += decr_count; + decr_hits = atoll(fields[2]); + submit_derive("memcached_ops", "decr_hits", decr_hits, st); } /* @@ -418,8 +567,8 @@ static int memcached_read(user_data_t *user_data) { * - evictions */ else if (FIELD_IS("get_hits")) { - submit_derive("memcached_ops", "hits", atoll(fields[2]), st); - hits = atof(fields[2]); + get_hits = atoll(fields[2]); + submit_derive("memcached_ops", "hits", get_hits, st); } else if (FIELD_IS("get_misses")) { submit_derive("memcached_ops", "misses", atoll(fields[2]), st); } else if (FIELD_IS("evictions")) { @@ -440,7 +589,7 @@ static int memcached_read(user_data_t *user_data) { } } /* while ((line = strtok_r (ptr, "\n\r", &saveptr)) != NULL) */ - if (!isnan(bytes_used) && !isnan(bytes_total) && (bytes_used <= bytes_total)) + if ((bytes_total > 0) && (bytes_used <= bytes_total)) submit_gauge2("df", "cache", bytes_used, bytes_total - bytes_used, st); if ((rusage_user != 0) || (rusage_syst != 0)) @@ -449,37 +598,30 @@ static int memcached_read(user_data_t *user_data) { if ((octets_rx != 0) || (octets_tx != 0)) submit_derive2("memcached_octets", NULL, octets_rx, octets_tx, st); - if (!isnan(gets) && !isnan(hits)) { - gauge_t rate = NAN; - - if (gets != 0.0) - rate = 100.0 * hits / gets; - - submit_gauge("percent", "hitratio", rate, st); + if ((cmd_get != 0) && (get_hits != 0)) { + gauge_t ratio = + calculate_ratio_percent(get_hits, cmd_get, &prev->hits, &prev->gets); + submit_gauge("percent", "hitratio", ratio, st); } - if (!isnan(incr_hits) && incr != 0) { - gauge_t incr_rate = 100.0 * incr_hits / incr; - submit_gauge("percent", "incr_hitratio", incr_rate, st); - submit_derive("memcached_ops", "incr", incr, st); + if ((incr_hits != 0) && (incr_misses != 0)) { + gauge_t ratio = calculate_ratio_percent2( + incr_hits, incr_misses, &prev->incr_hits, &prev->incr_misses); + submit_gauge("percent", "incr_hitratio", ratio, st); + submit_derive("memcached_ops", "incr", incr_hits + incr_misses, st); } - if (!isnan(decr_hits) && decr != 0) { - gauge_t decr_rate = 100.0 * decr_hits / decr; - submit_gauge("percent", "decr_hitratio", decr_rate, st); - submit_derive("memcached_ops", "decr", decr, st); + if ((decr_hits != 0) && (decr_misses != 0)) { + gauge_t ratio = calculate_ratio_percent2( + decr_hits, decr_misses, &prev->decr_hits, &prev->decr_misses); + submit_gauge("percent", "decr_hitratio", ratio, st); + submit_derive("memcached_ops", "decr", decr_hits + decr_misses, st); } return 0; } /* int memcached_read */ -static int memcached_add_read_callback(memcached_t *st) { - char callback_name[3 * DATA_MAX_NAME_LEN]; - int status; - - snprintf(callback_name, sizeof(callback_name), "memcached/%s", - (st->name != NULL) ? st->name : "__legacy__"); - +static int memcached_set_defaults(memcached_t *st) { /* If no
used then: * - Connect to the destination specified by , if present. * If not, use the default address. @@ -516,7 +658,28 @@ static int memcached_add_read_callback(memcached_t *st) { assert(st->connhost != NULL); assert(st->connport != NULL); - status = plugin_register_complex_read( + st->prev.hits = 0; + st->prev.gets = 0; + st->prev.incr_hits = 0; + st->prev.incr_misses = 0; + st->prev.decr_hits = 0; + st->prev.decr_misses = 0; + + return 0; +} /* int memcached_set_defaults */ + +static int memcached_add_read_callback(memcached_t *st) { + char callback_name[3 * DATA_MAX_NAME_LEN]; + + if (memcached_set_defaults(st) != 0) { + memcached_free(st); + return -1; + } + + snprintf(callback_name, sizeof(callback_name), "memcached/%s", + (st->name != NULL) ? st->name : "__legacy__"); + + return plugin_register_complex_read( /* group = */ "memcached", /* name = */ callback_name, /* callback = */ memcached_read, @@ -524,8 +687,6 @@ static int memcached_add_read_callback(memcached_t *st) { &(user_data_t){ .data = st, .free_func = memcached_free, }); - - return status; } /* int memcached_add_read_callback */ /* Configuration handling functiions @@ -538,13 +699,12 @@ static int memcached_add_read_callback(memcached_t *st) { * */ static int config_add_instance(oconfig_item_t *ci) { - memcached_t *st; int status = 0; /* Disable automatic generation of default instance in the init callback. */ memcached_have_instances = 1; - st = calloc(1, sizeof(*st)); + memcached_t *st = calloc(1, sizeof(*st)); if (st == NULL) { ERROR("memcached plugin: calloc failed."); return ENOMEM; @@ -556,6 +716,8 @@ static int config_add_instance(oconfig_item_t *ci) { st->connhost = NULL; st->connport = NULL; + st->fd = -1; + if (strcasecmp(ci->key, "Instance") == 0) status = cf_util_get_string(ci, &st->name); @@ -584,19 +746,15 @@ static int config_add_instance(oconfig_item_t *ci) { break; } - if (status == 0) - status = memcached_add_read_callback(st); - if (status != 0) { memcached_free(st); return -1; } - return 0; -} + return memcached_add_read_callback(st); +} /* int config_add_instance */ static int memcached_config(oconfig_item_t *ci) { - int status = 0; _Bool have_instance_block = 0; for (int i = 0; i < ci->children_num; i++) { @@ -617,18 +775,16 @@ static int memcached_config(oconfig_item_t *ci) { child->key); } /* for (ci->children) */ - return status; -} + return 0; +} /* int memcached_config */ static int memcached_init(void) { - memcached_t *st; - int status; if (memcached_have_instances) return 0; /* No instances were configured, lets start a default instance. */ - st = calloc(1, sizeof(*st)); + memcached_t *st = calloc(1, sizeof(*st)); if (st == NULL) return ENOMEM; st->name = NULL; @@ -637,11 +793,11 @@ static int memcached_init(void) { st->connhost = NULL; st->connport = NULL; - status = memcached_add_read_callback(st); + st->fd = -1; + + int status = memcached_add_read_callback(st); if (status == 0) memcached_have_instances = 1; - else - memcached_free(st); return status; } /* int memcached_init */ diff --git a/src/mic.c b/src/mic.c index a7ee433c..3f9521d8 100644 --- a/src/mic.c +++ b/src/mic.c @@ -163,7 +163,6 @@ static void mic_submit_temp(int micnumber, const char *type, gauge_t value) { vl.values = &(value_t){.gauge = value}; vl.values_len = 1; - strncpy(vl.host, hostname_g, sizeof(vl.host)); strncpy(vl.plugin, "mic", sizeof(vl.plugin)); snprintf(vl.plugin_instance, sizeof(vl.plugin_instance), "%i", micnumber); strncpy(vl.type, "temperature", sizeof(vl.type)); @@ -205,7 +204,6 @@ static void mic_submit_cpu(int micnumber, const char *type_instance, int core, vl.values = &(value_t){.derive = value}; vl.values_len = 1; - strncpy(vl.host, hostname_g, sizeof(vl.host)); strncpy(vl.plugin, "mic", sizeof(vl.plugin)); if (core < 0) /* global aggregation */ snprintf(vl.plugin_instance, sizeof(vl.plugin_instance), "%i", micnumber); @@ -259,7 +257,6 @@ static void mic_submit_power(int micnumber, const char *type, vl.values = &(value_t){.gauge = value}; vl.values_len = 1; - strncpy(vl.host, hostname_g, sizeof(vl.host)); strncpy(vl.plugin, "mic", sizeof(vl.plugin)); snprintf(vl.plugin_instance, sizeof(vl.plugin_instance), "%i", micnumber); strncpy(vl.type, type, sizeof(vl.type)); diff --git a/src/mqtt.c b/src/mqtt.c index 851866b0..51644855 100644 --- a/src/mqtt.c +++ b/src/mqtt.c @@ -525,10 +525,10 @@ static int mqtt_write(const data_set_t *ds, const value_list_t *vl, * StoreRates true * Retain false * QoS 0 - * CACert "ca.pem" Enables TLS if set - * CertificateFile "client-cert.pem" optional - * CertificateKeyFile "client-key.pem" optional - * TLSProtocol "tlsv1.2" optional + * CACert "ca.pem" Enables TLS if set + * CertificateFile "client-cert.pem" optional + * CertificateKeyFile "client-key.pem" optional + * TLSProtocol "tlsv1.2" optional * */ static int mqtt_config_publisher(oconfig_item_t *ci) { @@ -624,6 +624,10 @@ static int mqtt_config_publisher(oconfig_item_t *ci) { * User "guest" * Password "secret" * Topic "collectd/#" + * CACert "ca.pem" Enables TLS if set + * CertificateFile "client-cert.pem" optional + * CertificateKeyFile "client-key.pem" optional + * TLSProtocol "tlsv1.2" optional * */ static int mqtt_config_subscriber(oconfig_item_t *ci) { @@ -687,6 +691,16 @@ static int mqtt_config_subscriber(oconfig_item_t *ci) { cf_util_get_string(child, &conf->topic); else if (strcasecmp("CleanSession", child->key) == 0) cf_util_get_boolean(child, &conf->clean_session); + else if (strcasecmp("CACert", child->key) == 0) + cf_util_get_string(child, &conf->cacertificatefile); + else if (strcasecmp("CertificateFile", child->key) == 0) + cf_util_get_string(child, &conf->certificatefile); + else if (strcasecmp("CertificateKeyFile", child->key) == 0) + cf_util_get_string(child, &conf->certificatekeyfile); + else if (strcasecmp("TLSProtocol", child->key) == 0) + cf_util_get_string(child, &conf->tlsprotocol); + else if (strcasecmp("CipherSuite", child->key) == 0) + cf_util_get_string(child, &conf->ciphersuite); else ERROR("mqtt plugin: Unknown config option: %s", child->key); } diff --git a/src/msr-index.h b/src/msr-index.h new file mode 100644 index 00000000..2adca577 --- /dev/null +++ b/src/msr-index.h @@ -0,0 +1,88 @@ +/* + * Partial header file imported from the linux kernel + * (arch/x86/include/asm/msr-index.h) + * as it is not provided by the kernel sources anymore + * + * Only the minimal blocks of macro have been included + * ---- + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope 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. + * ---- + */ + +#ifndef _ASM_X86_MSR_INDEX_H +#define _ASM_X86_MSR_INDEX_H + +/* + * CPU model specific register (MSR) numbers. + * + * Do not add new entries to this file unless the definitions are shared + * between multiple compilation units. + */ + +/* Intel MSRs. Some also available on other CPUs */ + +/* C-state Residency Counters */ +#define MSR_PKG_C3_RESIDENCY 0x000003f8 +#define MSR_PKG_C6_RESIDENCY 0x000003f9 +#define MSR_ATOM_PKG_C6_RESIDENCY 0x000003fa +#define MSR_PKG_C7_RESIDENCY 0x000003fa +#define MSR_CORE_C3_RESIDENCY 0x000003fc +#define MSR_CORE_C6_RESIDENCY 0x000003fd +#define MSR_CORE_C7_RESIDENCY 0x000003fe +#define MSR_KNL_CORE_C6_RESIDENCY 0x000003ff +#define MSR_PKG_C2_RESIDENCY 0x0000060d +#define MSR_PKG_C8_RESIDENCY 0x00000630 +#define MSR_PKG_C9_RESIDENCY 0x00000631 +#define MSR_PKG_C10_RESIDENCY 0x00000632 + +/* Run Time Average Power Limiting (RAPL) Interface */ + +#define MSR_RAPL_POWER_UNIT 0x00000606 + +#define MSR_PKG_POWER_LIMIT 0x00000610 +#define MSR_PKG_ENERGY_STATUS 0x00000611 +#define MSR_PKG_PERF_STATUS 0x00000613 +#define MSR_PKG_POWER_INFO 0x00000614 + +#define MSR_DRAM_POWER_LIMIT 0x00000618 +#define MSR_DRAM_ENERGY_STATUS 0x00000619 +#define MSR_DRAM_PERF_STATUS 0x0000061b +#define MSR_DRAM_POWER_INFO 0x0000061c + +#define MSR_PP0_POWER_LIMIT 0x00000638 +#define MSR_PP0_ENERGY_STATUS 0x00000639 +#define MSR_PP0_POLICY 0x0000063a +#define MSR_PP0_PERF_STATUS 0x0000063b + +#define MSR_PP1_POWER_LIMIT 0x00000640 +#define MSR_PP1_ENERGY_STATUS 0x00000641 +#define MSR_PP1_POLICY 0x00000642 + + + +/* Intel defined MSRs. */ +#define MSR_IA32_TSC 0x00000010 +#define MSR_SMI_COUNT 0x00000034 + +#define MSR_IA32_MPERF 0x000000e7 +#define MSR_IA32_APERF 0x000000e8 + +#define MSR_IA32_THERM_STATUS 0x0000019c + +#define MSR_IA32_TEMPERATURE_TARGET 0x000001a2 + +#define MSR_IA32_PACKAGE_THERM_STATUS 0x000001b1 + + +#endif /* _ASM_X86_MSR_INDEX_H */ diff --git a/src/multimeter.c b/src/multimeter.c index 72b0fed9..fc69e02d 100644 --- a/src/multimeter.c +++ b/src/multimeter.c @@ -62,7 +62,7 @@ static int multimeter_read_value(double *value) { struct timeval time_now; status = swrite(fd, "D", 1); - if (status < 0) { + if (status != 0) { ERROR("multimeter plugin: swrite failed."); return -1; } diff --git a/src/network.c b/src/network.c index 375da847..4e684215 100644 --- a/src/network.c +++ b/src/network.c @@ -1230,9 +1230,9 @@ static int parse_part_encr_aes256(sockent_t *se, /* {{{ */ part_size - buffer_offset, /* in = */ NULL, /* in len = */ 0); if (err != 0) { - sfree(pea.username); ERROR("network plugin: gcry_cipher_decrypt returned: %s. Username: %s", gcry_strerror(err), pea.username); + sfree(pea.username); return -1; } @@ -1254,8 +1254,6 @@ static int parse_part_encr_aes256(sockent_t *se, /* {{{ */ parse_packet(se, buffer + buffer_offset, payload_len, flags | PP_ENCRYPTED, pea.username); - /* XXX: Free pea.username?!? */ - /* Update return values */ *ret_buffer = buffer + part_size; *ret_buffer_len = buffer_len - part_size; @@ -2652,10 +2650,10 @@ static int network_write(const data_set_t *ds, const value_list_t *vl, pthread_mutex_lock(&send_buffer_lock); - status = add_to_buffer(send_buffer_ptr, - network_config_packet_size - - (send_buffer_fill + BUFF_SIG_SIZE), - &send_buffer_vl, ds, vl); + status = + add_to_buffer(send_buffer_ptr, network_config_packet_size - + (send_buffer_fill + BUFF_SIG_SIZE), + &send_buffer_vl, ds, vl); if (status >= 0) { /* status == bytes added to the buffer */ send_buffer_fill += status; @@ -2666,10 +2664,10 @@ static int network_write(const data_set_t *ds, const value_list_t *vl, } else { flush_buffer(); - status = add_to_buffer(send_buffer_ptr, - network_config_packet_size - - (send_buffer_fill + BUFF_SIG_SIZE), - &send_buffer_vl, ds, vl); + status = + add_to_buffer(send_buffer_ptr, network_config_packet_size - + (send_buffer_fill + BUFF_SIG_SIZE), + &send_buffer_vl, ds, vl); if (status >= 0) { send_buffer_fill += status; diff --git a/src/nfs.c b/src/nfs.c index bbe84384..a7adc920 100644 --- a/src/nfs.c +++ b/src/nfs.c @@ -31,6 +31,12 @@ #include #endif +static const char *config_keys[] = {"ReportV2", "ReportV3", "ReportV4"}; +static int config_keys_num = STATIC_ARRAY_SIZE(config_keys); +static _Bool report_v2 = 1; +static _Bool report_v3 = 1; +static _Bool report_v4 = 1; + /* see /proc/net/rpc/nfs see http://www.missioncriticallinux.com/orph/NFS-Statistics @@ -295,6 +301,19 @@ static kstat_t *nfs4_ksp_client; static kstat_t *nfs4_ksp_server; #endif +static int nfs_config(const char *key, const char *value) { + if (strcasecmp(key, "ReportV2") == 0) + report_v2 = IS_TRUE(value); + else if (strcasecmp(key, "ReportV3") == 0) + report_v3 = IS_TRUE(value); + else if (strcasecmp(key, "ReportV4") == 0) + report_v4 = IS_TRUE(value); + else + return -1; + + return 0; +} + #if KERNEL_LINUX static int nfs_init(void) { return 0; } /* #endif KERNEL_LINUX */ @@ -495,18 +514,18 @@ static void nfs_read_linux(FILE *fh, const char *inst) { if (fields_num < 3) continue; - if (strcmp(fields[0], "proc2") == 0) { + if (strcmp(fields[0], "proc2") == 0 && report_v2) { nfs_submit_fields_safe(/* version = */ 2, inst, fields + 2, (size_t)(fields_num - 2), nfs2_procedures_names, nfs2_procedures_names_num); - } else if (strncmp(fields[0], "proc3", 5) == 0) { + } else if (strncmp(fields[0], "proc3", 5) == 0 && report_v3) { nfs_submit_fields_safe(/* version = */ 3, inst, fields + 2, (size_t)(fields_num - 2), nfs3_procedures_names, nfs3_procedures_names_num); - } else if (strcmp(fields[0], "proc4ops") == 0) { + } else if (strcmp(fields[0], "proc4ops") == 0 && report_v4) { if (inst[0] == 's') nfs_submit_nfs4_server(inst, fields + 2, (size_t)(fields_num - 2)); - } else if (strcmp(fields[0], "proc4") == 0) { + } else if (strcmp(fields[0], "proc4") == 0 && report_v4) { if (inst[0] == 'c') nfs_submit_nfs4_client(inst, fields + 2, (size_t)(fields_num - 2)); } @@ -561,24 +580,31 @@ static int nfs_read(void) { #elif HAVE_LIBKSTAT static int nfs_read(void) { - nfs_read_kstat(nfs2_ksp_client, /* version = */ 2, "client", - nfs2_procedures_names, nfs2_procedures_names_num); - nfs_read_kstat(nfs2_ksp_server, /* version = */ 2, "server", - nfs2_procedures_names, nfs2_procedures_names_num); - nfs_read_kstat(nfs3_ksp_client, /* version = */ 3, "client", - nfs3_procedures_names, nfs3_procedures_names_num); - nfs_read_kstat(nfs3_ksp_server, /* version = */ 3, "server", - nfs3_procedures_names, nfs3_procedures_names_num); - nfs_read_kstat(nfs4_ksp_client, /* version = */ 4, "client", - nfs4_procedures_names, nfs4_procedures_names_num); - nfs_read_kstat(nfs4_ksp_server, /* version = */ 4, "server", - nfs4_procedures_names, nfs4_procedures_names_num); + if (report_v2) { + nfs_read_kstat(nfs2_ksp_client, /* version = */ 2, "client", + nfs2_procedures_names, nfs2_procedures_names_num); + nfs_read_kstat(nfs2_ksp_server, /* version = */ 2, "server", + nfs2_procedures_names, nfs2_procedures_names_num); + } + if (report_v3) { + nfs_read_kstat(nfs3_ksp_client, /* version = */ 3, "client", + nfs3_procedures_names, nfs3_procedures_names_num); + nfs_read_kstat(nfs3_ksp_server, /* version = */ 3, "server", + nfs3_procedures_names, nfs3_procedures_names_num); + } + if (report_v4) { + nfs_read_kstat(nfs4_ksp_client, /* version = */ 4, "client", + nfs4_procedures_names, nfs4_procedures_names_num); + nfs_read_kstat(nfs4_ksp_server, /* version = */ 4, "server", + nfs4_procedures_names, nfs4_procedures_names_num); + } return 0; } #endif /* HAVE_LIBKSTAT */ void module_register(void) { + plugin_register_config("nfs", nfs_config, config_keys, config_keys_num); plugin_register_init("nfs", nfs_init); plugin_register_read("nfs", nfs_read); } /* void module_register */ diff --git a/src/ntpd.c b/src/ntpd.c index a19d05c1..0faf2a2a 100644 --- a/src/ntpd.c +++ b/src/ntpd.c @@ -669,7 +669,7 @@ static int ntpd_send_request(int req_code, int req_items, int req_size, (void *)req_data); status = swrite(sd, (const char *)&req, REQ_LEN_NOMAC); - if (status < 0) { + if (status != 0) { DEBUG("`swrite' failed. Closing socket #%i", sd); close(sd); sock_descr = sd = -1; @@ -846,9 +846,9 @@ static int ntpd_read(void) { } /* kerninfo -> estimated error */ - offset_loop = scale_loop * ((gauge_t)ntohl(ik->offset)); + offset_loop = (gauge_t)((int32_t)ntohl(ik->offset) * scale_loop); freq_loop = ntpd_read_fp(ik->freq); - offset_error = scale_error * ((gauge_t)ntohl(ik->esterror)); + offset_error = (gauge_t)((int32_t)ntohl(ik->esterror) * scale_error); DEBUG("info_kernel:\n" " pll offset = %.8g\n" diff --git a/src/nut.c b/src/nut.c index 2bd4019b..58c7d797 100644 --- a/src/nut.c +++ b/src/nut.c @@ -96,13 +96,13 @@ static int nut_add_ups(const char *name) { cb_name = ssnprintf_alloc("nut/%s", name); status = plugin_register_complex_read( - /* group = */ "nut", - /* name = */ cb_name, - /* callback = */ nut_read, - /* interval = */ 0, - /* user_data = */ &(user_data_t){ - .data = ups, .free_func = free_nut_ups_t, - }); + /* group = */ "nut", + /* name = */ cb_name, + /* callback = */ nut_read, + /* interval = */ 0, + /* user_data = */ &(user_data_t){ + .data = ups, .free_func = free_nut_ups_t, + }); sfree(cb_name); @@ -191,10 +191,8 @@ static void nut_submit(nut_ups_t *ups, const char *type, vl.values = &(value_t){.gauge = value}; vl.values_len = 1; - sstrncpy(vl.host, - (strcasecmp(ups->hostname, "localhost") == 0) ? hostname_g - : ups->hostname, - sizeof(vl.host)); + if (strcasecmp(ups->hostname, "localhost") != 0) + sstrncpy(vl.host, ups->hostname, sizeof(vl.host)); sstrncpy(vl.plugin, "nut", sizeof(vl.plugin)); sstrncpy(vl.plugin_instance, ups->upsname, sizeof(vl.plugin_instance)); sstrncpy(vl.type, type, sizeof(vl.type)); @@ -211,8 +209,8 @@ static int nut_connect(nut_ups_t *ups) { tv.tv_sec = connect_timeout / 1000; tv.tv_usec = connect_timeout % 1000; - status = upscli_tryconnect(ups->conn, ups->hostname, ups->port, ssl_flags, - &tv); + status = + upscli_tryconnect(ups->conn, ups->hostname, ups->port, ssl_flags, &tv); #else /* #if HAVE_UPSCLI_TRYCONNECT */ status = upscli_connect(ups->conn, ups->hostname, ups->port, ssl_flags); #endif diff --git a/src/openldap.c b/src/openldap.c index b3fcb10f..afe2479e 100644 --- a/src/openldap.c +++ b/src/openldap.c @@ -47,7 +47,6 @@ struct cldap_s /* {{{ */ char *password; char *cacert; char *host; - int state; _Bool starttls; int timeout; char *url; @@ -58,11 +57,10 @@ struct cldap_s /* {{{ */ }; typedef struct cldap_s cldap_t; /* }}} */ -static cldap_t **databases = NULL; -static size_t databases_num = 0; - -static void cldap_free(cldap_t *st) /* {{{ */ +static void cldap_free(void *arg) /* {{{ */ { + cldap_t *st = arg; + if (st == NULL) return; @@ -73,32 +71,30 @@ static void cldap_free(cldap_t *st) /* {{{ */ sfree(st->name); sfree(st->url); if (st->ld) - ldap_memfree(st->ld); + ldap_unbind_ext_s(st->ld, NULL, NULL); + sfree(st); } /* }}} void cldap_free */ /* initialize ldap for each host */ static int cldap_init_host(cldap_t *st) /* {{{ */ { - LDAP *ld; int rc; - if (st->state && st->ld) { + if (st->ld) { DEBUG("openldap plugin: Already connected to %s", st->url); return 0; } - rc = ldap_initialize(&ld, st->url); + rc = ldap_initialize(&st->ld, st->url); if (rc != LDAP_SUCCESS) { ERROR("openldap plugin: ldap_initialize failed: %s", ldap_err2string(rc)); - st->state = 0; - if (ld != NULL) - ldap_unbind_ext_s(ld, NULL, NULL); + if (st->ld != NULL) + ldap_unbind_ext_s(st->ld, NULL, NULL); + st->ld = NULL; return (-1); } - st->ld = ld; - ldap_set_option(st->ld, LDAP_OPT_PROTOCOL_VERSION, &st->version); ldap_set_option(st->ld, LDAP_OPT_TIMEOUT, @@ -115,13 +111,12 @@ static int cldap_init_host(cldap_t *st) /* {{{ */ } if (st->starttls != 0) { - rc = ldap_start_tls_s(ld, NULL, NULL); + rc = ldap_start_tls_s(st->ld, NULL, NULL); if (rc != LDAP_SUCCESS) { ERROR("openldap plugin: Failed to start tls on %s: %s", st->url, ldap_err2string(rc)); - st->state = 0; - if (st->ld != NULL) - ldap_unbind_ext_s(st->ld, NULL, NULL); + ldap_unbind_ext_s(st->ld, NULL, NULL); + st->ld = NULL; return (-1); } } @@ -140,13 +135,11 @@ static int cldap_init_host(cldap_t *st) /* {{{ */ if (rc != LDAP_SUCCESS) { ERROR("openldap plugin: Failed to bind to %s: %s", st->url, ldap_err2string(rc)); - st->state = 0; - if (st->ld != NULL) - ldap_unbind_ext_s(st->ld, NULL, NULL); + ldap_unbind_ext_s(st->ld, NULL, NULL); + st->ld = NULL; return (-1); } else { DEBUG("openldap plugin: Successfully connected to %s", st->url); - st->state = 1; return 0; } } /* }}} static cldap_init_host */ @@ -216,9 +209,8 @@ static int cldap_read_host(user_data_t *ud) /* {{{ */ if (rc != LDAP_SUCCESS) { ERROR("openldap plugin: Failed to execute search: %s", ldap_err2string(rc)); ldap_msgfree(result); - st->state = 0; - if (st->ld != NULL) - ldap_unbind_ext_s(st->ld, NULL, NULL); + ldap_unbind_ext_s(st->ld, NULL, NULL); + st->ld = NULL; return (-1); } @@ -463,42 +455,24 @@ static int cldap_config_add(oconfig_item_t *ci) /* {{{ */ ldap_free_urldesc(ludpp); } - if (status == 0) { - cldap_t **temp; - - temp = (cldap_t **)realloc(databases, - sizeof(*databases) * (databases_num + 1)); - - if (temp == NULL) { - ERROR("openldap plugin: realloc failed"); - status = -1; - } else { - char callback_name[3 * DATA_MAX_NAME_LEN] = {0}; - - databases = temp; - databases[databases_num] = st; - databases_num++; - - snprintf(callback_name, sizeof(callback_name), "openldap/%s/%s", - (st->host != NULL) ? st->host : hostname_g, - (st->name != NULL) ? st->name : "default"); - - status = plugin_register_complex_read(/* group = */ NULL, - /* name = */ callback_name, - /* callback = */ cldap_read_host, - /* interval = */ 0, - &(user_data_t){ - .data = st, - }); - } - } - if (status != 0) { cldap_free(st); return -1; } - return 0; + char callback_name[3 * DATA_MAX_NAME_LEN] = {0}; + + snprintf(callback_name, sizeof(callback_name), "openldap/%s/%s", + (st->host != NULL) ? st->host : hostname_g, + (st->name != NULL) ? st->name : "default"); + + return plugin_register_complex_read(/* group = */ NULL, + /* name = */ callback_name, + /* callback = */ cldap_read_host, + /* interval = */ 0, + &(user_data_t){ + .data = st, .free_func = cldap_free, + }); } /* }}} int cldap_config_add */ static int cldap_config(oconfig_item_t *ci) /* {{{ */ @@ -532,22 +506,10 @@ static int cldap_init(void) /* {{{ */ return 0; } /* }}} int cldap_init */ -static int cldap_shutdown(void) /* {{{ */ -{ - for (size_t i = 0; i < databases_num; i++) - if (databases[i]->ld != NULL) - ldap_unbind_ext_s(databases[i]->ld, NULL, NULL); - sfree(databases); - databases_num = 0; - - return 0; -} /* }}} int cldap_shutdown */ - void module_register(void) /* {{{ */ { plugin_register_complex_config("openldap", cldap_config); plugin_register_init("openldap", cldap_init); - plugin_register_shutdown("openldap", cldap_shutdown); } /* }}} void module_register */ #if defined(__APPLE__) diff --git a/src/openvpn.c b/src/openvpn.c index 143a770b..a98649b6 100644 --- a/src/openvpn.c +++ b/src/openvpn.c @@ -23,6 +23,7 @@ * Florian octo Forster * Marco Chiappero * Fabian Schuh + * Pavel Rochnyak **/ #include "collectd.h" @@ -30,35 +31,55 @@ #include "common.h" #include "plugin.h" -#define V1STRING \ +/** + * There is two main kinds of OpenVPN status file: + * - for 'single' mode (point-to-point or client mode) + * - for 'multi' mode (server with multiple clients) + * + * For 'multi' there is 3 versions of status file format: + * - version 1 - First version of status file: without line type tokens, + * comma delimited for easy machine parsing. Currently used by default. + * Added in openvpn-2.0-beta3. + * - version 2 - with line type tokens, with 'HEADER' line type, uses comma + * as a delimiter. + * Added in openvpn-2.0-beta15. + * - version 3 - The only difference from version 2 is delimiter: in version 3 + * tabs are used instead of commas. Set of fields is the same. + * Added in openvpn-2.1_rc14. + * + * For versions 2/3 there may be different sets of fields in different + * OpenVPN versions. + * + * Versions 2.0, 2.1, 2.2: + * Common Name,Real Address,Virtual Address, + * Bytes Received,Bytes Sent,Connected Since,Connected Since (time_t) + * + * Version 2.3: + * Common Name,Real Address,Virtual Address, + * Bytes Received,Bytes Sent,Connected Since,Connected Since (time_t),Username + * + * Version 2.4: + * Common Name,Real Address,Virtual Address,Virtual IPv6 Address, + * Bytes Received,Bytes Sent,Connected Since,Connected Since (time_t),Username, + * Client ID,Peer ID + * + * Current Collectd code tries to handle changes in this field set, + * if they are backward-compatible. + **/ + +#define TITLE_SINGLE "OpenVPN STATISTICS\n" +#define TITLE_V1 "OpenVPN CLIENT LIST\n" +#define TITLE_V2 "TITLE" + +#define V1HEADER \ "Common Name,Real Address,Bytes Received,Bytes Sent,Connected Since\n" -#define V2STRING \ - "HEADER,CLIENT_LIST,Common Name,Real Address,Virtual Address,Bytes " \ - "Received,Bytes Sent,Connected Since,Connected Since (time_t)\n" -#define V3STRING \ - "HEADER CLIENT_LIST Common Name Real Address Virtual Address Bytes " \ - "Received Bytes Sent Connected Since Connected Since (time_t)\n" -#define V4STRING \ - "HEADER,CLIENT_LIST,Common Name,Real Address,Virtual Address,Bytes " \ - "Received,Bytes Sent,Connected Since,Connected Since (time_t),Username\n" -#define VSSTRING "OpenVPN STATISTICS\n" struct vpn_status_s { char *file; - enum { - MULTI1 = 1, /* status-version 1 */ - MULTI2, /* status-version 2 */ - MULTI3, /* status-version 3 */ - MULTI4, /* status-version 4 */ - SINGLE = 10 /* currently no versions for single mode, maybe in the future */ - } version; char *name; }; typedef struct vpn_status_s vpn_status_t; -static vpn_status_t **vpn_list = NULL; -static int vpn_num = 0; - static _Bool new_naming_schema = 0; static _Bool collect_compression = 1; static _Bool collect_user_count = 0; @@ -71,16 +92,13 @@ static const char *config_keys[] = { static int config_keys_num = STATIC_ARRAY_SIZE(config_keys); /* Helper function - * copy-n-pasted from common.c - changed delim to "," */ + * copy-n-pasted from common.c - changed delim to ",\t" */ static int openvpn_strsplit(char *string, char **fields, size_t size) { - size_t i; - char *ptr; - char *saveptr; - - i = 0; - ptr = string; - saveptr = NULL; - while ((fields[i] = strtok_r(ptr, ",", &saveptr)) != NULL) { + size_t i = 0; + char *ptr = string; + char *saveptr = NULL; + + while ((fields[i] = strtok_r(ptr, ",\t", &saveptr)) != NULL) { ptr = NULL; i++; @@ -91,6 +109,13 @@ static int openvpn_strsplit(char *string, char **fields, size_t size) { return i; } /* int openvpn_strsplit */ +static void openvpn_free(void *arg) { + vpn_status_t *st = arg; + + sfree(st->file); + sfree(st); +} /* void openvpn_free */ + /* dispatches number of users */ static void numusers_submit(const char *pinst, const char *tinst, gauge_t value) { @@ -159,25 +184,14 @@ static int single_read(const char *name, FILE *fh) { char buffer[1024]; char *fields[4]; const int max_fields = STATIC_ARRAY_SIZE(fields); - int fields_num, read = 0; - - derive_t link_rx, link_tx; - derive_t tun_rx, tun_tx; - derive_t pre_compress, post_compress; - derive_t pre_decompress, post_decompress; - derive_t overhead_rx, overhead_tx; - - link_rx = 0; - link_tx = 0; - tun_rx = 0; - tun_tx = 0; - pre_compress = 0; - post_compress = 0; - pre_decompress = 0; - post_decompress = 0; + + derive_t link_rx = 0, link_tx = 0; + derive_t tun_rx = 0, tun_tx = 0; + derive_t pre_compress = 0, post_compress = 0; + derive_t pre_decompress = 0, post_decompress = 0; while (fgets(buffer, sizeof(buffer), fh) != NULL) { - fields_num = openvpn_strsplit(buffer, fields, max_fields); + int fields_num = openvpn_strsplit(buffer, fields, max_fields); /* status file is generated by openvpn/sig.c:print_status() * http://svn.openvpn.net/projects/openvpn/trunk/openvpn/sig.c @@ -213,8 +227,9 @@ static int single_read(const char *name, FILE *fh) { iostats_submit(name, "traffic", link_rx, link_tx); /* we need to force this order to avoid negative values with these unsigned */ - overhead_rx = (((link_rx - pre_decompress) + post_decompress) - tun_rx); - overhead_tx = (((link_tx - post_compress) + pre_compress) - tun_tx); + derive_t overhead_rx = + (((link_rx - pre_decompress) + post_decompress) - tun_rx); + derive_t overhead_tx = (((link_tx - post_compress) + pre_compress) - tun_tx); iostats_submit(name, "overhead", overhead_rx, overhead_tx); @@ -223,17 +238,16 @@ static int single_read(const char *name, FILE *fh) { compression_submit(name, "data_out", pre_compress, post_compress); } - read = 1; - - return read; + return 0; } /* int single_read */ /* for reading status version 1 */ static int multi1_read(const char *name, FILE *fh) { char buffer[1024]; char *fields[10]; - int fields_num, found_header = 0; + const int max_fields = STATIC_ARRAY_SIZE(fields); long long sum_users = 0; + _Bool found_header = 0; /* read the file until the "ROUTING TABLE" line is found (no more info after) */ @@ -241,7 +255,7 @@ static int multi1_read(const char *name, FILE *fh) { if (strcmp(buffer, "ROUTING TABLE\n") == 0) break; - if (strcmp(buffer, V1STRING) == 0) { + if (strcmp(buffer, V1HEADER) == 0) { found_header = 1; continue; } @@ -251,7 +265,7 @@ static int multi1_read(const char *name, FILE *fh) { /* we can't start reading data until this string is found */ continue; - fields_num = openvpn_strsplit(buffer, fields, STATIC_ARRAY_SIZE(fields)); + int fields_num = openvpn_strsplit(buffer, fields, max_fields); if (fields_num < 4) continue; @@ -276,325 +290,196 @@ static int multi1_read(const char *name, FILE *fh) { } if (ferror(fh)) - return 0; + return -1; + + if (found_header == 0) { + NOTICE("openvpn plugin: Unknown file format in instance %s, please " + "report this as bug. Make sure to include " + "your status file, so the plugin can " + "be adapted.", + name); + return -1; + } if (collect_user_count) numusers_submit(name, name, sum_users); - return 1; + return 0; } /* int multi1_read */ -/* for reading status version 2 */ +/* for reading status version 2 / version 3 + * status file is generated by openvpn/multi.c:multi_print_status() + * http://svn.openvpn.net/projects/openvpn/trunk/openvpn/multi.c + */ static int multi2_read(const char *name, FILE *fh) { char buffer[1024]; - char *fields[10]; + /* OpenVPN-2.4 has 11 fields of data + 2 fields for "HEADER" and "CLIENT_LIST" + * So, set array size to 20 elements, to support future extensions. + */ + char *fields[20]; const int max_fields = STATIC_ARRAY_SIZE(fields); - int fields_num, read = 0; long long sum_users = 0; - while (fgets(buffer, sizeof(buffer), fh) != NULL) { - fields_num = openvpn_strsplit(buffer, fields, max_fields); - - /* status file is generated by openvpn/multi.c:multi_print_status() - * http://svn.openvpn.net/projects/openvpn/trunk/openvpn/multi.c - * - * The line we're expecting has 8 fields. We ignore all lines - * with more or less fields. - */ - if (fields_num != 8) - continue; - - if (strcmp(fields[0], "CLIENT_LIST") != 0) - continue; - - if (collect_user_count) - /* If so, sum all users, ignore the individuals*/ - { - sum_users += 1; - } - if (collect_individual_users) { - if (new_naming_schema) { - /* plugin inst = file name, type inst = fields[1] */ - iostats_submit(name, /* vpn instance */ - fields[1], /* "Common Name" */ - atoll(fields[4]), /* "Bytes Received" */ - atoll(fields[5])); /* "Bytes Sent" */ - } else { - /* plugin inst = fields[1], type inst = "" */ - iostats_submit(fields[1], /* "Common Name" */ - NULL, /* unused when in multimode */ - atoll(fields[4]), /* "Bytes Received" */ - atoll(fields[5])); /* "Bytes Sent" */ - } - } - - read = 1; - } - - if (collect_user_count) { - numusers_submit(name, name, sum_users); - read = 1; - } - - return read; -} /* int multi2_read */ - -/* for reading status version 3 */ -static int multi3_read(const char *name, FILE *fh) { - char buffer[1024]; - char *fields[15]; - const int max_fields = STATIC_ARRAY_SIZE(fields); - int fields_num, read = 0; - long long sum_users = 0; + _Bool found_header = 0; + int idx_cname = 0; + int idx_bytes_recv = 0; + int idx_bytes_sent = 0; + int columns = 0; while (fgets(buffer, sizeof(buffer), fh) != NULL) { - fields_num = strsplit(buffer, fields, max_fields); + int fields_num = openvpn_strsplit(buffer, fields, max_fields); - /* status file is generated by openvpn/multi.c:multi_print_status() - * http://svn.openvpn.net/projects/openvpn/trunk/openvpn/multi.c - * - * The line we're expecting has 12 fields. We ignore all lines - * with more or less fields. - */ - if (fields_num != 12) { - continue; - } else { - if (strcmp(fields[0], "CLIENT_LIST") != 0) + /* Try to find section header */ + if (found_header == 0) { + if (fields_num < 2) + continue; + if (strcmp(fields[0], "HEADER") != 0) + continue; + if (strcmp(fields[1], "CLIENT_LIST") != 0) continue; - if (collect_user_count) - /* If so, sum all users, ignore the individuals*/ - { - sum_users += 1; - } - - if (collect_individual_users) { - if (new_naming_schema) { - iostats_submit(name, /* vpn instance */ - fields[1], /* "Common Name" */ - atoll(fields[4]), /* "Bytes Received" */ - atoll(fields[5])); /* "Bytes Sent" */ - } else { - iostats_submit(fields[1], /* "Common Name" */ - NULL, /* unused when in multimode */ - atoll(fields[4]), /* "Bytes Received" */ - atoll(fields[5])); /* "Bytes Sent" */ + for (int i = 2; i < fields_num; i++) { + if (strcmp(fields[i], "Common Name") == 0) { + idx_cname = i - 1; + } else if (strcmp(fields[i], "Bytes Received") == 0) { + idx_bytes_recv = i - 1; + } else if (strcmp(fields[i], "Bytes Sent") == 0) { + idx_bytes_sent = i - 1; } } - read = 1; - } - } - - if (collect_user_count) { - numusers_submit(name, name, sum_users); - read = 1; - } + DEBUG("openvpn plugin: found MULTI v2/v3 HEADER. " + "Column idx: cname: %d, bytes_recv: %d, bytes_sent: %d", + idx_cname, idx_bytes_recv, idx_bytes_sent); - return read; -} /* int multi3_read */ + if (idx_cname == 0 || idx_bytes_recv == 0 || idx_bytes_sent == 0) + break; -/* for reading status version 4 */ -static int multi4_read(const char *name, FILE *fh) { - char buffer[1024]; - char *fields[11]; - const int max_fields = STATIC_ARRAY_SIZE(fields); - int fields_num, read = 0; - long long sum_users = 0; + /* Data row has 1 field ("HEADER") less than header row */ + columns = fields_num - 1; - while (fgets(buffer, sizeof(buffer), fh) != NULL) { - fields_num = openvpn_strsplit(buffer, fields, max_fields); + found_header = 1; + continue; + } - /* status file is generated by openvpn/multi.c:multi_print_status() - * http://svn.openvpn.net/projects/openvpn/trunk/openvpn/multi.c - * - * The line we're expecting has 9 fields. We ignore all lines - * with more or less fields. + /* Header already found. Check if the line is the section data. + * If no match, then section was finished and there is no more data. + * Empty section is OK too. */ - if (fields_num != 9) - continue; + if (fields_num == 0 || strcmp(fields[0], "CLIENT_LIST") != 0) + break; - if (strcmp(fields[0], "CLIENT_LIST") != 0) - continue; + /* Check if the data line fields count matches header line. */ + if (fields_num != columns) { + ERROR("openvpn plugin: File format error in instance %s: Fields count " + "mismatch.", + name); + return -1; + } + + DEBUG("openvpn plugin: found MULTI v2/v3 CLIENT_LIST. " + "Columns: cname: %s, bytes_recv: %s, bytes_sent: %s", + fields[idx_cname], fields[idx_bytes_recv], fields[idx_bytes_sent]); if (collect_user_count) - /* If so, sum all users, ignore the individuals*/ - { sum_users += 1; - } + if (collect_individual_users) { if (new_naming_schema) { /* plugin inst = file name, type inst = fields[1] */ - iostats_submit(name, /* vpn instance */ - fields[1], /* "Common Name" */ - atoll(fields[4]), /* "Bytes Received" */ - atoll(fields[5])); /* "Bytes Sent" */ + iostats_submit(name, /* vpn instance */ + fields[idx_cname], /* "Common Name" */ + atoll(fields[idx_bytes_recv]), /* "Bytes Received" */ + atoll(fields[idx_bytes_sent])); /* "Bytes Sent" */ } else { - /* plugin inst = fields[1], type inst = "" */ - iostats_submit(fields[1], /* "Common Name" */ - NULL, /* unused when in multimode */ - atoll(fields[4]), /* "Bytes Received" */ - atoll(fields[5])); /* "Bytes Sent" */ + /* plugin inst = fields[idx_cname], type inst = "" */ + iostats_submit(fields[idx_cname], /* "Common Name" */ + NULL, /* unused when in multimode */ + atoll(fields[idx_bytes_recv]), /* "Bytes Received" */ + atoll(fields[idx_bytes_sent])); /* "Bytes Sent" */ } } + } + + if (ferror(fh)) + return -1; - read = 1; + if (found_header == 0) { + NOTICE("openvpn plugin: Unknown file format in instance %s, please " + "report this as bug. Make sure to include " + "your status file, so the plugin can " + "be adapted.", + name); + return -1; } if (collect_user_count) { numusers_submit(name, name, sum_users); - read = 1; } - return read; -} /* int multi4_read */ + return 0; +} /* int multi2_read */ /* read callback */ -static int openvpn_read(void) { - FILE *fh; - int read; - - read = 0; - - if (vpn_num == 0) - return 0; - - /* call the right read function for every status entry in the list */ - for (int i = 0; i < vpn_num; i++) { - int vpn_read = 0; - - fh = fopen(vpn_list[i]->file, "r"); - if (fh == NULL) { - char errbuf[1024]; - WARNING("openvpn plugin: fopen(%s) failed: %s", vpn_list[i]->file, - sstrerror(errno, errbuf, sizeof(errbuf))); - - continue; - } - - switch (vpn_list[i]->version) { - case SINGLE: - vpn_read = single_read(vpn_list[i]->name, fh); - break; - - case MULTI1: - vpn_read = multi1_read(vpn_list[i]->name, fh); - break; - - case MULTI2: - vpn_read = multi2_read(vpn_list[i]->name, fh); - break; - - case MULTI3: - vpn_read = multi3_read(vpn_list[i]->name, fh); - break; - - case MULTI4: - vpn_read = multi4_read(vpn_list[i]->name, fh); - break; - } - - fclose(fh); - read += vpn_read; - } - - return read ? 0 : -1; -} /* int openvpn_read */ - -static int version_detect(const char *filename) { - FILE *fh; +static int openvpn_read(user_data_t *user_data) { char buffer[1024]; - int version = 0; + int read = 0; - /* Sanity checking. We're called from the config handling routine, so - * better play it save. */ - if ((filename == NULL) || (*filename == 0)) - return 0; + vpn_status_t *st = user_data->data; - fh = fopen(filename, "r"); + FILE *fh = fopen(st->file, "r"); if (fh == NULL) { char errbuf[1024]; - WARNING("openvpn plugin: Unable to read \"%s\": %s", filename, + WARNING("openvpn plugin: fopen(%s) failed: %s", st->file, sstrerror(errno, errbuf, sizeof(errbuf))); - return 0; + + return -1; } - /* now search for the specific multimode data format */ - while ((fgets(buffer, sizeof(buffer), fh)) != NULL) { - /* we look at the first line searching for SINGLE mode configuration */ - if (strcmp(buffer, VSSTRING) == 0) { - DEBUG("openvpn plugin: found status file version SINGLE"); - version = SINGLE; - break; - } - /* searching for multi version 1 */ - else if (strcmp(buffer, V1STRING) == 0) { - DEBUG("openvpn plugin: found status file version MULTI1"); - version = MULTI1; - break; - } - /* searching for multi version 2 */ - else if (strcmp(buffer, V2STRING) == 0) { - DEBUG("openvpn plugin: found status file version MULTI2"); - version = MULTI2; - break; - } - /* searching for multi version 3 */ - else if (strcmp(buffer, V3STRING) == 0) { - DEBUG("openvpn plugin: found status file version MULTI3"); - version = MULTI3; - break; - } - /* searching for multi version 4 */ - else if (strcmp(buffer, V4STRING) == 0) { - DEBUG("openvpn plugin: found status file version MULTI4"); - version = MULTI4; - break; - } + // Try to detect file format by its first line + if ((fgets(buffer, sizeof(buffer), fh)) == NULL) { + WARNING("openvpn plugin: failed to get data from: %s", st->file); + fclose(fh); + return -1; } - if (version == 0) { - /* This is only reached during configuration, so complaining to - * the user is in order. */ + if (strcmp(buffer, TITLE_SINGLE) == 0) { // OpenVPN STATISTICS + DEBUG("openvpn plugin: found status file SINGLE"); + read = single_read(st->name, fh); + } else if (strcmp(buffer, TITLE_V1) == 0) { // OpenVPN CLIENT LIST + DEBUG("openvpn plugin: found status file MULTI version 1"); + read = multi1_read(st->name, fh); + } else if (strncmp(buffer, TITLE_V2, strlen(TITLE_V2)) == 0) { // TITLE + DEBUG("openvpn plugin: found status file MULTI version 2/3"); + read = multi2_read(st->name, fh); + } else { NOTICE("openvpn plugin: %s: Unknown file format, please " "report this as bug. Make sure to include " "your status file, so the plugin can " "be adapted.", - filename); + st->file); + read = -1; } - fclose(fh); - - return version; -} /* int version_detect */ + return read; +} /* int openvpn_read */ static int openvpn_config(const char *key, const char *value) { if (strcasecmp("StatusFile", key) == 0) { - char *status_file, *status_name, *filename; - int status_version; - vpn_status_t *temp; + char callback_name[3 * DATA_MAX_NAME_LEN]; + char *status_name; - /* try to detect the status file format */ - status_version = version_detect(value); - - if (status_version == 0) { - WARNING("openvpn plugin: unable to detect status version, " - "discarding status file \"%s\".", - value); - return 1; - } - - status_file = sstrdup(value); + char *status_file = strdup(value); if (status_file == NULL) { char errbuf[1024]; - WARNING("openvpn plugin: sstrdup failed: %s", - sstrerror(errno, errbuf, sizeof(errbuf))); + ERROR("openvpn plugin: strdup failed: %s", + sstrerror(errno, errbuf, sizeof(errbuf))); return 1; } /* it determines the file name as string starting at location filename + 1 */ - filename = strrchr(status_file, (int)'/'); + char *filename = strrchr(status_file, (int)'/'); if (filename == NULL) { /* status_file is already the file name only */ status_name = status_file; @@ -603,50 +488,38 @@ static int openvpn_config(const char *key, const char *value) { status_name = filename + 1; } - /* scan the list looking for a clone */ - for (int i = 0; i < vpn_num; i++) { - if (strcasecmp(vpn_list[i]->name, status_name) == 0) { - WARNING("openvpn plugin: status filename \"%s\" " - "already used, please choose a " - "different one.", - status_name); - sfree(status_file); - return 1; - } - } - - /* create a new vpn element since file, version and name are ok */ - temp = malloc(sizeof(*temp)); - if (temp == NULL) { + /* create a new vpn element */ + vpn_status_t *instance = calloc(1, sizeof(*instance)); + if (instance == NULL) { char errbuf[1024]; ERROR("openvpn plugin: malloc failed: %s", sstrerror(errno, errbuf, sizeof(errbuf))); sfree(status_file); return 1; } - temp->file = status_file; - temp->version = status_version; - temp->name = status_name; - - vpn_status_t **tmp_list = - realloc(vpn_list, (vpn_num + 1) * sizeof(*vpn_list)); - if (tmp_list == NULL) { - char errbuf[1024]; - ERROR("openvpn plugin: realloc failed: %s", - sstrerror(errno, errbuf, sizeof(errbuf))); - - sfree(vpn_list); - sfree(temp->file); - sfree(temp); - return 1; + instance->file = status_file; + instance->name = status_name; + + snprintf(callback_name, sizeof(callback_name), "openvpn/%s", status_name); + + int status = plugin_register_complex_read( + /* group = */ "openvpn", + /* name = */ callback_name, + /* callback = */ openvpn_read, + /* interval = */ 0, + &(user_data_t){ + .data = instance, .free_func = openvpn_free, + }); + + if (status == EINVAL) { + WARNING("openvpn plugin: status filename \"%s\" " + "already used, please choose a " + "different one.", + status_name); + return -1; } - vpn_list = tmp_list; - - vpn_list[vpn_num] = temp; - vpn_num++; - - DEBUG("openvpn plugin: status file \"%s\" added", temp->file); + DEBUG("openvpn plugin: status file \"%s\" added", instance->file); } /* if (strcasecmp ("StatusFile", key) == 0) */ else if ((strcasecmp("CollectCompression", key) == 0) || (strcasecmp("Compression", key) == 0)) /* old, deprecated name */ @@ -683,18 +556,6 @@ static int openvpn_config(const char *key, const char *value) { return 0; } /* int openvpn_config */ -/* shutdown callback */ -static int openvpn_shutdown(void) { - for (int i = 0; i < vpn_num; i++) { - sfree(vpn_list[i]->file); - sfree(vpn_list[i]); - } - - sfree(vpn_list); - - return 0; -} /* int openvpn_shutdown */ - static int openvpn_init(void) { if (!collect_individual_users && !collect_compression && !collect_user_count) { @@ -704,9 +565,6 @@ static int openvpn_init(void) { return -1; } - plugin_register_read("openvpn", openvpn_read); - plugin_register_shutdown("openvpn", openvpn_shutdown); - return 0; } /* int openvpn_init */ diff --git a/src/oracle.c b/src/oracle.c index 44bd5648..099013e3 100644 --- a/src/oracle.c +++ b/src/oracle.c @@ -62,6 +62,7 @@ struct o_database_s { char *connect_id; char *username; char *password; + char *plugin_name; udb_query_preparation_area_t **q_prep_areas; udb_query_t **queries; @@ -141,6 +142,7 @@ static void o_database_free(o_database_t *db) /* {{{ */ sfree(db->username); sfree(db->password); sfree(db->queries); + sfree(db->plugin_name); if (db->q_prep_areas != NULL) for (size_t i = 0; i < db->queries_num; ++i) @@ -192,6 +194,7 @@ static int o_config_add_database(oconfig_item_t *ci) /* {{{ */ db->connect_id = NULL; db->username = NULL; db->password = NULL; + db->plugin_name = NULL; status = cf_util_get_string(ci, &db->name); if (status != 0) { @@ -211,6 +214,8 @@ static int o_config_add_database(oconfig_item_t *ci) /* {{{ */ status = cf_util_get_string(child, &db->username); else if (strcasecmp("Password", child->key) == 0) status = cf_util_get_string(child, &db->password); + else if (strcasecmp("Plugin", child->key) == 0) + status = cf_util_get_string(child, &db->plugin_name); else if (strcasecmp("Query", child->key) == 0) status = udb_query_pick_from_list(child, queries, queries_num, &db->queries, &db->queries_num); @@ -544,7 +549,8 @@ static int o_read_database_query(o_database_t *db, /* {{{ */ status = udb_query_prepare_result( q, prep_area, (db->host != NULL) ? db->host : hostname_g, - /* plugin = */ "oracle", db->name, column_names, column_num, + /* plugin = */ (db->plugin_name != NULL) ? db->plugin_name : "oracle", + db->name, column_names, column_num, /* interval = */ 0); if (status != 0) { ERROR("oracle plugin: o_read_database_query (%s, %s): " diff --git a/src/perl.c b/src/perl.c index 7c8a6158..671d1f3f 100644 --- a/src/perl.c +++ b/src/perl.c @@ -261,12 +261,6 @@ struct { {"Collectd::NOTIF_WARNING", NOTIF_WARNING}, {"Collectd::NOTIF_OKAY", NOTIF_OKAY}, {"", 0}}; - -struct { - char name[64]; - char *var; -} g_strings[] = {{"Collectd::hostname_g", hostname_g}, {"", NULL}}; - /* * Helper functions for data type conversion. */ @@ -424,8 +418,6 @@ static int hv2value_list(pTHX_ HV *hash, value_list_t *vl) { if (NULL != (tmp = hv_fetch(hash, "host", 4, 0))) sstrncpy(vl->host, SvPV_nolen(*tmp), sizeof(vl->host)); - else - sstrncpy(vl->host, hostname_g, sizeof(vl->host)); if (NULL != (tmp = hv_fetch(hash, "plugin", 6, 0))) sstrncpy(vl->plugin, SvPV_nolen(*tmp), sizeof(vl->plugin)); @@ -1626,18 +1618,19 @@ static void _plugin_register_generic_userdata(pTHX, int type, ret = plugin_register_flush("perl", perl_flush, /* user_data = */ NULL); } - if (0 == ret) + if (0 == ret) { ret = plugin_register_flush(pluginname, perl_flush, &userdata); + } else { + free(userdata.data); + } } else { ret = -1; } if (0 == ret) XSRETURN_YES; - else { - free(userdata.data); + else XSRETURN_EMPTY; - } } /* static void _plugin_register_generic_userdata ( ... ) */ /* @@ -2100,7 +2093,7 @@ static int perl_init(void) { /* Lock the base thread to avoid race conditions with c_ithread_create(). * See https://github.com/collectd/collectd/issues/9 and * https://github.com/collectd/collectd/issues/1706 for details. - */ + */ assert(aTHX == perl_threads->head->interp); pthread_mutex_lock(&perl_threads->mutex); @@ -2191,7 +2184,7 @@ static void perl_log(int level, const char *msg, user_data_t *user_data) { /* Lock the base thread if this is not called from one of the read threads * to avoid race conditions with c_ithread_create(). See * https://github.com/collectd/collectd/issues/9 for details. - */ + */ if (aTHX == perl_threads->head->interp) pthread_mutex_lock(&perl_threads->mutex); @@ -2402,6 +2395,11 @@ static void xs_init(pTHX) { * accessing any such variable (this is basically the same as using * tie() in Perl) */ /* global strings */ + struct { + char name[64]; + char *var; + } g_strings[] = {{"Collectd::hostname_g", hostname_g}, {"", NULL}}; + for (int i = 0; '\0' != g_strings[i].name[0]; ++i) { tmp = get_sv(g_strings[i].name, 1); sv_magicext(tmp, NULL, PERL_MAGIC_ext, &g_pv_vtbl, g_strings[i].var, 0); @@ -2628,6 +2626,12 @@ static int perl_config_plugin(pTHX_ oconfig_item_t *ci) { char *plugin; HV *config; + if (NULL == perl_threads) { + log_err("A `Plugin' block was encountered but no plugin was loaded yet. " + "Put the appropriate `LoadPlugin' option in front of it."); + return -1; + } + dSP; if ((1 != ci->values_num) || (OCONFIG_TYPE_STRING != ci->values[0].type)) { diff --git a/src/postgresql.c b/src/postgresql.c index 56730b47..3b702aee 100644 --- a/src/postgresql.c +++ b/src/postgresql.c @@ -139,6 +139,7 @@ typedef struct { char *password; char *instance; + char *plugin_name; char *sslmode; @@ -250,6 +251,8 @@ static c_psql_database_t *c_psql_database_new(const char *name) { db->instance = sstrdup(name); + db->plugin_name = NULL; + db->sslmode = NULL; db->krbsrvname = NULL; @@ -300,6 +303,8 @@ static void c_psql_database_delete(void *data) { sfree(db->instance); + sfree(db->plugin_name); + sfree(db->sslmode); sfree(db->krbsrvname); @@ -539,9 +544,11 @@ static int c_psql_exec_query(c_psql_database_t *db, udb_query_t *q, else host = db->host; - status = - udb_query_prepare_result(q, prep_area, host, "postgresql", db->instance, - column_names, (size_t)column_num, db->interval); + status = udb_query_prepare_result( + q, prep_area, host, + (db->plugin_name != NULL) ? db->plugin_name : "postgresql", + db->instance, column_names, (size_t)column_num, db->interval); + if (0 != status) { log_err("udb_query_prepare_result failed with status %i.", status); BAIL_OUT(-1); @@ -1139,6 +1146,8 @@ static int c_psql_config_database(oconfig_item_t *ci) { cf_util_get_string(c, &db->password); else if (0 == strcasecmp(c->key, "Instance")) cf_util_get_string(c, &db->instance); + else if (0 == strcasecmp (c->key, "Plugin")) + cf_util_get_string (c, &db->plugin_name); else if (0 == strcasecmp(c->key, "SSLMode")) cf_util_get_string(c, &db->sslmode); else if (0 == strcasecmp(c->key, "KRBSrvName")) diff --git a/src/processes.c b/src/processes.c index 4fec161e..89cf3e0e 100644 --- a/src/processes.c +++ b/src/processes.c @@ -33,6 +33,7 @@ * Clément Stenac * Cosmin Ioiart * Pavel Rochnyack + * Wilfried Goesgens **/ #include "collectd.h" @@ -168,6 +169,7 @@ typedef struct process_entry_s { unsigned long num_proc; unsigned long num_lwp; unsigned long num_fd; + unsigned long num_maps; unsigned long vmem_size; unsigned long vmem_rss; unsigned long vmem_data; @@ -185,6 +187,8 @@ typedef struct process_entry_s { derive_t io_wchar; derive_t io_syscr; derive_t io_syscw; + derive_t io_diskr; + derive_t io_diskw; _Bool has_io; derive_t cswitch_vol; @@ -192,6 +196,8 @@ typedef struct process_entry_s { _Bool has_cswitch; _Bool has_fd; + + _Bool has_maps; } process_entry_t; typedef struct procstat_entry_s { @@ -209,6 +215,8 @@ typedef struct procstat_entry_s { derive_t io_wchar; derive_t io_syscr; derive_t io_syscw; + derive_t io_diskr; + derive_t io_diskw; derive_t cswitch_vol; derive_t cswitch_invol; @@ -225,6 +233,7 @@ typedef struct procstat { unsigned long num_proc; unsigned long num_lwp; unsigned long num_fd; + unsigned long num_maps; unsigned long vmem_size; unsigned long vmem_rss; unsigned long vmem_data; @@ -242,11 +251,14 @@ typedef struct procstat { derive_t io_wchar; derive_t io_syscr; derive_t io_syscw; + derive_t io_diskr; + derive_t io_diskw; derive_t cswitch_vol; derive_t cswitch_invol; _Bool report_fd_num; + _Bool report_maps_num; _Bool report_ctx_switch; struct procstat *next; @@ -258,6 +270,7 @@ static procstat_t *list_head_g = NULL; static _Bool want_init = 1; static _Bool report_ctx_switch = 0; static _Bool report_fd_num = 0; +static _Bool report_maps_num = 0; #if HAVE_THREAD_INFO static mach_port_t port_host_self; @@ -310,10 +323,13 @@ static procstat_t *ps_list_register(const char *name, const char *regexp) { new->io_wchar = -1; new->io_syscr = -1; new->io_syscw = -1; + new->io_diskr = -1; + new->io_diskw = -1; new->cswitch_vol = -1; new->cswitch_invol = -1; new->report_fd_num = report_fd_num; + new->report_maps_num = report_maps_num; new->report_ctx_switch = report_ctx_switch; #if HAVE_REGEX_H @@ -464,6 +480,7 @@ static void ps_list_add(const char *name, const char *cmdline, ps->num_proc += entry->num_proc; ps->num_lwp += entry->num_lwp; ps->num_fd += entry->num_fd; + ps->num_maps += entry->num_maps; ps->vmem_size += entry->vmem_size; ps->vmem_rss += entry->vmem_rss; ps->vmem_data += entry->vmem_data; @@ -480,6 +497,11 @@ static void ps_list_add(const char *name, const char *cmdline, ps_update_counter(&ps->io_syscw, &pse->io_syscw, entry->io_syscw); } + if ((entry->io_diskr != -1) && (entry->io_diskw != -1)) { + ps_update_counter(&ps->io_diskr, &pse->io_diskr, entry->io_diskr); + ps_update_counter(&ps->io_diskw, &pse->io_diskw, entry->io_diskw); + } + if ((entry->cswitch_vol != -1) && (entry->cswitch_vol != -1)) { ps_update_counter(&ps->cswitch_vol, &pse->cswitch_vol, entry->cswitch_vol); @@ -508,6 +530,7 @@ static void ps_list_reset(void) { ps->num_proc = 0; ps->num_lwp = 0; ps->num_fd = 0; + ps->num_maps = 0; ps->vmem_size = 0; ps->vmem_rss = 0; ps->vmem_data = 0; @@ -548,6 +571,8 @@ static void ps_tune_instance(oconfig_item_t *ci, procstat_t *ps) { cf_util_get_boolean(c, &ps->report_ctx_switch); else if (strcasecmp(c->key, "CollectFileDescriptor") == 0) cf_util_get_boolean(c, &ps->report_fd_num); + else if (strcasecmp(c->key, "CollectMemoryMaps") == 0) + cf_util_get_boolean(c, &ps->report_maps_num); else { ERROR("processes plugin: Option `%s' not allowed here.", c->key); } @@ -606,6 +631,8 @@ static int ps_config(oconfig_item_t *ci) { cf_util_get_boolean(c, &report_ctx_switch); } else if (strcasecmp(c->key, "CollectFileDescriptor") == 0) { cf_util_get_boolean(c, &report_fd_num); + } else if (strcasecmp(c->key, "CollectMemoryMaps") == 0) { + cf_util_get_boolean(c, &report_maps_num); } else { ERROR("processes plugin: The `%s' configuration option is not " "understood and will be ignored.", @@ -725,7 +752,7 @@ static void ps_submit_proc_list(procstat_t *ps) { plugin_dispatch_values(&vl); if ((ps->io_rchar != -1) && (ps->io_wchar != -1)) { - sstrncpy(vl.type, "ps_disk_octets", sizeof(vl.type)); + sstrncpy(vl.type, "io_octets", sizeof(vl.type)); vl.values[0].derive = ps->io_rchar; vl.values[1].derive = ps->io_wchar; vl.values_len = 2; @@ -733,13 +760,21 @@ static void ps_submit_proc_list(procstat_t *ps) { } if ((ps->io_syscr != -1) && (ps->io_syscw != -1)) { - sstrncpy(vl.type, "ps_disk_ops", sizeof(vl.type)); + sstrncpy(vl.type, "io_ops", sizeof(vl.type)); vl.values[0].derive = ps->io_syscr; vl.values[1].derive = ps->io_syscw; vl.values_len = 2; plugin_dispatch_values(&vl); } + if ((ps->io_diskr != -1) && (ps->io_diskw != -1)) { + sstrncpy(vl.type, "disk_octets", sizeof(vl.type)); + vl.values[0].derive = ps->io_diskr; + vl.values[1].derive = ps->io_diskw; + vl.values_len = 2; + plugin_dispatch_values(&vl); + } + if (ps->num_fd > 0) { sstrncpy(vl.type, "file_handles", sizeof(vl.type)); vl.values[0].gauge = ps->num_fd; @@ -747,6 +782,14 @@ static void ps_submit_proc_list(procstat_t *ps) { plugin_dispatch_values(&vl); } + if (ps->num_maps > 0) { + sstrncpy(vl.type, "file_handles", sizeof(vl.type)); + sstrncpy(vl.type_instance, "mapped", sizeof(vl.type_instance)); + vl.values[0].gauge = ps->num_maps; + vl.values_len = 1; + plugin_dispatch_values(&vl); + } + if ((ps->cswitch_vol != -1) && (ps->cswitch_invol != -1)) { sstrncpy(vl.type, "contextswitch", sizeof(vl.type)); sstrncpy(vl.type_instance, "voluntary", sizeof(vl.type_instance)); @@ -761,19 +804,23 @@ static void ps_submit_proc_list(procstat_t *ps) { plugin_dispatch_values(&vl); } - DEBUG("name = %s; num_proc = %lu; num_lwp = %lu; num_fd = %lu; " - "vmem_size = %lu; vmem_rss = %lu; vmem_data = %lu; " - "vmem_code = %lu; " - "vmem_minflt_counter = %" PRIi64 "; vmem_majflt_counter = %" PRIi64 "; " - "cpu_user_counter = %" PRIi64 "; cpu_system_counter = %" PRIi64 "; " - "io_rchar = %" PRIi64 "; io_wchar = %" PRIi64 "; " - "io_syscr = %" PRIi64 "; io_syscw = %" PRIi64 "; " - "cswitch_vol = %" PRIi64 "; cswitch_invol = %" PRIi64 ";", - ps->name, ps->num_proc, ps->num_lwp, ps->num_fd, ps->vmem_size, - ps->vmem_rss, ps->vmem_data, ps->vmem_code, ps->vmem_minflt_counter, - ps->vmem_majflt_counter, ps->cpu_user_counter, ps->cpu_system_counter, - ps->io_rchar, ps->io_wchar, ps->io_syscr, ps->io_syscw, ps->cswitch_vol, - ps->cswitch_invol); + DEBUG( + "name = %s; num_proc = %lu; num_lwp = %lu; num_fd = %lu; num_maps = %lu; " + "vmem_size = %lu; vmem_rss = %lu; vmem_data = %lu; " + "vmem_code = %lu; " + "vmem_minflt_counter = %" PRIi64 "; vmem_majflt_counter = %" PRIi64 "; " + "cpu_user_counter = %" PRIi64 "; cpu_system_counter = %" PRIi64 "; " + "io_rchar = %" PRIi64 "; io_wchar = %" PRIi64 "; " + "io_syscr = %" PRIi64 "; io_syscw = %" PRIi64 "; " + "io_diskr = %" PRIi64 "; io_diskw = %" PRIi64 "; " + "cswitch_vol = %" PRIi64 "; cswitch_invol = %" PRIi64 ";", + ps->name, ps->num_proc, ps->num_lwp, ps->num_fd, ps->num_maps, + ps->vmem_size, ps->vmem_rss, ps->vmem_data, ps->vmem_code, + ps->vmem_minflt_counter, ps->vmem_majflt_counter, ps->cpu_user_counter, + ps->cpu_system_counter, ps->io_rchar, ps->io_wchar, ps->io_syscr, + ps->io_syscw, ps->io_diskr, ps->io_diskw, ps->cswitch_vol, + ps->cswitch_invol); + } /* void ps_submit_proc_list */ #if KERNEL_LINUX || KERNEL_SOLARIS @@ -954,6 +1001,10 @@ static int ps_read_io(process_entry_t *ps) { val = &(ps->io_syscr); else if (strncasecmp(buffer, "syscw:", 6) == 0) val = &(ps->io_syscw); + else if (strncasecmp(buffer, "read_bytes:", 11) == 0) + val = &(ps->io_diskr); + else if (strncasecmp(buffer, "write_bytes:", 12) == 0) + val = &(ps->io_diskw); else continue; @@ -978,6 +1029,31 @@ static int ps_read_io(process_entry_t *ps) { return 0; } /* int ps_read_io (...) */ +static int ps_count_maps(pid_t pid) { + FILE *fh; + char buffer[1024]; + char filename[64]; + int count = 0; + + snprintf(filename, sizeof(filename), "/proc/%d/maps", pid); + if ((fh = fopen(filename, "r")) == NULL) { + DEBUG("ps_count_maps: Failed to open file `%s'", filename); + return -1; + } + + while (fgets(buffer, sizeof(buffer), fh) != NULL) { + if (strchr(buffer, '\n')) { + count++; + } + } /* while (fgets) */ + + if (fclose(fh)) { + char errbuf[1024]; + WARNING("processes: fclose: %s", sstrerror(errno, errbuf, sizeof(errbuf))); + } + return count; +} /* int ps_count_maps (...) */ + static int ps_count_fd(int pid) { char dirname[64]; DIR *dh; @@ -1014,6 +1090,14 @@ static void ps_fill_details(const procstat_t *ps, process_entry_t *entry) { } } + if (ps->report_maps_num) { + int num_maps; + if (entry->has_maps == 0 && (num_maps = ps_count_maps(entry->id)) > 0) { + entry->num_maps = num_maps; + } + entry->has_maps = 1; + } + if (ps->report_fd_num) { int num_fd; if (entry->has_fd == 0 && (num_fd = ps_count_fd(entry->id)) > 0) { @@ -1149,6 +1233,8 @@ static int ps_read_process(long pid, process_entry_t *ps, char *state) { ps->io_wchar = -1; ps->io_syscr = -1; ps->io_syscw = -1; + ps->io_diskr = -1; + ps->io_diskw = -1; ps->cswitch_vol = -1; ps->cswitch_invol = -1; @@ -1393,6 +1479,9 @@ static int ps_read_process(long pid, process_entry_t *ps, char *state) { */ ps->num_fd = 0; + /* Number of memory mappings */ + ps->num_maps = 0; + /* * Calculating input/ouput chars * Formula used is total chars / total blocks => chars/block @@ -1407,6 +1496,8 @@ static int ps_read_process(long pid, process_entry_t *ps, char *state) { ps->io_wchar = myUsage->pr_oublk * chars_per_block; ps->io_syscr = myUsage->pr_sysc; ps->io_syscw = myUsage->pr_sysc; + ps->io_diskr = -1; + ps->io_diskw = -1; /* * TODO: context switch counters for Solaris @@ -1620,10 +1711,15 @@ static int ps_read(void) { pse.io_wchar = -1; pse.io_syscr = -1; pse.io_syscw = -1; + pse.io_diskr = -1; + pse.io_diskw = -1; /* File descriptor count not implemented */ pse.num_fd = 0; + /* Number of memory mappings */ + pse.num_maps = 0; + pse.vmem_minflt_counter = task_events_info.cow_faults; pse.vmem_majflt_counter = task_events_info.faults; @@ -1923,10 +2019,15 @@ static int ps_read(void) { pse.io_wchar = -1; pse.io_syscr = -1; pse.io_syscw = -1; + pse.io_diskr = -1; + pse.io_diskw = -1; /* file descriptor count not implemented */ pse.num_fd = 0; + /* Number of memory mappings */ + pse.num_maps = 0; + /* context switch counters not implemented */ pse.cswitch_vol = -1; pse.cswitch_invol = -1; @@ -2062,10 +2163,15 @@ static int ps_read(void) { pse.io_wchar = -1; pse.io_syscr = -1; pse.io_syscw = -1; + pse.io_diskr = -1; + pse.io_diskw = -1; /* file descriptor count not implemented */ pse.num_fd = 0; + /* Number of memory mappings */ + pse.num_maps = 0; + /* context switch counters not implemented */ pse.cswitch_vol = -1; pse.cswitch_invol = -1; @@ -2225,8 +2331,11 @@ static int ps_read(void) { pse.io_wchar = -1; pse.io_syscr = -1; pse.io_syscw = -1; + pse.io_diskr = -1; + pse.io_diskw = -1; pse.num_fd = 0; + pse.num_maps = 0; pse.cswitch_vol = -1; pse.cswitch_invol = -1; diff --git a/src/python.c b/src/python.c index 32c5847d..e60ba459 100644 --- a/src/python.c +++ b/src/python.c @@ -438,7 +438,7 @@ static int cpy_write_callback(const data_set_t *ds, } dict = PyDict_New(); /* New reference. */ if (value_list->meta) { - char **table; + char **table = NULL; meta_data_t *meta = value_list->meta; int num = meta_data_toc(meta, &table); @@ -461,19 +461,21 @@ static int cpy_write_callback(const data_set_t *ds, } else if (type == MD_TYPE_SIGNED_INT) { if (meta_data_get_signed_int(meta, table[i], &si)) continue; - temp = PyObject_CallFunctionObjArgs((void *)&SignedType, - PyLong_FromLongLong(si), + PyObject *sival = PyLong_FromLongLong(si); /* New reference */ + temp = PyObject_CallFunctionObjArgs((void *)&SignedType, sival, (void *)0); /* New reference. */ PyDict_SetItemString(dict, table[i], temp); Py_XDECREF(temp); + Py_XDECREF(sival); } else if (type == MD_TYPE_UNSIGNED_INT) { if (meta_data_get_unsigned_int(meta, table[i], &ui)) continue; - temp = PyObject_CallFunctionObjArgs((void *)&UnsignedType, - PyLong_FromUnsignedLongLong(ui), + PyObject *uval = PyLong_FromUnsignedLongLong(ui); /* New reference */ + temp = PyObject_CallFunctionObjArgs((void *)&UnsignedType, uval, (void *)0); /* New reference. */ PyDict_SetItemString(dict, table[i], temp); Py_XDECREF(temp); + Py_XDECREF(uval); } else if (type == MD_TYPE_DOUBLE) { if (meta_data_get_double(meta, table[i], &d)) continue; @@ -525,6 +527,39 @@ static int cpy_notification_callback(const notification_t *notification, Notification *n; CPY_LOCK_THREADS + PyObject *dict = PyDict_New(); /* New reference. */ + for (notification_meta_t *meta = notification->meta; meta != NULL; + meta = meta->next) { + PyObject *temp = NULL; + if (meta->type == NM_TYPE_STRING) { + temp = cpy_string_to_unicode_or_bytes( + meta->nm_value.nm_string); /* New reference. */ + PyDict_SetItemString(dict, meta->name, temp); + Py_XDECREF(temp); + } else if (meta->type == NM_TYPE_SIGNED_INT) { + PyObject *sival = PyLong_FromLongLong(meta->nm_value.nm_signed_int); + temp = PyObject_CallFunctionObjArgs((void *)&SignedType, sival, + (void *)0); /* New reference. */ + PyDict_SetItemString(dict, meta->name, temp); + Py_XDECREF(temp); + Py_XDECREF(sival); + } else if (meta->type == NM_TYPE_UNSIGNED_INT) { + PyObject *uval = + PyLong_FromUnsignedLongLong(meta->nm_value.nm_unsigned_int); + temp = PyObject_CallFunctionObjArgs((void *)&UnsignedType, uval, + (void *)0); /* New reference. */ + PyDict_SetItemString(dict, meta->name, temp); + Py_XDECREF(temp); + Py_XDECREF(uval); + } else if (meta->type == NM_TYPE_DOUBLE) { + temp = PyFloat_FromDouble(meta->nm_value.nm_double); /* New reference. */ + PyDict_SetItemString(dict, meta->name, temp); + Py_XDECREF(temp); + } else if (meta->type == NM_TYPE_BOOLEAN) { + PyDict_SetItemString(dict, meta->name, + meta->nm_value.nm_boolean ? Py_True : Py_False); + } + } notify = Notification_New(); /* New reference. */ n = (Notification *)notify; sstrncpy(n->data.host, notification->host, sizeof(n->data.host)); @@ -537,6 +572,8 @@ static int cpy_notification_callback(const notification_t *notification, n->data.time = CDTIME_T_TO_DOUBLE(notification->time); sstrncpy(n->message, notification->message, sizeof(n->message)); n->severity = notification->severity; + Py_CLEAR(n->meta); + n->meta = dict; /* Steals a reference. */ ret = PyObject_CallFunctionObjArgs(c->callback, n, c->data, (void *)0); /* New reference. */ Py_XDECREF(notify); @@ -666,8 +703,9 @@ static PyObject *cpy_get_dataset(PyObject *self, PyObject *args) { for (size_t i = 0; i < ds->ds_num; ++i) { tuple = PyTuple_New(4); PyTuple_SET_ITEM(tuple, 0, cpy_string_to_unicode_or_bytes(ds->ds[i].name)); - PyTuple_SET_ITEM(tuple, 1, cpy_string_to_unicode_or_bytes( - DS_TYPE_TO_STRING(ds->ds[i].type))); + PyTuple_SET_ITEM( + tuple, 1, + cpy_string_to_unicode_or_bytes(DS_TYPE_TO_STRING(ds->ds[i].type))); PyTuple_SET_ITEM(tuple, 2, float_or_none(ds->ds[i].min)); PyTuple_SET_ITEM(tuple, 3, float_or_none(ds->ds[i].max)); PyList_SET_ITEM(list, i, tuple); @@ -737,7 +775,8 @@ static PyObject *cpy_register_generic_userdata(void *reg, void *handler, register_function(buf, handler, &(user_data_t){ - .data = c, .free_func = cpy_destroy_user_data, + .data = c, + .free_func = cpy_destroy_user_data, }); ++cpy_num_callbacks; @@ -780,7 +819,8 @@ static PyObject *cpy_register_read(PyObject *self, PyObject *args, /* group = */ "python", buf, cpy_read_callback, DOUBLE_TO_CDTIME_T(interval), &(user_data_t){ - .data = c, .free_func = cpy_destroy_user_data, + .data = c, + .free_func = cpy_destroy_user_data, }); ++cpy_num_callbacks; return cpy_string_to_unicode_or_bytes(buf); @@ -1160,8 +1200,9 @@ static PyObject *cpy_oconfig_to_pyconfig(oconfig_item_t *ci, PyObject *parent) { values = PyTuple_New(ci->values_num); /* New reference. */ for (int i = 0; i < ci->values_num; ++i) { if (ci->values[i].type == OCONFIG_TYPE_STRING) { - PyTuple_SET_ITEM(values, i, cpy_string_to_unicode_or_bytes( - 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)); diff --git a/src/pyvalues.c b/src/pyvalues.c index 4f7e8349..15c18484 100644 --- a/src/pyvalues.c +++ b/src/pyvalues.c @@ -33,6 +33,14 @@ #include "cpython.h" +typedef struct { + int (*add_string)(void *, const char *, const char *); + int (*add_signed_int)(void *, const char *, int64_t); + int (*add_unsigned_int)(void *, const char *, uint64_t); + int (*add_double)(void *, const char *, double); + int (*add_boolean)(void *, const char *, _Bool); +} cpy_build_meta_handler_t; + #define FreeAll() \ do { \ PyMem_Free(type); \ @@ -457,26 +465,26 @@ static int Values_init(PyObject *s, PyObject *args, PyObject *kwds) { return 0; } -static meta_data_t *cpy_build_meta(PyObject *meta) { +static int cpy_build_meta_generic(PyObject *meta, + cpy_build_meta_handler_t *meta_func, + void *m) { int s; - meta_data_t *m = NULL; PyObject *l; if ((meta == NULL) || (meta == Py_None)) - return NULL; + return -1; l = PyDict_Items(meta); /* New reference. */ if (!l) { cpy_log_exception("building meta data"); - return NULL; + return -1; } s = PyList_Size(l); if (s <= 0) { Py_XDECREF(l); - return NULL; + return -1; } - m = meta_data_create(); for (int i = 0; i < s; ++i) { const char *string, *keystring; PyObject *key, *value, *item, *tmp; @@ -493,45 +501,45 @@ static meta_data_t *cpy_build_meta(PyObject *meta) { value = PyTuple_GET_ITEM(item, 1); Py_INCREF(value); if (value == Py_True) { - meta_data_add_boolean(m, keystring, 1); + meta_func->add_boolean(m, keystring, 1); } else if (value == Py_False) { - meta_data_add_boolean(m, keystring, 0); + meta_func->add_boolean(m, keystring, 0); } else if (PyFloat_Check(value)) { - meta_data_add_double(m, keystring, PyFloat_AsDouble(value)); + meta_func->add_double(m, keystring, PyFloat_AsDouble(value)); } else if (PyObject_TypeCheck(value, &SignedType)) { long long int lli; lli = PyLong_AsLongLong(value); if (!PyErr_Occurred() && (lli == (int64_t)lli)) - meta_data_add_signed_int(m, keystring, lli); + meta_func->add_signed_int(m, keystring, lli); } else if (PyObject_TypeCheck(value, &UnsignedType)) { long long unsigned llu; llu = PyLong_AsUnsignedLongLong(value); if (!PyErr_Occurred() && (llu == (uint64_t)llu)) - meta_data_add_unsigned_int(m, keystring, llu); + meta_func->add_unsigned_int(m, keystring, llu); } else if (PyNumber_Check(value)) { long long int lli; long long unsigned llu; tmp = PyNumber_Long(value); lli = PyLong_AsLongLong(tmp); if (!PyErr_Occurred() && (lli == (int64_t)lli)) { - meta_data_add_signed_int(m, keystring, lli); + meta_func->add_signed_int(m, keystring, lli); } else { PyErr_Clear(); llu = PyLong_AsUnsignedLongLong(tmp); if (!PyErr_Occurred() && (llu == (uint64_t)llu)) - meta_data_add_unsigned_int(m, keystring, llu); + meta_func->add_unsigned_int(m, keystring, llu); } Py_XDECREF(tmp); } else { string = cpy_unicode_or_bytes_to_string(&value); if (string) { - meta_data_add_string(m, keystring, string); + meta_func->add_string(m, keystring, string); } else { PyErr_Clear(); tmp = PyObject_Str(value); string = cpy_unicode_or_bytes_to_string(&tmp); if (string) - meta_data_add_string(m, keystring, string); + meta_func->add_string(m, keystring, string); Py_XDECREF(tmp); } } @@ -541,9 +549,44 @@ static meta_data_t *cpy_build_meta(PyObject *meta) { Py_DECREF(key); } Py_XDECREF(l); + return 0; +} + +#define CPY_BUILD_META_FUNC(meta_type, func, val_type) \ + static int cpy_##func(void *meta, const char *key, val_type val) { \ + return func((meta_type *)meta, key, val); \ + } + +#define CPY_BUILD_META_HANDLER(func_prefix, meta_type) \ + CPY_BUILD_META_FUNC(meta_type, func_prefix##_add_string, const char *) \ + CPY_BUILD_META_FUNC(meta_type, func_prefix##_add_signed_int, int64_t) \ + CPY_BUILD_META_FUNC(meta_type, func_prefix##_add_unsigned_int, uint64_t) \ + CPY_BUILD_META_FUNC(meta_type, func_prefix##_add_double, double) \ + CPY_BUILD_META_FUNC(meta_type, func_prefix##_add_boolean, _Bool) \ + \ + static cpy_build_meta_handler_t cpy_##func_prefix = { \ + .add_string = cpy_##func_prefix##_add_string, \ + .add_signed_int = cpy_##func_prefix##_add_signed_int, \ + .add_unsigned_int = cpy_##func_prefix##_add_unsigned_int, \ + .add_double = cpy_##func_prefix##_add_double, \ + .add_boolean = cpy_##func_prefix##_add_boolean} + +CPY_BUILD_META_HANDLER(meta_data, meta_data_t); +CPY_BUILD_META_HANDLER(plugin_notification_meta, notification_t); + +static meta_data_t *cpy_build_meta(PyObject *meta) { + meta_data_t *m = meta_data_create(); + if (cpy_build_meta_generic(meta, &cpy_meta_data, (void *)m) < 0) { + meta_data_destroy(m); + return NULL; + } return m; } +static void cpy_build_notification_meta(notification_t *n, PyObject *meta) { + cpy_build_meta_generic(meta, &cpy_plugin_notification_meta, (void *)n); +} + static PyObject *Values_dispatch(Values *self, PyObject *args, PyObject *kwds) { int ret; const data_set_t *ds; @@ -910,6 +953,17 @@ PyTypeObject ValuesType = { Values_new /* tp_new */ }; +static char notification_meta_doc[] = + "These are the meta data for the Notification object.\n" + "It has to be a dictionary of numbers, strings or bools. All keys must be\n" + "strings. int and long objects will be dispatched as signed integers " + "unless\n" + "they are between 2**63 and 2**64-1, which will result in an unsigned " + "integer.\n" + "One of these storage classes can be forced by using the classes\n" + "collectd.Signed and collectd.Unsigned. A meta object received by a\n" + "notification callback will always contain Signed or Unsigned objects."; + static char severity_doc[] = "The severity of this notification. Assign or compare to\n" "NOTIF_FAILURE, NOTIF_WARNING or NOTIF_OKAY."; @@ -931,16 +985,17 @@ static int Notification_init(PyObject *s, PyObject *args, PyObject *kwds) { int severity = 0; double time = 0; char *message = NULL; + PyObject *meta = NULL; char *type = NULL, *plugin_instance = NULL, *type_instance = NULL, *plugin = NULL, *host = NULL; - static char *kwlist[] = {"type", "message", "plugin_instance", - "type_instance", "plugin", "host", - "time", "severity", NULL}; + static char *kwlist[] = { + "type", "message", "plugin_instance", "type_instance", "plugin", + "host", "time", "severity", "meta", NULL}; - if (!PyArg_ParseTupleAndKeywords(args, kwds, "|etetetetetetdi", kwlist, NULL, - &type, NULL, &message, NULL, - &plugin_instance, NULL, &type_instance, NULL, - &plugin, NULL, &host, &time, &severity)) + if (!PyArg_ParseTupleAndKeywords( + args, kwds, "|etetetetetetdiO", kwlist, NULL, &type, NULL, &message, + NULL, &plugin_instance, NULL, &type_instance, NULL, &plugin, NULL, + &host, &time, &severity, &meta)) return -1; if (type && plugin_get_ds(type) == NULL) { @@ -963,6 +1018,18 @@ static int Notification_init(PyObject *s, PyObject *args, PyObject *kwds) { FreeAll(); PyMem_Free(message); + + if (meta == NULL) { + meta = PyDict_New(); + PyErr_Clear(); + } else { + Py_INCREF(meta); + } + + PyObject *tmp = self->meta; + self->meta = meta; + Py_XDECREF(tmp); + return 0; } @@ -972,18 +1039,19 @@ static PyObject *Notification_dispatch(Notification *self, PyObject *args, const data_set_t *ds; notification_t notification; double t = self->data.time; + PyObject *meta = self->meta; int severity = self->severity; char *host = NULL, *plugin = NULL, *plugin_instance = NULL, *type = NULL, *type_instance = NULL; char *message = NULL; - static char *kwlist[] = {"type", "message", "plugin_instance", - "type_instance", "plugin", "host", - "time", "severity", NULL}; - if (!PyArg_ParseTupleAndKeywords(args, kwds, "|etetetetetetdi", kwlist, NULL, + static char *kwlist[] = { + "type", "message", "plugin_instance", "type_instance", "plugin", + "host", "time", "severity", "meta", NULL}; + if (!PyArg_ParseTupleAndKeywords(args, kwds, "|etetetetetetdiO", kwlist, NULL, &type, NULL, &message, NULL, &plugin_instance, NULL, &type_instance, NULL, - &plugin, NULL, &host, &t, &severity)) + &plugin, NULL, &host, &t, &severity, &meta)) return NULL; notification.time = DOUBLE_TO_CDTIME_T(t); @@ -1015,6 +1083,11 @@ static PyObject *Notification_dispatch(Notification *self, PyObject *args, PyErr_Format(PyExc_TypeError, "Dataset %s not found", notification.type); return NULL; } + if (meta != NULL && meta != Py_None && !PyDict_Check(meta)) { + PyErr_Format(PyExc_TypeError, "meta must be a dict"); + return NULL; + } + cpy_build_notification_meta(¬ification, meta); if (notification.time == 0) notification.time = cdtime(); @@ -1024,6 +1097,8 @@ static PyObject *Notification_dispatch(Notification *self, PyObject *args, sstrncpy(notification.plugin, "python", sizeof(notification.plugin)); Py_BEGIN_ALLOW_THREADS; ret = plugin_dispatch_notification(¬ification); + if (notification.meta) + plugin_notification_meta_free(notification.meta); Py_END_ALLOW_THREADS; if (ret != 0) { PyErr_SetString(PyExc_RuntimeError, @@ -1041,6 +1116,7 @@ static PyObject *Notification_new(PyTypeObject *type, PyObject *args, if (self == NULL) return NULL; + self->meta = PyDict_New(); self->message[0] = 0; self->severity = 0; return (PyObject *)self; @@ -1068,17 +1144,21 @@ static int Notification_setstring(PyObject *self, PyObject *value, void *data) { static PyObject *Notification_repr(PyObject *s) { PyObject *ret, *tmp; - static PyObject *l_severity = NULL, *l_message = NULL, *l_closing = NULL; + static PyObject *l_severity = NULL, *l_message = NULL, *l_meta = NULL, + *l_closing = NULL; Notification *self = (Notification *)s; 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_meta == NULL) + l_meta = cpy_string_to_unicode_or_bytes(",meta="); if (l_closing == NULL) l_closing = cpy_string_to_unicode_or_bytes(")"); - if (l_severity == NULL || l_message == NULL || l_closing == NULL) + if (l_severity == NULL || l_message == NULL || l_meta == NULL || + l_closing == NULL) return NULL; ret = cpy_common_repr(s); @@ -1094,10 +1174,33 @@ static PyObject *Notification_repr(PyObject *s) { CPY_SUBSTITUTE(PyObject_Repr, tmp, tmp); CPY_STRCAT_AND_DEL(&ret, tmp); } + if (self->meta && + (!PyDict_Check(self->meta) || PyDict_Size(self->meta) > 0)) { + CPY_STRCAT(&ret, l_meta); + tmp = PyObject_Repr(self->meta); + CPY_STRCAT_AND_DEL(&ret, tmp); + } CPY_STRCAT(&ret, l_closing); return ret; } +static int Notification_traverse(PyObject *self, visitproc visit, void *arg) { + Notification *n = (Notification *)self; + Py_VISIT(n->meta); + return 0; +} + +static int Notification_clear(PyObject *self) { + Notification *n = (Notification *)self; + Py_CLEAR(n->meta); + return 0; +} + +static void Notification_dealloc(PyObject *self) { + Notification_clear(self); + self->ob_type->tp_free(self); +} + static PyMethodDef Notification_methods[] = { {"dispatch", (PyCFunction)Notification_dispatch, METH_VARARGS | METH_KEYWORDS, dispatch_doc}, @@ -1105,6 +1208,8 @@ static PyMethodDef Notification_methods[] = { static PyMemberDef Notification_members[] = { {"severity", T_INT, offsetof(Notification, severity), 0, severity_doc}, + {"meta", T_OBJECT_EX, offsetof(Notification, meta), 0, + notification_meta_doc}, {NULL}}; static PyGetSetDef Notification_getseters[] = { @@ -1113,43 +1218,43 @@ static PyGetSetDef Notification_getseters[] = { {NULL}}; PyTypeObject NotificationType = { - CPY_INIT_TYPE "collectd.Notification", /* tp_name */ - sizeof(Notification), /* tp_basicsize */ - 0, /* Will be filled in later */ - 0, /* tp_dealloc */ - 0, /* tp_print */ - 0, /* tp_getattr */ - 0, /* tp_setattr */ - 0, /* tp_compare */ - Notification_repr, /* tp_repr */ - 0, /* tp_as_number */ - 0, /* tp_as_sequence */ - 0, /* tp_as_mapping */ - 0, /* tp_hash */ - 0, /* tp_call */ - 0, /* tp_str */ - 0, /* tp_getattro */ - 0, /* tp_setattro */ - 0, /* tp_as_buffer */ - Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /*tp_flags*/ - Notification_doc, /* tp_doc */ - 0, /* tp_traverse */ - 0, /* tp_clear */ - 0, /* tp_richcompare */ - 0, /* tp_weaklistoffset */ - 0, /* tp_iter */ - 0, /* tp_iternext */ - Notification_methods, /* tp_methods */ - Notification_members, /* tp_members */ - Notification_getseters, /* tp_getset */ - 0, /* tp_base */ - 0, /* tp_dict */ - 0, /* tp_descr_get */ - 0, /* tp_descr_set */ - 0, /* tp_dictoffset */ - Notification_init, /* tp_init */ - 0, /* tp_alloc */ - Notification_new /* tp_new */ + CPY_INIT_TYPE "collectd.Notification", /* tp_name */ + sizeof(Notification), /* tp_basicsize */ + 0, /* Will be filled in later */ + Notification_dealloc, /* tp_dealloc */ + 0, /* tp_print */ + 0, /* tp_getattr */ + 0, /* tp_setattr */ + 0, /* tp_compare */ + Notification_repr, /* tp_repr */ + 0, /* tp_as_number */ + 0, /* tp_as_sequence */ + 0, /* tp_as_mapping */ + 0, /* tp_hash */ + 0, /* tp_call */ + 0, /* tp_str */ + 0, /* tp_getattro */ + 0, /* tp_setattro */ + 0, /* tp_as_buffer */ + Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HAVE_GC, /*tp_flags*/ + Notification_doc, /* tp_doc */ + Notification_traverse, /* tp_traverse */ + Notification_clear, /* tp_clear */ + 0, /* tp_richcompare */ + 0, /* tp_weaklistoffset */ + 0, /* tp_iter */ + 0, /* tp_iternext */ + Notification_methods, /* tp_methods */ + Notification_members, /* tp_members */ + Notification_getseters, /* tp_getset */ + 0, /* tp_base */ + 0, /* tp_dict */ + 0, /* tp_descr_get */ + 0, /* tp_descr_set */ + 0, /* tp_dictoffset */ + Notification_init, /* tp_init */ + 0, /* tp_alloc */ + Notification_new /* tp_new */ }; static char Signed_doc[] = diff --git a/src/routeros.c b/src/routeros.c index fa08b3b0..9ea82976 100644 --- a/src/routeros.c +++ b/src/routeros.c @@ -376,18 +376,17 @@ static int cr_config_router(oconfig_item_t *ci) /* {{{ */ } } - snprintf(read_name, sizeof(read_name), "routeros/%s", router_data->node); - if (status == 0) - status = plugin_register_complex_read( - /* group = */ NULL, read_name, cr_read, /* interval = */ 0, - &(user_data_t){ - .data = router_data, .free_func = (void *)cr_free_data, - }); - - if (status != 0) + if (status != 0) { cr_free_data(router_data); + return status; + } - return status; + snprintf(read_name, sizeof(read_name), "routeros/%s", router_data->node); + return plugin_register_complex_read( + /* group = */ NULL, read_name, cr_read, /* interval = */ 0, + &(user_data_t){ + .data = router_data, .free_func = (void *)cr_free_data, + }); } /* }}} int cr_config_router */ static int cr_config(oconfig_item_t *ci) { diff --git a/src/rrdtool.c b/src/rrdtool.c index 41289058..5f42561a 100644 --- a/src/rrdtool.c +++ b/src/rrdtool.c @@ -36,15 +36,14 @@ /* * Private types */ -struct rrd_cache_s { +typedef struct rrd_cache_s { int values_num; char **values; cdtime_t first_value; cdtime_t last_value; int64_t random_variation; enum { FLAG_NONE = 0x00, FLAG_QUEUED = 0x01, FLAG_FLUSHQ = 0x02 } flags; -}; -typedef struct rrd_cache_s rrd_cache_t; +} rrd_cache_t; enum rrd_queue_dir_e { QUEUE_INSERT_FRONT, QUEUE_INSERT_BACK }; typedef enum rrd_queue_dir_e rrd_queue_dir_t; @@ -87,7 +86,7 @@ static rrdcreate_config_t rrdcreate_config = { * ALWAYS lock `cache_lock' first! */ static cdtime_t cache_timeout = 0; static cdtime_t cache_flush_timeout = 0; -static cdtime_t random_timeout = TIME_T_TO_CDTIME_T_STATIC(1); +static cdtime_t random_timeout = 0; static cdtime_t cache_flush_last; static c_avl_tree_t *cache = NULL; static pthread_mutex_t cache_lock = PTHREAD_MUTEX_INITIALIZER; @@ -110,13 +109,10 @@ static int do_shutdown = 0; #if HAVE_THREADSAFE_LIBRRD static int srrd_update(char *filename, char *template, int argc, const char **argv) { - int status; - optind = 0; /* bug in librrd? */ rrd_clear_error(); - status = rrd_update_r(filename, template, argc, (void *)argv); - + int status = rrd_update_r(filename, template, argc, (void *)argv); if (status != 0) { WARNING("rrdtool plugin: rrd_update_r (%s) failed: %s", filename, rrd_get_error()); @@ -505,7 +501,6 @@ static void rrd_cache_flush(cdtime_t timeout) { CDTIME_T_TO_DOUBLE(timeout)); now = cdtime(); - timeout = TIME_T_TO_CDTIME_T(timeout); /* Build a list of entries to be flushed */ iter = c_avl_get_iterator(cache); @@ -606,23 +601,10 @@ static int rrd_cache_flush_identifier(cdtime_t timeout, } /* int rrd_cache_flush_identifier */ static int64_t rrd_get_random_variation(void) { - long min; - long max; - if (random_timeout == 0) return 0; - /* Assure that "cache_timeout + random_variation" is never negative. */ - if (random_timeout > cache_timeout) { - INFO("rrdtool plugin: Adjusting \"RandomTimeout\" to %.3f seconds.", - CDTIME_T_TO_DOUBLE(cache_timeout)); - random_timeout = cache_timeout; - } - - max = (long)(random_timeout / 2); - min = max - ((long)random_timeout); - - return (int64_t)cdrand_range(min, max); + return (int64_t)cdrand_range(-random_timeout, random_timeout); } /* int64_t rrd_get_random_variation */ static int rrd_cache_insert(const char *filename, const char *value, @@ -740,7 +722,7 @@ static int rrd_cache_insert(const char *filename, const char *value, if ((cache_timeout > 0) && ((cdtime() - cache_flush_last) > cache_flush_timeout)) - rrd_cache_flush(cache_flush_timeout); + rrd_cache_flush(cache_timeout + random_timeout); pthread_mutex_unlock(&cache_lock); @@ -808,10 +790,6 @@ static int rrd_compare_numeric(const void *a_ptr, const void *b_ptr) { static int rrd_write(const data_set_t *ds, const value_list_t *vl, user_data_t __attribute__((unused)) * user_data) { - struct stat statbuf; - char filename[512]; - char values[512]; - int status; if (do_shutdown) return 0; @@ -821,33 +799,34 @@ static int rrd_write(const data_set_t *ds, const value_list_t *vl, return -1; } + char filename[PATH_MAX]; if (value_list_to_filename(filename, sizeof(filename), vl) != 0) return -1; + char values[32 * ds->ds_num]; if (value_list_to_string(values, sizeof(values), ds, vl) != 0) return -1; + struct stat statbuf = {0}; if (stat(filename, &statbuf) == -1) { if (errno == ENOENT) { - status = cu_rrd_create_file(filename, ds, vl, &rrdcreate_config); - if (status != 0) + if (cu_rrd_create_file(filename, ds, vl, &rrdcreate_config) != 0) { return -1; - else if (rrdcreate_config.async) + } else if (rrdcreate_config.async) { return 0; + } } else { char errbuf[1024]; - ERROR("stat(%s) failed: %s", filename, + ERROR("rrdtool plugin: stat(%s) failed: %s", filename, sstrerror(errno, errbuf, sizeof(errbuf))); return -1; } } else if (!S_ISREG(statbuf.st_mode)) { - ERROR("stat(%s): Not a regular file!", filename); + ERROR("rrdtool plugin: stat(%s): Not a regular file!", filename); return -1; } - status = rrd_cache_insert(filename, values, vl->time); - - return status; + return rrd_cache_insert(filename, values, vl->time); } /* int rrd_write */ static int rrd_flush(cdtime_t timeout, const char *identifier, @@ -877,7 +856,7 @@ static int rrd_config(const char *key, const char *value) { } cache_timeout = DOUBLE_TO_CDTIME_T(tmp); } else if (strcasecmp("CacheFlush", key) == 0) { - int tmp = atoi(value); + double tmp = atof(value); if (tmp < 0) { fprintf(stderr, "rrdtool: `CacheFlush' must " "be greater than 0.\n"); @@ -885,7 +864,7 @@ static int rrd_config(const char *key, const char *value) { "be greater than 0.\n"); return 1; } - cache_flush_timeout = tmp; + cache_flush_timeout = DOUBLE_TO_CDTIME_T(tmp); } else if (strcasecmp("DataDir", key) == 0) { char *tmp; size_t len; @@ -1044,7 +1023,6 @@ static int rrd_shutdown(void) { static int rrd_init(void) { static int init_once = 0; - int status; if (init_once != 0) return 0; @@ -1065,13 +1043,28 @@ static int rrd_init(void) { cache_flush_last = cdtime(); if (cache_timeout == 0) { + random_timeout = 0; cache_flush_timeout = 0; - } else if (cache_flush_timeout < cache_timeout) + } else if (cache_flush_timeout < cache_timeout) { + INFO("rrdtool plugin: \"CacheFlush %.3f\" is less than \"CacheTimeout " + "%.3f\". " + "Ajusting \"CacheFlush\" to %.3f seconds.", + CDTIME_T_TO_DOUBLE(cache_flush_timeout), + CDTIME_T_TO_DOUBLE(cache_timeout), + CDTIME_T_TO_DOUBLE(cache_timeout * 10)); cache_flush_timeout = 10 * cache_timeout; + } + + /* Assure that "cache_timeout + random_variation" is never negative. */ + if (random_timeout > cache_timeout) { + INFO("rrdtool plugin: Adjusting \"RandomTimeout\" to %.3f seconds.", + CDTIME_T_TO_DOUBLE(cache_timeout)); + random_timeout = cache_timeout; + } pthread_mutex_unlock(&cache_lock); - status = + int status = plugin_thread_create(&queue_thread, /* attr = */ NULL, rrd_queue_thread, /* args = */ NULL, "rrdtool queue"); if (status != 0) { diff --git a/src/snmp.c b/src/snmp.c index c299db96..0a20e34b 100644 --- a/src/snmp.c +++ b/src/snmp.c @@ -63,7 +63,7 @@ struct data_definition_s { struct data_definition_s *next; char **ignores; size_t ignores_len; - int invert_match; + _Bool invert_match; }; typedef struct data_definition_s data_definition_t; @@ -71,6 +71,8 @@ struct host_definition_s { char *name; char *address; int version; + cdtime_t timeout; + int retries; /* snmpv1/2 options */ char *community; @@ -327,29 +329,14 @@ static int csnmp_config_add_data_blacklist(data_definition_t *dd, return 0; } /* int csnmp_config_add_data_blacklist */ -static int csnmp_config_add_data_blacklist_match_inverted(data_definition_t *dd, - oconfig_item_t *ci) { - if ((ci->values_num != 1) || (ci->values[0].type != OCONFIG_TYPE_BOOLEAN)) { - WARNING("snmp plugin: `InvertMatch' needs exactly one boolean argument."); - return -1; - } - - dd->invert_match = ci->values[0].value.boolean ? 1 : 0; - - return 0; -} /* int csnmp_config_add_data_blacklist_match_inverted */ - static int csnmp_config_add_data(oconfig_item_t *ci) { - data_definition_t *dd; - int status = 0; - - dd = calloc(1, sizeof(*dd)); + data_definition_t *dd = calloc(1, sizeof(*dd)); if (dd == NULL) return -1; - status = cf_util_get_string(ci, &dd->name); + int status = cf_util_get_string(ci, &dd->name); if (status != 0) { - free(dd); + sfree(dd); return -1; } @@ -376,7 +363,7 @@ static int csnmp_config_add_data(oconfig_item_t *ci) { else if (strcasecmp("Ignore", option->key) == 0) status = csnmp_config_add_data_blacklist(dd, option); else if (strcasecmp("InvertMatch", option->key) == 0) - status = csnmp_config_add_data_blacklist_match_inverted(dd, option); + status = cf_util_get_boolean(option, &dd->invert_match); else { WARNING("snmp plugin: Option `%s' not allowed here.", option->key); status = -1; @@ -597,6 +584,10 @@ static int csnmp_config_add_host(oconfig_item_t *ci) { hd->sess_handle = NULL; hd->interval = 0; + /* These mean that we have not set a timeout or retry value */ + hd->timeout = 0; + hd->retries = -1; + for (int i = 0; i < ci->children_num; i++) { oconfig_item_t *option = ci->children + i; status = 0; @@ -607,6 +598,10 @@ static int csnmp_config_add_host(oconfig_item_t *ci) { status = cf_util_get_string(option, &hd->community); else if (strcasecmp("Version", option->key) == 0) status = csnmp_config_add_host_version(hd, option); + else if (strcasecmp("Timeout", option->key) == 0) + cf_util_get_cdtime(option, &hd->timeout); + else if (strcasecmp("Retries", option->key) == 0) + cf_util_get_int(option, &hd->retries); else if (strcasecmp("Collect", option->key) == 0) csnmp_config_add_host_collect(hd, option); else if (strcasecmp("Interval", option->key) == 0) @@ -711,7 +706,6 @@ static int csnmp_config_add_host(oconfig_item_t *ci) { }); if (status != 0) { ERROR("snmp plugin: Registering complex read function failed."); - csnmp_host_definition_destroy(hd); return -1; } @@ -804,6 +798,15 @@ static void csnmp_host_open_session(host_definition_t *host) { sess.community_len = strlen(host->community); } + /* Set timeout & retries, if they have been changed from the default */ + if (host->timeout != 0) { + /* net-snmp expects microseconds */ + sess.timeout = CDTIME_T_TO_US(host->timeout); + } + if (host->retries >= 0) { + sess.retries = host->retries; + } + /* snmp_sess_open will copy the `struct snmp_session *'. */ host->sess_handle = snmp_sess_open(&sess); @@ -1033,7 +1036,6 @@ static int csnmp_instance_list_add(csnmp_list_instances_t **head, struct variable_list *vb; oid_t vb_name; int status; - uint32_t is_matched; /* Set vb on the last variable */ for (vb = res->variables; (vb != NULL) && (vb->next_variable != NULL); @@ -1063,11 +1065,11 @@ static int csnmp_instance_list_add(csnmp_list_instances_t **head, char *ptr; csnmp_strvbcopy(il->instance, vb, sizeof(il->instance)); - is_matched = 0; + _Bool is_matched = 0; for (uint32_t i = 0; i < dd->ignores_len; i++) { status = fnmatch(dd->ignores[i], il->instance, 0); if (status == 0) { - if (dd->invert_match == 0) { + if (!dd->invert_match) { sfree(il); return 0; } else { @@ -1076,7 +1078,7 @@ static int csnmp_instance_list_add(csnmp_list_instances_t **head, } } } - if (dd->invert_match != 0 && is_matched == 0) { + if (dd->invert_match && !is_matched) { sfree(il); return 0; } @@ -1328,8 +1330,6 @@ static int csnmp_read_table(host_definition_t *host, data_definition_t *data) { status = 0; while (status == 0) { - int oid_list_todo_num; - req = snmp_pdu_create(SNMP_MSG_GETNEXT); if (req == NULL) { ERROR("snmp plugin: snmp_pdu_create failed."); @@ -1337,24 +1337,33 @@ static int csnmp_read_table(host_definition_t *host, data_definition_t *data) { break; } - oid_list_todo_num = 0; + size_t oid_list_todo_num = 0; + size_t var_idx[oid_list_len]; + memset(var_idx, 0, sizeof(var_idx)); + for (i = 0; i < oid_list_len; i++) { /* Do not rerequest already finished OIDs */ if (!oid_list_todo[i]) continue; - oid_list_todo_num++; snmp_add_null_var(req, oid_list[i].oid, oid_list[i].oid_len); + var_idx[oid_list_todo_num] = i; + oid_list_todo_num++; } if (oid_list_todo_num == 0) { /* The request is still empty - so we are finished */ DEBUG("snmp plugin: all variables have left their subtree"); + snmp_free_pdu(req); status = 0; break; } res = NULL; status = snmp_sess_synch_response(host->sess_handle, req, &res); + + /* snmp_sess_synch_response always frees our req PDU */ + req = NULL; + if ((status != STAT_SUCCESS) || (res == NULL)) { char *errstr = NULL; @@ -1368,8 +1377,6 @@ static int csnmp_read_table(host_definition_t *host, data_definition_t *data) { snmp_free_pdu(res); res = NULL; - /* snmp_synch_response already freed our PDU */ - req = NULL; sfree(errstr); csnmp_host_close_session(host); @@ -1389,6 +1396,39 @@ static int csnmp_read_table(host_definition_t *host, data_definition_t *data) { break; } + if (res->errstat != SNMP_ERR_NOERROR) { + if (res->errindex != 0) { + /* Find the OID which caused error */ + for (i = 1, vb = res->variables; vb != NULL && i != res->errindex; + vb = vb->next_variable, i++) + /* do nothing */; + } + + if ((res->errindex == 0) || (vb == NULL)) { + ERROR("snmp plugin: host %s; data %s: response error: %s (%li) ", + host->name, data->name, snmp_errstring(res->errstat), + res->errstat); + status = -1; + break; + } + + char oid_buffer[1024] = {0}; + snprint_objid(oid_buffer, sizeof(oid_buffer) - 1, vb->name, + vb->name_length); + NOTICE("snmp plugin: host %s; data %s: OID `%s` failed: %s", host->name, + data->name, oid_buffer, snmp_errstring(res->errstat)); + + /* Get value index from todo list and skip OID found */ + assert(res->errindex <= oid_list_todo_num); + i = var_idx[res->errindex - 1]; + assert(i < oid_list_len); + oid_list_todo[i] = 0; + + snmp_free_pdu(res); + res = NULL; + continue; + } + for (vb = res->variables, i = 0; (vb != NULL); vb = vb->next_variable, i++) { /* Calculate value index from todo list */ @@ -1484,10 +1524,6 @@ static int csnmp_read_table(host_definition_t *host, data_definition_t *data) { snmp_free_pdu(res); res = NULL; - if (req != NULL) - snmp_free_pdu(req); - req = NULL; - if (status == 0) csnmp_dispatch_table(host, data, instance_list_head, value_list_head); diff --git a/src/swap.c b/src/swap.c index a5531c24..78f05c5f 100644 --- a/src/swap.c +++ b/src/swap.c @@ -111,6 +111,7 @@ static int pagesize; static _Bool values_absolute = 1; static _Bool values_percentage = 0; +static _Bool report_io = 1; static int swap_config(oconfig_item_t *ci) /* {{{ */ { @@ -136,6 +137,8 @@ static int swap_config(oconfig_item_t *ci) /* {{{ */ cf_util_get_boolean(child, &values_absolute); else if (strcasecmp("ValuesPercentage", child->key) == 0) cf_util_get_boolean(child, &values_percentage); + else if (strcasecmp("ReportIO", child->key) == 0) + cf_util_get_boolean(child, &report_io); else WARNING("swap plugin: Unknown config option: \"%s\"", child->key); } @@ -406,7 +409,8 @@ static int swap_read(void) /* {{{ */ else swap_read_combined(); - swap_read_io(); + if (report_io) + swap_read_io(); return 0; } /* }}} int swap_read */ @@ -726,8 +730,11 @@ static int swap_read(void) /* {{{ */ reserved = (gauge_t)(pmemory.pgsp_rsvd * pagesize); swap_submit_usage(NULL, total - free, free, "reserved", reserved); - swap_submit_derive("in", (derive_t)pmemory.pgspins * pagesize); - swap_submit_derive("out", (derive_t)pmemory.pgspouts * pagesize); + + if (report_io) { + swap_submit_derive("in", (derive_t)pmemory.pgspins * pagesize); + swap_submit_derive("out", (derive_t)pmemory.pgspouts * pagesize); + } return 0; } /* }}} int swap_read */ diff --git a/src/synproxy.c b/src/synproxy.c new file mode 100644 index 00000000..6c6b997d --- /dev/null +++ b/src/synproxy.c @@ -0,0 +1,112 @@ +/** + * collectd - src/synproxy.c + * Copyright (C) 2017 Marek Becka + * + * 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: + * Marek Becka + **/ + +#include "collectd.h" + +#include "common.h" +#include "plugin.h" + +#if !KERNEL_LINUX +#error "No applicable input method." +#endif + +#define SYNPROXY_FIELDS 6 + +static const char *synproxy_stat_path = "/proc/net/stat/synproxy"; + +static const char *column_names[SYNPROXY_FIELDS] = { + "entries", "syn_received", "invalid", + "valid", "retransmission", "reopened"}; +static const char *column_types[SYNPROXY_FIELDS] = { + "current_connections", "connections", "cookies", "cookies", "cookies", + "connections"}; + +static void synproxy_submit(value_t *results) { + value_list_t vl = VALUE_LIST_INIT; + + /* 1st column (entries) is hardcoded to 0 in kernel code */ + for (size_t n = 1; n < SYNPROXY_FIELDS; n++) { + vl.values = &results[n]; + vl.values_len = 1; + + sstrncpy(vl.plugin, "synproxy", sizeof(vl.plugin)); + sstrncpy(vl.type, column_types[n], sizeof(vl.type)); + sstrncpy(vl.type_instance, column_names[n], sizeof(vl.type_instance)); + + plugin_dispatch_values(&vl); + } +} + +static int synproxy_read(void) { + char buf[1024]; + value_t results[SYNPROXY_FIELDS]; + int is_header = 1, status = 0; + + FILE *fh = fopen(synproxy_stat_path, "r"); + if (fh == NULL) { + ERROR("synproxy plugin: unable to open %s", synproxy_stat_path); + return -1; + } + + memset(results, 0, sizeof(results)); + + while (fgets(buf, sizeof(buf), fh) != NULL) { + char *fields[SYNPROXY_FIELDS], *endprt; + + if (is_header) { + is_header = 0; + continue; + } + + int numfields = strsplit(buf, fields, STATIC_ARRAY_SIZE(fields)); + if (numfields != SYNPROXY_FIELDS) { + ERROR("synproxy plugin: unexpected number of columns in %s", + synproxy_stat_path); + status = -1; + break; + } + + /* 1st column (entries) is hardcoded to 0 in kernel code */ + for (size_t n = 1; n < SYNPROXY_FIELDS; n++) { + char *endptr = NULL; + errno = 0; + + results[n].derive += strtoull(fields[n], &endprt, 16); + if ((endptr == fields[n]) || errno != 0) { + ERROR("synproxy plugin: unable to parse value: %s", fields[n]); + fclose(fh); + return -1; + } + } + } + + fclose(fh); + + if (status == 0) { + synproxy_submit(results); + } + + return status; +} + +void module_register(void) { + plugin_register_read("synproxy", synproxy_read); +} /* void module_register */ diff --git a/src/table.c b/src/table.c index 578e019e..5fb51512 100644 --- a/src/table.c +++ b/src/table.c @@ -55,6 +55,7 @@ typedef struct { typedef struct { char *file; char *sep; + char *plugin_name; char *instance; tbl_result_t *results; @@ -92,6 +93,7 @@ static void tbl_result_clear(tbl_result_t *res) { static void tbl_setup(tbl_t *tbl, char *file) { tbl->file = sstrdup(file); tbl->sep = NULL; + tbl->plugin_name = NULL; tbl->instance = NULL; tbl->results = NULL; @@ -103,6 +105,7 @@ static void tbl_setup(tbl_t *tbl, char *file) { static void tbl_clear(tbl_t *tbl) { sfree(tbl->file); sfree(tbl->sep); + sfree(tbl->plugin_name); sfree(tbl->instance); for (size_t i = 0; i < tbl->results_num; ++i) @@ -256,6 +259,8 @@ static int tbl_config_table(oconfig_item_t *ci) { if (0 == strcasecmp(c->key, "Separator")) tbl_config_set_s(c->key, &tbl->sep, c); + else if (0 == strcasecmp(c->key, "Plugin")) + tbl_config_set_s(c->key, &tbl->plugin_name, c); else if (0 == strcasecmp(c->key, "Instance")) tbl_config_set_s(c->key, &tbl->instance, c); else if (0 == strcasecmp(c->key, "Result")) @@ -367,7 +372,8 @@ static int tbl_result_dispatch(tbl_t *tbl, tbl_result_t *res, char **fields, vl.values = values; vl.values_len = STATIC_ARRAY_SIZE(values); - sstrncpy(vl.plugin, "table", sizeof(vl.plugin)); + sstrncpy(vl.plugin, (tbl->plugin_name != NULL) ? tbl->plugin_name : "table", + sizeof(vl.plugin)); sstrncpy(vl.plugin_instance, tbl->instance, sizeof(vl.plugin_instance)); sstrncpy(vl.type, res->type, sizeof(vl.type)); diff --git a/src/tail.c b/src/tail.c index 407970ae..fbba4788 100644 --- a/src/tail.c +++ b/src/tail.c @@ -34,7 +34,8 @@ /* * * - * Instance "exim" + * Plugin "mail" + * Instance "exim" * Interval 60 * * Regex "S=([1-9][0-9]*)" @@ -134,6 +135,7 @@ static int ctail_config_add_match_dstype(ctail_config_match_t *cm, } /* int ctail_config_add_match_dstype */ static int ctail_config_add_match(cu_tail_match_t *tm, + const char *plugin_name, const char *plugin_instance, oconfig_item_t *ci, cdtime_t interval) { ctail_config_match_t cm = {0}; @@ -191,7 +193,8 @@ static int ctail_config_add_match(cu_tail_match_t *tm, if (status == 0) { // TODO(octo): there's nothing "simple" about the latency stuff … status = tail_match_add_match_simple( - tm, cm.regex, cm.excluderegex, cm.flags, "tail", plugin_instance, + tm, cm.regex, cm.excluderegex, cm.flags, + (plugin_name != NULL) ? plugin_name : "tail", plugin_instance, cm.type, cm.type_instance, cm.latency, interval); if (status != 0) @@ -210,6 +213,7 @@ static int ctail_config_add_match(cu_tail_match_t *tm, static int ctail_config_add_file(oconfig_item_t *ci) { cu_tail_match_t *tm; cdtime_t interval = 0; + char *plugin_name = NULL; char *plugin_instance = NULL; int num_matches = 0; @@ -229,12 +233,15 @@ static int ctail_config_add_file(oconfig_item_t *ci) { oconfig_item_t *option = ci->children + i; int status = 0; - if (strcasecmp("Instance", option->key) == 0) + if (strcasecmp("Plugin", option->key) == 0) + status = cf_util_get_string (option, &plugin_name); + else if (strcasecmp("Instance", option->key) == 0) status = cf_util_get_string(option, &plugin_instance); else if (strcasecmp("Interval", option->key) == 0) cf_util_get_cdtime(option, &interval); else if (strcasecmp("Match", option->key) == 0) { - status = ctail_config_add_match(tm, plugin_instance, option, interval); + status = ctail_config_add_match(tm, plugin_name, plugin_instance, option, + interval); if (status == 0) num_matches++; /* Be mild with failed matches.. */ @@ -247,6 +254,7 @@ static int ctail_config_add_file(oconfig_item_t *ci) { break; } /* for (i = 0; i < ci->children_num; i++) */ + sfree(plugin_name); sfree(plugin_instance); if (num_matches == 0) { diff --git a/src/tail_csv.c b/src/tail_csv.c index 9d0a15be..2e3ac5f0 100644 --- a/src/tail_csv.c +++ b/src/tail_csv.c @@ -44,6 +44,7 @@ struct metric_definition_s { typedef struct metric_definition_s metric_definition_t; struct instance_definition_s { + char *plugin_name; char *instance; char *path; cu_tail_t *tail; @@ -67,7 +68,8 @@ static int tcsv_submit(instance_definition_t *id, metric_definition_t *md, vl.values_len = 1; vl.values = &v; - sstrncpy(vl.plugin, "tail_csv", sizeof(vl.plugin)); + sstrncpy(vl.plugin, (id->plugin_name != NULL) ? id->plugin_name : "tail_csv", + sizeof(vl.plugin)); if (id->instance != NULL) sstrncpy(vl.plugin_instance, id->instance, sizeof(vl.plugin_instance)); sstrncpy(vl.type, md->type, sizeof(vl.type)); @@ -358,6 +360,7 @@ static void tcsv_instance_definition_destroy(void *arg) { cu_tail_destroy(id->tail); id->tail = NULL; + sfree(id->plugin_name); sfree(id->instance); sfree(id->path); sfree(id->metric_list); @@ -420,6 +423,7 @@ static int tcsv_config_add_file(oconfig_item_t *ci) { id = calloc(1, sizeof(*id)); if (id == NULL) return -1; + id->plugin_name = NULL; id->instance = NULL; id->path = NULL; id->metric_list = NULL; @@ -447,6 +451,8 @@ static int tcsv_config_add_file(oconfig_item_t *ci) { cf_util_get_cdtime(option, &id->interval); else if (strcasecmp("TimeFrom", option->key) == 0) status = tcsv_config_get_index(option, &id->time_from); + else if (strcasecmp("Plugin", option->key) == 0) + status = cf_util_get_string(option, &id->plugin_name); else { WARNING("tail_csv plugin: Option `%s' not allowed here.", option->key); status = -1; @@ -484,7 +490,6 @@ static int tcsv_config_add_file(oconfig_item_t *ci) { }); if (status != 0) { ERROR("tail_csv plugin: Registering complex read function failed."); - tcsv_instance_definition_destroy(id); return -1; } diff --git a/src/ted.c b/src/ted.c index 94b4e3ab..3b64b75f 100644 --- a/src/ted.c +++ b/src/ted.c @@ -88,7 +88,7 @@ static int ted_read_value(double *ret_power, double *ret_voltage) { status = write(fd, pkt_request, sizeof(pkt_request)); if (status <= 0) { - ERROR("ted plugin: swrite failed."); + ERROR("ted plugin: write failed."); return -1; } diff --git a/src/turbostat.c b/src/turbostat.c index 1049fb2a..e4419b85 100644 --- a/src/turbostat.c +++ b/src/turbostat.c @@ -41,7 +41,7 @@ #include "plugin.h" #include "utils_time.h" -#include +#include "msr-index.h" #include #ifdef HAVE_SYS_CAPABILITY_H #include @@ -585,7 +585,10 @@ static int submit_counters(struct thread_data *t, struct core_data *c, /* If not using logical core numbering, set core id */ if (!config_lcn) { - snprintf(name, sizeof(name), "core%02d", c->core_id); + if (topology.num_packages > 1) + snprintf(name, sizeof(name), "pkg%02d-core%02d", p->package_id, c->core_id); + else + snprintf(name, sizeof(name), "core%02d", c->core_id); } if (do_core_cstate & (1 << 3)) diff --git a/src/types.db b/src/types.db index c9838e16..577d28f1 100644 --- a/src/types.db +++ b/src/types.db @@ -34,6 +34,7 @@ compression_ratio value:GAUGE:0:2 connections value:DERIVE:0:U conntrack value:GAUGE:0:4294967295 contextswitch value:DERIVE:0:U +cookies value:DERIVE:0:U count value:GAUGE:0:U counter value:COUNTER:U:U cpu value:DERIVE:0:U @@ -117,6 +118,7 @@ if_tx_octets value:DERIVE:0:U if_tx_packets value:DERIVE:0:U invocations value:DERIVE:0:U io_octets rx:DERIVE:0:U, tx:DERIVE:0:U +io_ops read:DERIVE:0:U, write:DERIVE:0:U io_packets rx:DERIVE:0:U, tx:DERIVE:0:U ipc value:GAUGE:0:U ipt_bytes value:DERIVE:0:U @@ -189,6 +191,7 @@ ping value:GAUGE:0:65535 ping_droprate value:GAUGE:0:100 ping_stddev value:GAUGE:0:65535 players value:GAUGE:0:1000000 +pools value:GAUGE:0:U power value:GAUGE:U:U pressure value:GAUGE:0:U protocol_counter value:DERIVE:0:U diff --git a/src/unixsock.c b/src/unixsock.c index 00b930e6..99e39eee 100644 --- a/src/unixsock.c +++ b/src/unixsock.c @@ -134,7 +134,13 @@ static int us_open_socket(void) { const char *grpname; struct group *g; struct group sg; - char grbuf[4096]; + + long int grbuf_size = sysconf(_SC_GETGR_R_SIZE_MAX); + if (grbuf_size <= 0) + grbuf_size = sysconf(_SC_PAGESIZE); + if (grbuf_size <= 0) + grbuf_size = 4096; + char grbuf[grbuf_size]; grpname = (sock_group != NULL) ? sock_group : COLLECTD_GRP_NAME; g = NULL; diff --git a/src/uptime.c b/src/uptime.c index 2be9d79d..d51aa391 100644 --- a/src/uptime.c +++ b/src/uptime.c @@ -25,8 +25,7 @@ #include "plugin.h" #if KERNEL_LINUX -#define STAT_FILE "/proc/stat" -/* Using /proc filesystem to retrieve the boot time, Linux only. */ +#include /* #endif KERNEL_LINUX */ #elif HAVE_LIBKSTAT @@ -53,8 +52,6 @@ /* * Global variables */ -/* boottime always used, no OS distinction */ -static time_t boottime; #if HAVE_LIBKSTAT extern kstat_ctl_t *kc; @@ -72,8 +69,6 @@ static void uptime_submit(gauge_t value) { plugin_dispatch_values(&vl); } -static int uptime_init(void) /* {{{ */ -{ /* * On most unix systems the uptime is calculated by looking at the boot * time (stored in unix time, since epoch) and the current one. We are @@ -84,48 +79,21 @@ static int uptime_init(void) /* {{{ */ * the boot time, the plugin is unregistered and there is no chance to * try again later. Nevertheless, this is very unlikely to happen. */ - +static time_t uptime_get_sys(void) { /* {{{ */ + time_t result; #if KERNEL_LINUX - unsigned long starttime; - char buffer[1024]; - int ret; - FILE *fh; - - ret = 0; - - fh = fopen(STAT_FILE, "r"); + struct sysinfo info; + int status; - if (fh == NULL) { + status = sysinfo(&info); + if (status != 0) { char errbuf[1024]; - ERROR("uptime plugin: Cannot open " STAT_FILE ": %s", + ERROR("uptime plugin: Error calling sysinfo: %s", sstrerror(errno, errbuf, sizeof(errbuf))); return -1; } - while (fgets(buffer, 1024, fh) != NULL) { - /* look for the btime string and read the value */ - ret = sscanf(buffer, "btime %lu", &starttime); - /* avoid further loops if btime has been found and read - * correctly (hopefully) */ - if (ret == 1) - break; - } - - fclose(fh); - - /* loop done, check if no value has been found/read */ - if (ret != 1) { - ERROR("uptime plugin: No value read from " STAT_FILE ""); - return -1; - } - - boottime = (time_t)starttime; - - if (boottime == 0) { - ERROR("uptime plugin: btime read from " STAT_FILE ", " - "but `boottime' is zero!"); - return -1; - } + result = (time_t)info.uptime; /* #endif KERNEL_LINUX */ #elif HAVE_LIBKSTAT @@ -159,13 +127,13 @@ static int uptime_init(void) /* {{{ */ return -1; } - boottime = (time_t)knp->value.ui32; - - if (boottime == 0) { + if (knp->value.ui32 == 0) { ERROR("uptime plugin: kstat_data_lookup returned success, " "but `boottime' is zero!"); return -1; } + + result = time(NULL) - (time_t)knp->value.ui32; /* #endif HAVE_LIBKSTAT */ #elif HAVE_SYS_SYSCTL_H @@ -186,13 +154,13 @@ static int uptime_init(void) /* {{{ */ return -1; } - boottime = boottv.tv_sec; - - if (boottime == 0) { + if (boottv.tv_sec == 0) { ERROR("uptime plugin: sysctl(3) returned success, " "but `boottime' is zero!"); return -1; } + + result = time(NULL) - boottv.tv_sec; /* #endif HAVE_SYS_SYSCTL_H */ #elif HAVE_PERFSTAT @@ -212,18 +180,18 @@ static int uptime_init(void) /* {{{ */ if (hertz <= 0) hertz = HZ; - boottime = time(NULL) - cputotal.lbolt / hertz; + result = cputotal.lbolt / hertz; #endif /* HAVE_PERFSTAT */ - return 0; -} /* }}} int uptime_init */ + return result; +} /* }}} int uptime_get_sys */ static int uptime_read(void) { gauge_t uptime; time_t elapsed; /* calculate the amount of time elapsed since boot, AKA uptime */ - elapsed = time(NULL) - boottime; + elapsed = uptime_get_sys(); uptime = (gauge_t)elapsed; @@ -233,6 +201,5 @@ static int uptime_read(void) { } void module_register(void) { - plugin_register_init("uptime", uptime_init); plugin_register_read("uptime", uptime_read); } /* void module_register */ diff --git a/src/utils_curl_stats.c b/src/utils_curl_stats.c index 2a426646..2a1d9de3 100644 --- a/src/utils_curl_stats.c +++ b/src/utils_curl_stats.c @@ -218,15 +218,15 @@ int curl_stats_dispatch(curl_stats_t *s, CURL *curl, const char *hostname, if (s == NULL) return 0; - if ((curl == NULL) || (hostname == NULL) || (plugin == NULL)) { + if ((curl == NULL) || (plugin == NULL)) { ERROR("curl stats: dispatch() called with missing arguments " - "(curl=%p; hostname=%s; plugin=%s)", - curl, hostname == NULL ? "" : hostname, - plugin == NULL ? "" : plugin); + "(curl=%p; plugin=%s)", + curl, plugin == NULL ? "" : plugin); return -1; } - sstrncpy(vl.host, hostname, sizeof(vl.host)); + if (hostname != NULL) + sstrncpy(vl.host, hostname, sizeof(vl.host)); sstrncpy(vl.plugin, plugin, sizeof(vl.plugin)); if (plugin_instance != NULL) sstrncpy(vl.plugin_instance, plugin_instance, sizeof(vl.plugin_instance)); diff --git a/src/utils_format_kairosdb.c b/src/utils_format_kairosdb.c index 0128c575..460f807a 100644 --- a/src/utils_format_kairosdb.c +++ b/src/utils_format_kairosdb.c @@ -183,7 +183,8 @@ static int value_list_to_kairosdb(char *buffer, size_t buffer_size, /* {{{ */ const data_set_t *ds, const value_list_t *vl, int store_rates, char const *const *http_attrs, - size_t http_attrs_num, int data_ttl) { + size_t http_attrs_num, int data_ttl, + char const *metrics_prefix) { char temp[512]; size_t offset = 0; int status; @@ -212,11 +213,13 @@ static int value_list_to_kairosdb(char *buffer, size_t buffer_size, /* {{{ */ for (size_t i = 0; i < ds->ds_num; i++) { /* All value lists have a leading comma. The first one will be replaced with * a square bracket in `format_kairosdb_finalize'. */ - BUFFER_ADD(",{"); + BUFFER_ADD(",{\"name\":\""); - BUFFER_ADD("\"name\":\"collectd"); + if (metrics_prefix != NULL) { + BUFFER_ADD("%s.", metrics_prefix); + } - BUFFER_ADD(".%s", vl->plugin); + BUFFER_ADD("%s", vl->plugin); status = values_to_kairosdb(temp, sizeof(temp), ds, vl, store_rates, i); if (status != 0) @@ -263,12 +266,14 @@ static int format_kairosdb_value_list_nocheck( char *buffer, /* {{{ */ size_t *ret_buffer_fill, size_t *ret_buffer_free, const data_set_t *ds, const value_list_t *vl, int store_rates, size_t temp_size, - char const *const *http_attrs, size_t http_attrs_num, int data_ttl) { + char const *const *http_attrs, size_t http_attrs_num, int data_ttl, + char const *metrics_prefix) { char temp[temp_size]; int status; status = value_list_to_kairosdb(temp, sizeof(temp), ds, vl, store_rates, - http_attrs, http_attrs_num, data_ttl); + http_attrs, http_attrs_num, data_ttl, + metrics_prefix); if (status != 0) return status; temp_size = strlen(temp); @@ -337,7 +342,8 @@ int format_kairosdb_value_list(char *buffer, /* {{{ */ size_t *ret_buffer_fill, size_t *ret_buffer_free, const data_set_t *ds, const value_list_t *vl, int store_rates, char const *const *http_attrs, - size_t http_attrs_num, int data_ttl) { + size_t http_attrs_num, int data_ttl, + char const *metrics_prefix) { if ((buffer == NULL) || (ret_buffer_fill == NULL) || (ret_buffer_free == NULL) || (ds == NULL) || (vl == NULL)) return -EINVAL; @@ -347,7 +353,8 @@ int format_kairosdb_value_list(char *buffer, /* {{{ */ return format_kairosdb_value_list_nocheck( buffer, ret_buffer_fill, ret_buffer_free, ds, vl, store_rates, - (*ret_buffer_free) - 2, http_attrs, http_attrs_num, data_ttl); + (*ret_buffer_free) - 2, http_attrs, http_attrs_num, data_ttl, + metrics_prefix); } /* }}} int format_kairosdb_value_list */ /* vim: set sw=2 sts=2 et fdm=marker : */ diff --git a/src/utils_format_kairosdb.h b/src/utils_format_kairosdb.h index 3a4c7c77..7b9e0e7a 100644 --- a/src/utils_format_kairosdb.h +++ b/src/utils_format_kairosdb.h @@ -41,7 +41,8 @@ int format_kairosdb_value_list(char *buffer, size_t *ret_buffer_fill, size_t *ret_buffer_free, const data_set_t *ds, const value_list_t *vl, int store_rates, char const *const *http_attrs, - size_t http_attrs_num, int data_ttl); + size_t http_attrs_num, int data_ttl, + char const *metrics_prefix); int format_kairosdb_finalize(char *buffer, size_t *ret_buffer_fill, size_t *ret_buffer_free); diff --git a/src/utils_latency_config.c b/src/utils_latency_config.c index 0f8c2a26..5eb5b6d9 100644 --- a/src/utils_latency_config.c +++ b/src/utils_latency_config.c @@ -105,6 +105,8 @@ int latency_config(latency_config_t *conf, oconfig_item_t *ci, status = latency_config_add_percentile(conf, child, plugin); else if (strcasecmp("Bucket", child->key) == 0) status = latency_config_add_bucket(conf, child, plugin); + else if (strcasecmp("BucketType", child->key) == 0) + status = cf_util_get_string(child, &conf->bucket_type); else WARNING("%s plugin: \"%s\" is not a valid option within a \"%s\" block.", plugin, child->key, ci->key); @@ -137,6 +139,14 @@ int latency_config_copy(latency_config_t *dst, const latency_config_t src) { return ENOMEM; } + if (src.bucket_type != NULL) { + dst->bucket_type = strdup(src.bucket_type); + if (dst->bucket_type == NULL) { + latency_config_free(*dst); + return ENOMEM; + } + } + memmove(dst->percentile, src.percentile, dst->percentile_num * sizeof(*dst->percentile)); memmove(dst->buckets, src.buckets, dst->buckets_num * sizeof(*dst->buckets)); @@ -147,4 +157,5 @@ int latency_config_copy(latency_config_t *dst, const latency_config_t src) { void latency_config_free(latency_config_t conf) { sfree(conf.percentile); sfree(conf.buckets); + sfree(conf.bucket_type); } /* void latency_config_free */ diff --git a/src/utils_latency_config.h b/src/utils_latency_config.h index 9a7a11af..7008fd00 100644 --- a/src/utils_latency_config.h +++ b/src/utils_latency_config.h @@ -44,6 +44,7 @@ typedef struct { latency_bucket_t *buckets; size_t buckets_num; + char *bucket_type; /* _Bool lower; diff --git a/src/utils_match.c b/src/utils_match.c index df70fbec..d3edb57c 100644 --- a/src/utils_match.c +++ b/src/utils_match.c @@ -294,7 +294,7 @@ void match_value_reset(cu_match_value_t *mv) { /* Reset GAUGE metrics only and except GAUGE_PERSIST. */ if ((mv->ds_type & UTILS_MATCH_DS_TYPE_GAUGE) && !(mv->ds_type & UTILS_MATCH_CF_GAUGE_PERSIST)) { - mv->value.gauge = NAN; + mv->value.gauge = (mv->ds_type & UTILS_MATCH_CF_GAUGE_INC) ? 0 : NAN; mv->values_num = 0; } } /* }}} void match_value_reset */ diff --git a/src/utils_tail_match.c b/src/utils_tail_match.c index af8bbe7c..65655dcd 100644 --- a/src/utils_tail_match.c +++ b/src/utils_tail_match.c @@ -108,7 +108,6 @@ static int latency_submit_match(cu_match_t *match, void *user_data) { if (match_value == NULL) return -1; - sstrncpy(vl.host, hostname_g, sizeof(vl.host)); sstrncpy(vl.plugin, data->plugin, sizeof(vl.plugin)); sstrncpy(vl.plugin_instance, data->plugin_instance, sizeof(vl.plugin_instance)); @@ -138,7 +137,11 @@ static int latency_submit_match(cu_match_t *match, void *user_data) { } /* Submit buckets */ - sstrncpy(vl.type, "bucket", sizeof(vl.type)); + if (data->latency_config.bucket_type != NULL) + sstrncpy(vl.type, data->latency_config.bucket_type, sizeof(vl.type)); + else + sstrncpy(vl.type, "bucket", sizeof(vl.type)); + for (size_t i = 0; i < data->latency_config.buckets_num; i++) { latency_bucket_t bucket = data->latency_config.buckets[i]; diff --git a/src/varnish.c b/src/varnish.c index 7c5a9ef0..e4daf4bf 100644 --- a/src/varnish.c +++ b/src/varnish.c @@ -21,6 +21,7 @@ * Jérôme Renard * Marc Fournier * Florian octo Forster + * Denes Matetelki **/ #include "collectd.h" @@ -69,6 +70,8 @@ struct user_config_s { _Bool collect_sms; #if HAVE_VARNISH_V2 _Bool collect_sm; +#endif +#if HAVE_VARNISH_V2 || HAVE_VARNISH_V4 _Bool collect_sma; #endif _Bool collect_struct; @@ -80,6 +83,12 @@ struct user_config_s { _Bool collect_workers; #if HAVE_VARNISH_V4 _Bool collect_vsm; + _Bool collect_lck; + _Bool collect_mempool; + _Bool collect_mgt; + _Bool collect_smf; + _Bool collect_vbe; + _Bool collect_mse; #endif }; typedef struct user_config_s user_config_t; /* }}} */ @@ -182,6 +191,14 @@ static int varnish_monitor(void *priv, else if (strcmp(name, "client_req") == 0) return varnish_submit_derive(conf->instance, "connections", "connections", "received", val); +#ifdef HAVE_VARNISH_V4 + else if (strcmp(name, "client_req_400") == 0) + return varnish_submit_derive(conf->instance, "connections", "connections", + "error_400", val); + else if (strcmp(name, "client_req_417") == 0) + return varnish_submit_derive(conf->instance, "connections", "connections", + "error_417", val); +#endif } #ifdef HAVE_VARNISH_V3 @@ -211,6 +228,9 @@ static int varnish_monitor(void *priv, else if (strcmp(name, "esi_warnings") == 0) return varnish_submit_derive(conf->instance, "esi", "total_operations", "warning", val); + else if (strcmp(name, "esi_maxdepth") == 0) + return varnish_submit_derive(conf->instance, "esi", "total_operations", + "max_depth", val); } if (conf->collect_backend) { @@ -286,6 +306,20 @@ static int varnish_monitor(void *priv, else if (strcmp(name, "fetch_304") == 0) return varnish_submit_derive(conf->instance, "fetch", "http_requests", "no_body_304", val); +#if HAVE_VARNISH_V4 + else if (strcmp(name, "fetch_no_thread") == 0) + return varnish_submit_derive(conf->instance, "fetch", "http_requests", + "no_thread", val); + else if (strcmp(name, "fetch_none") == 0) + return varnish_submit_derive(conf->instance, "fetch", "http_requests", + "none", val); + else if (strcmp(name, "busy_sleep") == 0) + return varnish_submit_derive(conf->instance, "fetch", "http_requests", + "busy_sleep", val); + else if (strcmp(name, "busy_wakeup") == 0) + return varnish_submit_derive(conf->instance, "fetch", "http_requests", + "busy_wakeup", val); +#endif } if (conf->collect_hcb) { @@ -331,6 +365,14 @@ static int varnish_monitor(void *priv, else if (strcmp(name, "n_objoverflow") == 0) return varnish_submit_derive(conf->instance, "objects", "total_objects", "workspace_overflow", val); +#if HAVE_VARNISH_V4 + else if (strcmp(name, "exp_mailed") == 0) + return varnish_submit_gauge(conf->instance, "struct", "objects", + "exp_mailed", val); + else if (strcmp(name, "exp_received") == 0) + return varnish_submit_gauge(conf->instance, "struct", "objects", + "exp_received", val); +#endif } #if HAVE_VARNISH_V3 @@ -381,6 +423,34 @@ static int varnish_monitor(void *priv, else if (strcmp(name, "bans_dups") == 0) return varnish_submit_derive(conf->instance, "ban", "total_operations", "duplicate", val); + else if (strcmp(name, "bans_tested") == 0) + return varnish_submit_derive(conf->instance, "ban", "total_operations", + "tested", val); + else if (strcmp(name, "bans_lurker_contention") == 0) + return varnish_submit_derive(conf->instance, "ban", "total_operations", + "lurker_contention", val); + else if (strcmp(name, "bans_lurker_obj_killed") == 0) + return varnish_submit_derive(conf->instance, "ban", "total_operations", + "lurker_obj_killed", val); + else if (strcmp(name, "bans_lurker_tested") == 0) + return varnish_submit_derive(conf->instance, "ban", "total_operations", + "lurker_tested", val); + else if (strcmp(name, "bans_lurker_tests_tested") == 0) + return varnish_submit_derive(conf->instance, "ban", "total_operations", + "lurker_tests_tested", val); + else if (strcmp(name, "bans_obj_killed") == 0) + return varnish_submit_derive(conf->instance, "ban", "total_operations", + "obj_killed", val); + else if (strcmp(name, "bans_persisted_bytes") == 0) + return varnish_submit_derive(conf->instance, "ban", "total_bytes", + "persisted_bytes", val); + else if (strcmp(name, "bans_persisted_fragmentation") == 0) + return varnish_submit_derive(conf->instance, "ban", "total_bytes", + "persisted_fragmentation", val); + else if (strcmp(name, "bans_tests_tested") == 0) + return varnish_submit_derive(conf->instance, "ban", "total_operations", + "tests_tested", val); + } #endif @@ -415,6 +485,14 @@ static int varnish_monitor(void *priv, else if (strcmp(name, "sess_herd") == 0) return varnish_submit_derive(conf->instance, "session", "total_operations", "herd", val); +#if HAVE_VARNISH_V4 + else if (strcmp(name, "sess_closed_err") == 0) + return varnish_submit_derive(conf->instance, "session", + "total_operations", "closed_err", val); + else if (strcmp(name, "sess_dropped") == 0) + return varnish_submit_derive(conf->instance, "session", + "total_operations", "dropped_for_thread", val); +#endif } if (conf->collect_shm) { @@ -514,12 +592,18 @@ static int varnish_monitor(void *priv, else if (strcmp(name, "s_req_bodybytes") == 0) return varnish_submit_derive(conf->instance, "totals", "total_bytes", "req_body", val); + else if (strcmp(name, "s_req_protobytes") == 0) + return varnish_submit_derive(conf->instance, "totals", "total_bytes", + "req_proto", val); else if (strcmp(name, "s_resp_hdrbytes") == 0) return varnish_submit_derive(conf->instance, "totals", "total_bytes", "resp_header", val); else if (strcmp(name, "s_resp_bodybytes") == 0) return varnish_submit_derive(conf->instance, "totals", "total_bytes", "resp_body", val); + else if (strcmp(name, "s_resp_protobytes") == 0) + return varnish_submit_derive(conf->instance, "totals", "total_bytes", + "resp_proto", val); else if (strcmp(name, "s_pipe_hdrbytes") == 0) return varnish_submit_derive(conf->instance, "totals", "total_bytes", "pipe_header", val); @@ -584,7 +668,7 @@ static int varnish_monitor(void *priv, return varnish_submit_derive(conf->instance, "workers", "total_threads", "dropped", val); else if (strcmp(name, "thread_queue_len") == 0) - return varnish_submit_derive(conf->instance, "workers", "queue_length", + return varnish_submit_gauge(conf->instance, "workers", "queue_length", "threads", val); else if (strcmp(name, "n_wrk") == 0) return varnish_submit_gauge(conf->instance, "workers", "threads", @@ -613,6 +697,14 @@ static int varnish_monitor(void *priv, else if (strcmp(name, "n_wrk_lqueue") == 0) return varnish_submit_derive(conf->instance, "workers", "total_requests", "queue_length", val); +#if HAVE_VARNISH_V4 + else if (strcmp(name, "pools") == 0) + return varnish_submit_gauge(conf->instance, "workers", "pools", + "pools", val); + else if (strcmp(name, "busy_killed") == 0) + return varnish_submit_derive(conf->instance, "workers", "http_requests", + "busy_killed", val); +#endif } #if HAVE_VARNISH_V4 @@ -631,6 +723,260 @@ static int varnish_monitor(void *priv, return varnish_submit_derive(conf->instance, "vsm", "total_bytes", "overflowed", val); } + + if (conf->collect_vbe) { + /* @TODO figure out the collectd type for bitmap + if (strcmp(name, "happy") == 0) + return varnish_submit_derive(conf->instance, "vbe", + "bitmap", "happy_hprobes", val); + */ + if (strcmp(name, "bereq_hdrbytes") == 0) + return varnish_submit_derive(conf->instance, "vbe", + "total_bytes", "bereq_hdrbytes", val); + else if (strcmp(name, "bereq_bodybytes") == 0) + return varnish_submit_derive(conf->instance, "vbe", + "total_bytes", "bereq_bodybytes", val); + else if (strcmp(name, "bereq_protobytes") == 0) + return varnish_submit_derive(conf->instance, "vbe", + "total_bytes", "bereq_protobytes", val); + else if (strcmp(name, "beresp_hdrbytes") == 0) + return varnish_submit_derive(conf->instance, "vbe", + "total_bytes", "beresp_hdrbytes", val); + else if (strcmp(name, "beresp_bodybytes") == 0) + return varnish_submit_derive(conf->instance, "vbe", + "total_bytes", "beresp_bodybytes", val); + else if (strcmp(name, "beresp_protobytes") == 0) + return varnish_submit_derive(conf->instance, "vbe", + "total_bytes", "beresp_protobytes", val); + else if (strcmp(name, "pipe_hdrbytes") == 0) + return varnish_submit_derive(conf->instance, "vbe", + "total_bytes", "pipe_hdrbytes", val); + else if (strcmp(name, "pipe_out") == 0) + return varnish_submit_derive(conf->instance, "vbe", + "total_bytes", "pipe_out", val); + else if (strcmp(name, "pipe_in") == 0) + return varnish_submit_derive(conf->instance, "vbe", + "total_bytes", "pipe_in", val); + else if (strcmp(name, "conn") == 0) + return varnish_submit_derive(conf->instance, "vbe", "connections", + "c_conns", val); + else if (strcmp(name, "req") == 0) + return varnish_submit_derive(conf->instance, "vbe", "http_requests", + "b_reqs", val); + } + + /* All Stevedores support these counters */ + if (conf->collect_sma || conf->collect_smf || conf->collect_mse) { + + char category[4]; + if (conf->collect_sma) + strncpy(category, "sma", 4); + else if (conf->collect_smf) + strncpy(category, "smf", 4); + else + strncpy(category, "mse", 4); + + if (strcmp(name, "c_req") == 0) + return varnish_submit_derive(conf->instance, category, + "total_operations", "alloc_req", val); + else if (strcmp(name, "c_fail") == 0) + return varnish_submit_derive(conf->instance, category, + "total_operations", "alloc_fail", val); + else if (strcmp(name, "c_bytes") == 0) + return varnish_submit_derive(conf->instance, category, + "total_bytes", "bytes_allocated", val); + else if (strcmp(name, "c_freed") == 0) + return varnish_submit_derive(conf->instance, category, + "total_bytes", "bytes_freed", val); + else if (strcmp(name, "g_alloc") == 0) + return varnish_submit_derive(conf->instance, category, + "total_operations", "alloc_outstanding", val); + else if (strcmp(name, "g_bytes") == 0) + return varnish_submit_gauge(conf->instance, category, "bytes", + "bytes_outstanding", val); + else if (strcmp(name, "g_space") == 0) + return varnish_submit_gauge(conf->instance, category, "bytes", + "bytes_available", val); + } + + /* No SMA specific counters */ + + if (conf->collect_smf) { + if (strcmp(name, "g_smf") == 0) + return varnish_submit_gauge(conf->instance, "smf", "objects", + "n_struct_smf", val); + else if (strcmp(name, "g_smf_frag") == 0) + return varnish_submit_gauge(conf->instance, "smf", "objects", + "n_small_free_smf", val); + else if (strcmp(name, "g_smf_large") == 0) + return varnish_submit_gauge(conf->instance, "smf", "objects", + "n_large_free_smf", val); + } + + if (conf->collect_mgt) { + if (strcmp(name, "uptime") == 0) + return varnish_submit_gauge(conf->instance, "mgt", "uptime", + "mgt_proc_uptime", val); + else if (strcmp(name, "child_start") == 0) + return varnish_submit_derive(conf->instance, "mgt", + "total_operations", "child_start", val); + else if (strcmp(name, "child_exit") == 0) + return varnish_submit_derive(conf->instance, "mgt", + "total_operations", "child_exit", val); + else if (strcmp(name, "child_stop") == 0) + return varnish_submit_derive(conf->instance, "mgt", + "total_operations", "child_stop", val); + else if (strcmp(name, "child_died") == 0) + return varnish_submit_derive(conf->instance, "mgt", + "total_operations", "child_died", val); + else if (strcmp(name, "child_dump") == 0) + return varnish_submit_derive(conf->instance, "mgt", + "total_operations", "child_dump", val); + else if (strcmp(name, "child_panic") == 0) + return varnish_submit_derive(conf->instance, "mgt", + "total_operations", "child_panic", val); + } + + if (conf->collect_lck) { + if (strcmp(name, "creat") == 0) + return varnish_submit_gauge(conf->instance, "lck", "objects", + "created", val); + else if (strcmp(name, "destroy") == 0) + return varnish_submit_gauge(conf->instance, "lck", "objects", + "destroyed", val); + else if (strcmp(name, "locks") == 0) + return varnish_submit_derive(conf->instance, "lck", "total_operations", + "lock_ops", val); + } + + if (conf->collect_mempool) { + if (strcmp(name, "live") == 0) + return varnish_submit_gauge(conf->instance, "mempool", "objects", + "in_use", val); + else if (strcmp(name, "pool") == 0) + return varnish_submit_gauge(conf->instance, "mempool", "objects", + "in_pool", val); + else if (strcmp(name, "sz_wanted") == 0) + return varnish_submit_gauge(conf->instance, "mempool", "bytes", + "size_requested", val); + else if (strcmp(name, "sz_actual") == 0) + return varnish_submit_gauge(conf->instance, "mempool", "bytes", + "size_allocated", val); + else if (strcmp(name, "allocs") == 0) + return varnish_submit_derive(conf->instance, "mempool", + "total_operations", "allocations", val); + else if (strcmp(name, "frees") == 0) + return varnish_submit_derive(conf->instance, "mempool", + "total_operations", "frees", val); + else if (strcmp(name, "recycle") == 0) + return varnish_submit_gauge(conf->instance, "mempool", + "objects", "recycled", val); + else if (strcmp(name, "timeout") == 0) + return varnish_submit_gauge(conf->instance, "mempool", + "objects", "timed_out", val); + else if (strcmp(name, "toosmall") == 0) + return varnish_submit_gauge(conf->instance, "mempool", + "objects", "too_small", val); + else if (strcmp(name, "surplus") == 0) + return varnish_submit_gauge(conf->instance, "mempool", + "objects", "surplus", val); + else if (strcmp(name, "randry") == 0) + return varnish_submit_gauge(conf->instance, "mempool", + "objects", "ran_dry", val); + } + + if (conf->collect_mse) { + if (strcmp(name, "c_full") == 0) + return varnish_submit_derive(conf->instance, "mse", + "total_operations", "full_allocs", val); + else if (strcmp(name, "c_truncated") == 0) + return varnish_submit_derive(conf->instance, "mse", + "total_operations", "truncated_allocs", val); + else if (strcmp(name, "c_expanded") == 0) + return varnish_submit_derive(conf->instance, "mse", + "total_operations", "expanded_allocs", val); + else if (strcmp(name, "c_failed") == 0) + return varnish_submit_derive(conf->instance, "mse", + "total_operations", "failed_allocs", val); + else if (strcmp(name, "c_bytes") == 0) + return varnish_submit_derive(conf->instance, "mse", + "total_bytes", "bytes_allocated", val); + else if (strcmp(name, "c_freed") == 0) + return varnish_submit_derive(conf->instance, "mse", + "total_bytes", "bytes_freed", val); + else if (strcmp(name, "g_fo_alloc") == 0) + return varnish_submit_derive(conf->instance, "mse", + "total_operations", "fo_allocs_outstanding", val); + else if (strcmp(name, "g_fo_bytes") == 0) + return varnish_submit_gauge(conf->instance, "mse", + "bytes", "fo_bytes_outstanding", val); + else if (strcmp(name, "g_membuf_alloc") == 0) + return varnish_submit_gauge(conf->instance, "mse", + "objects", "membufs_allocated", val); + else if (strcmp(name, "g_membuf_inuse") == 0) + return varnish_submit_gauge(conf->instance, "mse", + "objects", "membufs_inuse", val); + else if (strcmp(name, "g_bans_bytes") == 0) + return varnish_submit_gauge(conf->instance, "mse", + "bytes", "persisted_banspace_used", val); + else if (strcmp(name, "g_bans_space") == 0) + return varnish_submit_gauge(conf->instance, "mse", + "bytes", "persisted_banspace_available", val); + else if (strcmp(name, "g_bans_persisted") == 0) + return varnish_submit_derive(conf->instance, "mse", + "total_operations", "bans_persisted", val); + else if (strcmp(name, "g_bans_lost") == 0) + return varnish_submit_derive(conf->instance, "mse", + "total_operations", "bans_lost", val); + + /* mse seg */ + else if (strcmp(name, "g_journal_bytes") == 0) + return varnish_submit_gauge(conf->instance, "mse_reg", + "bytes", "journal_bytes_used", val); + else if (strcmp(name, "g_journal_space") == 0) + return varnish_submit_gauge(conf->instance, "mse_reg", + "bytes", "journal_bytes_free", val); + + /* mse segagg */ + else if (strcmp(name, "g_bigspace") == 0) + return varnish_submit_gauge(conf->instance, "mse_segagg", + "bytes", "big_extents_bytes_available", val); + else if (strcmp(name, "g_extfree") == 0) + return varnish_submit_gauge(conf->instance, "mse_segagg", + "objects", "free_extents", val); + else if (strcmp(name, "g_sparenode") == 0) + return varnish_submit_gauge(conf->instance, "mse_segagg", + "objects", "spare_nodes_available", val); + else if (strcmp(name, "g_objnode") == 0) + return varnish_submit_gauge(conf->instance, "mse_segagg", + "objects", "object_nodes_in_use", val); + else if (strcmp(name, "g_extnode") == 0) + return varnish_submit_gauge(conf->instance, "mse_segagg", + "objects", "extent_nodes_in_use", val); + else if (strcmp(name, "g_bigextfree") == 0) + return varnish_submit_gauge(conf->instance, "mse_segagg", + "objects", "free_big_extents", val); + else if (strcmp(name, "c_pruneloop") == 0) + return varnish_submit_derive(conf->instance, "mse_segagg", + "total_operations", "prune_loops", val); + else if (strcmp(name, "c_pruned") == 0) + return varnish_submit_derive(conf->instance, "mse_segagg", + "total_objects", "pruned_objects", val); + else if (strcmp(name, "c_spared") == 0) + return varnish_submit_derive(conf->instance, "mse_segagg", + "total_operations", "spared_objects", val); + else if (strcmp(name, "c_skipped") == 0) + return varnish_submit_derive(conf->instance, "mse_segagg", + "total_operations", "missed_objects", val); + else if (strcmp(name, "c_nuked") == 0) + return varnish_submit_derive(conf->instance, "mse_segagg", + "total_operations", "nuked_objects", val); + else if (strcmp(name, "c_sniped") == 0) + return varnish_submit_derive(conf->instance, "mse_segagg", + "total_operations", "sniped_objects", val); + + } + #endif return 0; @@ -1100,6 +1446,8 @@ static int varnish_config_apply_default(user_config_t *conf) /* {{{ */ conf->collect_shm = 1; #if HAVE_VARNISH_V2 conf->collect_sm = 0; +#endif +#if HAVE_VARNISH_V2 || HAVE_VARNISH_V4 conf->collect_sma = 0; #endif conf->collect_sms = 0; @@ -1112,6 +1460,12 @@ static int varnish_config_apply_default(user_config_t *conf) /* {{{ */ conf->collect_workers = 0; #if HAVE_VARNISH_V4 conf->collect_vsm = 0; + conf->collect_lck = 0; + conf->collect_mempool = 0; + conf->collect_mgt = 0; + conf->collect_smf = 0; + conf->collect_vbe = 0; + conf->collect_mse = 0; #endif return 0; @@ -1223,11 +1577,11 @@ static int varnish_config_instance(const oconfig_item_t *ci) /* {{{ */ else if (strcasecmp("CollectSMS", child->key) == 0) cf_util_get_boolean(child, &conf->collect_sms); else if (strcasecmp("CollectSMA", child->key) == 0) -#if HAVE_VARNISH_V2 +#if HAVE_VARNISH_V2 || HAVE_VARNISH_V4 cf_util_get_boolean(child, &conf->collect_sma); #else WARNING("Varnish plugin: \"%s\" is available for Varnish %s only.", - child->key, "v2"); + child->key, "v2 and v4"); #endif else if (strcasecmp("CollectSM", child->key) == 0) #if HAVE_VARNISH_V2 @@ -1258,6 +1612,55 @@ static int varnish_config_instance(const oconfig_item_t *ci) /* {{{ */ WARNING("Varnish plugin: \"%s\" is available for Varnish %s only.", child->key, "v4"); #endif + else if (strcasecmp("CollectLock", child->key) == 0) +#if HAVE_VARNISH_V4 + cf_util_get_boolean(child, &conf->collect_lck); +#else + WARNING("Varnish plugin: \"%s\" is available for Varnish %s only.", + child->key, "v4"); +#endif + else if (strcasecmp("CollectMempool", child->key) == 0) +#if HAVE_VARNISH_V4 + cf_util_get_boolean(child, &conf->collect_mempool); +#else + WARNING("Varnish plugin: \"%s\" is available for Varnish %s only.", + child->key, "v4"); +#endif + else if (strcasecmp("CollectManagement", child->key) == 0) +#if HAVE_VARNISH_V4 + cf_util_get_boolean(child, &conf->collect_mgt); +#else + WARNING("Varnish plugin: \"%s\" is available for Varnish %s only.", + child->key, "v4"); +#endif + else if (strcasecmp("CollectSMF", child->key) == 0) +#if HAVE_VARNISH_V4 + cf_util_get_boolean(child, &conf->collect_smf); +#else + WARNING("Varnish plugin: \"%s\" is available for Varnish %s only.", + child->key, "v4"); +#endif + else if (strcasecmp("CollectSMF", child->key) == 0) +#if HAVE_VARNISH_V4 + cf_util_get_boolean(child, &conf->collect_smf); +#else + WARNING("Varnish plugin: \"%s\" is available for Varnish %s only.", + child->key, "v4"); +#endif + else if (strcasecmp("CollectVBE", child->key) == 0) +#if HAVE_VARNISH_V4 + cf_util_get_boolean(child, &conf->collect_vbe); +#else + WARNING("Varnish plugin: \"%s\" is available for Varnish %s only.", + child->key, "v4"); +#endif + else if (strcasecmp("CollectMSE", child->key) == 0) +#if HAVE_VARNISH_V4 + cf_util_get_boolean(child, &conf->collect_mse); +#else + WARNING("Varnish plugin: \"%s\" is available for Varnish %s only.", + child->key, "Plus v4"); +#endif else { WARNING("Varnish plugin: Ignoring unknown " "configuration option: \"%s\". Did " @@ -1280,7 +1683,10 @@ static int varnish_config_instance(const oconfig_item_t *ci) /* {{{ */ #endif && !conf->collect_session && !conf->collect_shm && !conf->collect_sms #if HAVE_VARNISH_V2 - && !conf->collect_sma && !conf->collect_sm + && !conf->collect_sm +#endif +#if HAVE_VARNISH_V2 || HAVE_VARNISH_V4 + && !conf->collect_sma #endif && !conf->collect_struct && !conf->collect_totals #if HAVE_VARNISH_V3 || HAVE_VARNISH_V4 @@ -1288,7 +1694,9 @@ static int varnish_config_instance(const oconfig_item_t *ci) /* {{{ */ #endif && !conf->collect_vcl && !conf->collect_workers #if HAVE_VARNISH_V4 - && !conf->collect_vsm + && !conf->collect_vsm && !conf->collect_vbe && !conf->collect_smf + && !conf->collect_mgt && !conf->collect_lck && !conf->collect_mempool + && !conf->collect_mse #endif ) { WARNING("Varnish plugin: No metric has been configured for " diff --git a/src/write_http.c b/src/write_http.c index 06327edb..87e518b6 100644 --- a/src/write_http.c +++ b/src/write_http.c @@ -36,6 +36,10 @@ #define WRITE_HTTP_DEFAULT_BUFFER_SIZE 4096 #endif +#ifndef WRITE_HTTP_DEFAULT_PREFIX +#define WRITE_HTTP_DEFAULT_PREFIX "collectd" +#endif + /* * Private variables */ @@ -80,6 +84,7 @@ struct wh_callback_s { pthread_mutex_t send_lock; int data_ttl; + char *metrics_prefix; }; typedef struct wh_callback_s wh_callback_t; @@ -328,6 +333,7 @@ static void wh_callback_free(void *data) /* {{{ */ sfree(cb->clientcert); sfree(cb->clientkeypass); sfree(cb->send_buffer); + sfree(cb->metrics_prefix); sfree(cb); } /* }}} void wh_callback_free */ @@ -476,7 +482,7 @@ static int wh_write_kairosdb(const data_set_t *ds, status = format_kairosdb_value_list( cb->send_buffer, &cb->send_buffer_fill, &cb->send_buffer_free, ds, vl, cb->store_rates, (char const *const *)http_attrs, http_attrs_num, - cb->data_ttl); + cb->data_ttl, cb->metrics_prefix); if (status == -ENOMEM) { status = wh_flush_nolock(/* timeout = */ 0, cb); if (status != 0) { @@ -488,7 +494,7 @@ static int wh_write_kairosdb(const data_set_t *ds, status = format_kairosdb_value_list( cb->send_buffer, &cb->send_buffer_fill, &cb->send_buffer_free, ds, vl, cb->store_rates, (char const *const *)http_attrs, http_attrs_num, - cb->data_ttl); + cb->data_ttl, cb->metrics_prefix); } if (status != 0) { pthread_mutex_unlock(&cb->send_lock); @@ -629,6 +635,13 @@ static int wh_config_node(oconfig_item_t *ci) /* {{{ */ cb->send_metrics = 1; cb->send_notifications = 0; cb->data_ttl = 0; + cb->metrics_prefix = strdup(WRITE_HTTP_DEFAULT_PREFIX); + + if (cb->metrics_prefix == NULL) { + ERROR("write_http plugin: strdup failed."); + sfree(cb); + return -1; + } pthread_mutex_init(&cb->send_lock, /* attr = */ NULL); @@ -740,6 +753,8 @@ static int wh_config_node(oconfig_item_t *ci) /* {{{ */ sfree(val); } else if (strcasecmp("TTL", child->key) == 0) { status = cf_util_get_int(child, &cb->data_ttl); + } else if (strcasecmp("Prefix", child->key) == 0) { + status = cf_util_get_string(child, &cb->metrics_prefix); } else { ERROR("write_http plugin: Invalid configuration " "option: %s.", @@ -770,6 +785,9 @@ static int wh_config_node(oconfig_item_t *ci) /* {{{ */ return -1; } + if (strlen(cb->metrics_prefix) == 0) + sfree(cb->metrics_prefix); + if (cb->low_speed_limit > 0) cb->low_speed_time = CDTIME_T_TO_TIME_T(plugin_get_interval()); diff --git a/src/write_prometheus.c b/src/write_prometheus.c index eac0d68e..a005ffb2 100644 --- a/src/write_prometheus.c +++ b/src/write_prometheus.c @@ -36,6 +36,10 @@ #include +#include +#include +#include + #ifndef PROMETHEUS_DEFAULT_STALENESS_DELTA #define PROMETHEUS_DEFAULT_STALENESS_DELTA TIME_T_TO_CDTIME_T_STATIC(300) #endif @@ -727,6 +731,90 @@ metric_family_get(data_set_t const *ds, value_list_t const *vl, size_t ds_index, } /* }}} */ +#if MHD_VERSION >= 0x00090000 +static int prom_open_socket(int addrfamily) { + /* {{{ */ + char service[NI_MAXSERV]; + snprintf(service, sizeof(service), "%hu", httpd_port); + + struct addrinfo *res; + int status = getaddrinfo(NULL, service, + &(struct addrinfo){ + .ai_flags = AI_PASSIVE | AI_ADDRCONFIG, + .ai_family = addrfamily, + .ai_socktype = SOCK_STREAM, + }, + &res); + if (status != 0) { + return -1; + } + + int fd = -1; + for (struct addrinfo *ai = res; ai != NULL; ai = ai->ai_next) { + fd = socket(ai->ai_family, ai->ai_socktype | SOCK_CLOEXEC, 0); + if (fd == -1) + continue; + + if (bind(fd, ai->ai_addr, ai->ai_addrlen) != 0) { + close(fd); + fd = -1; + continue; + } + + if (listen(fd, /* backlog = */ 16) != 0) { + close(fd); + fd = -1; + continue; + } + + break; + } + + freeaddrinfo(res); + + return fd; +} /* }}} int prom_open_socket */ + +static struct MHD_Daemon *prom_start_daemon() { + /* {{{ */ + int fd = prom_open_socket(PF_INET6); + if (fd == -1) + fd = prom_open_socket(PF_INET); + if (fd == -1) { + ERROR("write_prometheus plugin: Opening a listening socket failed."); + return NULL; + } + + struct MHD_Daemon *d = + MHD_start_daemon(MHD_USE_THREAD_PER_CONNECTION, 0, + /* MHD_AcceptPolicyCallback = */ NULL, + /* MHD_AcceptPolicyCallback arg = */ NULL, http_handler, + NULL, MHD_OPTION_LISTEN_SOCKET, fd, MHD_OPTION_END); + if (d == NULL) { + ERROR("write_prometheus plugin: MHD_start_daemon() failed."); + close(fd); + return NULL; + } + + return d; +} /* }}} struct MHD_Daemon *prom_start_daemon */ +#else /* if MHD_VERSION < 0x00090000 */ +static struct MHD_Daemon *prom_start_daemon() { + /* {{{ */ + struct MHD_Daemon *d = + MHD_start_daemon(MHD_USE_THREAD_PER_CONNECTION, 0, + /* MHD_AcceptPolicyCallback = */ NULL, + /* MHD_AcceptPolicyCallback arg = */ NULL, http_handler, + NULL, MHD_OPTION_END); + if (d == NULL) { + ERROR("write_prometheus plugin: MHD_start_daemon() failed."); + return NULL; + } + + return d; +} /* }}} struct MHD_Daemon *prom_start_daemon */ +#endif + /* * collectd callbacks */ @@ -760,15 +848,7 @@ static int prom_init() { } if (httpd == NULL) { - unsigned int flags = MHD_USE_THREAD_PER_CONNECTION; -#if MHD_VERSION >= 0x00093300 - flags |= MHD_USE_DUAL_STACK; -#endif - - httpd = MHD_start_daemon(flags, httpd_port, - /* MHD_AcceptPolicyCallback = */ NULL, - /* MHD_AcceptPolicyCallback arg = */ NULL, - http_handler, NULL, MHD_OPTION_END); + httpd = prom_start_daemon(); if (httpd == NULL) { ERROR("write_prometheus plugin: MHD_start_daemon() failed."); return -1; diff --git a/src/write_redis.c b/src/write_redis.c index 5a029de2..7dd5029c 100644 --- a/src/write_redis.c +++ b/src/write_redis.c @@ -45,6 +45,7 @@ struct wr_node_s { char *prefix; int database; int max_set_size; + int max_set_duration; _Bool store_rates; redisContext *conn; @@ -125,6 +126,20 @@ static int wr_write(const data_set_t *ds, /* {{{ */ freeReplyObject(rr); } + if (node->max_set_duration > 0) { + /* + * remove element, scored less than 'current-max_set_duration' + * '(%d' indicates 'less than' in redis CLI. + */ + rr = redisCommand(node->conn, "ZREMRANGEBYSCORE %s -1 (%d", key, + (time - node->max_set_duration) + 1); + if (rr == NULL) + WARNING("ZREMRANGEBYSCORE command error. key:%s message:%s", key, + node->conn->errstr); + else + freeReplyObject(rr); + } + /* TODO(octo): This is more overhead than necessary. Use the cache and * metadata to determine if it is a new metric and call SADD only once for * each metric. */ @@ -175,6 +190,7 @@ static int wr_config_node(oconfig_item_t *ci) /* {{{ */ node->prefix = NULL; node->database = 0; node->max_set_size = -1; + node->max_set_duration = -1; node->store_rates = 1; pthread_mutex_init(&node->lock, /* attr = */ NULL); @@ -205,6 +221,8 @@ static int wr_config_node(oconfig_item_t *ci) /* {{{ */ status = cf_util_get_int(child, &node->database); } else if (strcasecmp("MaxSetSize", child->key) == 0) { status = cf_util_get_int(child, &node->max_set_size); + } else if (strcasecmp("MaxSetDuration", child->key) == 0) { + status = cf_util_get_int(child, &node->max_set_duration); } else if (strcasecmp("StoreRates", child->key) == 0) { status = cf_util_get_boolean(child, &node->store_rates); } else diff --git a/src/write_tsdb.c b/src/write_tsdb.c index 10f636c5..eb6ceb3f 100644 --- a/src/write_tsdb.c +++ b/src/write_tsdb.c @@ -111,7 +111,7 @@ static int wt_send_buffer(struct wt_callback *cb) { ssize_t status = 0; status = swrite(cb->sock_fd, cb->send_buf, strlen(cb->send_buf)); - if (status < 0) { + if (status != 0) { char errbuf[1024]; ERROR("write_tsdb plugin: send failed with status %zi (%s)", status, sstrerror(errno, errbuf, sizeof(errbuf))); diff --git a/src/zfs_arc.c b/src/zfs_arc.c index 730d3048..e589184c 100644 --- a/src/zfs_arc.c +++ b/src/zfs_arc.c @@ -35,6 +35,10 @@ /* * Global variables */ +static value_to_rate_state_t arc_hits_state; +static value_to_rate_state_t arc_misses_state; +static value_to_rate_state_t l2_hits_state; +static value_to_rate_state_t l2_misses_state; #if defined(KERNEL_LINUX) #include "utils_llist.h" @@ -313,14 +317,25 @@ static int za_read(void) { za_read_derive(ksp, "mru_hits", "cache_result", "mru-hit"); za_read_derive(ksp, "mru_ghost_hits", "cache_result", "mru_ghost-hit"); + cdtime_t now = cdtime(); + /* Ratios */ - arc_hits = (gauge_t)get_zfs_value(ksp, "hits"); - arc_misses = (gauge_t)get_zfs_value(ksp, "misses"); - l2_hits = (gauge_t)get_zfs_value(ksp, "l2_hits"); - l2_misses = (gauge_t)get_zfs_value(ksp, "l2_misses"); + if ((value_to_rate(&arc_hits, (value_t){.derive = get_zfs_value(ksp, "hits")}, + DS_TYPE_DERIVE, now, &arc_hits_state) == 0) && + (value_to_rate(&arc_misses, + (value_t){.derive = get_zfs_value(ksp, "misses")}, + DS_TYPE_DERIVE, now, &arc_misses_state) == 0)) { + za_submit_ratio("arc", arc_hits, arc_misses); + } - za_submit_ratio("arc", arc_hits, arc_misses); - za_submit_ratio("L2", l2_hits, l2_misses); + if ((value_to_rate(&l2_hits, + (value_t){.derive = get_zfs_value(ksp, "l2_hits")}, + DS_TYPE_DERIVE, now, &l2_hits_state) == 0) && + (value_to_rate(&l2_misses, + (value_t){.derive = get_zfs_value(ksp, "l2_misses")}, + DS_TYPE_DERIVE, now, &l2_misses_state) == 0)) { + za_submit_ratio("L2", l2_hits, l2_misses); + } /* I/O */ value_t l2_io[] = {