Merge branch 'collectd-5.6' into collectd-5.7
authorRuben Kerkhof <ruben@rubenkerkhof.com>
Fri, 7 Jul 2017 16:40:55 +0000 (18:40 +0200)
committerRuben Kerkhof <ruben@rubenkerkhof.com>
Fri, 7 Jul 2017 16:40:55 +0000 (18:40 +0200)
211 files changed:
AUTHORS
CONTRIBUTING.md
ChangeLog
NEWS [deleted file]
README
TODO [deleted file]
bindings/perl/lib/Collectd.pm
bindings/perl/lib/Collectd/MockDaemon.pm
bindings/perl/lib/Collectd/Unixsock.pm
bindings/perl/t/01_methods.t
configure.ac
contrib/SpamAssassin/Collectd.pm
contrib/examples/myplugin.c
contrib/redhat/collectd.spec
docs/BUILD.dpdkstat.md [new file with mode: 0644]
docs/BUILD.java.md [new file with mode: 0644]
proto/Makefile.am
proto/collectd.proto
proto/prometheus.proto [new file with mode: 0644]
src/Makefile.am
src/aggregation.c
src/amqp.c
src/apache.c
src/apcups.c
src/apple_sensors.c
src/aquaero.c
src/ascent.c
src/barometer.c
src/battery.c
src/battery_statefs.c [new file with mode: 0644]
src/bind.c
src/ceph.c
src/cgroups.c
src/chrony.c
src/collectd-perl.pod
src/collectd.conf.in
src/collectd.conf.pod
src/conntrack.c
src/contextswitch.c
src/cpu.c
src/cpufreq.c
src/cpusleep.c
src/curl.c
src/curl_json.c
src/curl_xml.c
src/daemon/Makefile.am
src/daemon/collectd.c
src/daemon/common.c
src/daemon/common.h
src/daemon/common_test.c
src/daemon/meta_data.c
src/daemon/meta_data.h
src/daemon/plugin.c
src/daemon/plugin.h
src/daemon/plugin_mock.c
src/daemon/utils_cache.c
src/daemon/utils_cache.h
src/daemon/utils_cache_mock.c
src/daemon/utils_match.c [deleted file]
src/daemon/utils_match.h [deleted file]
src/daemon/utils_tail.c [deleted file]
src/daemon/utils_tail.h [deleted file]
src/daemon/utils_tail_match.c [deleted file]
src/daemon/utils_tail_match.h [deleted file]
src/daemon/utils_time.c
src/daemon/utils_time.h
src/daemon/utils_time_test.c
src/dbi.c
src/df.c
src/disk.c
src/dns.c
src/dpdkstat.c [new file with mode: 0644]
src/drbd.c
src/email.c
src/entropy.c
src/ethstat.c
src/exec.c
src/fhcount.c
src/filecount.c
src/fscache.c
src/gmond.c
src/gps.c
src/grpc.cc
src/hddtemp.c
src/hugepages.c [new file with mode: 0644]
src/intel_rdt.c [new file with mode: 0644]
src/interface.c
src/ipc.c
src/ipmi.c
src/iptables.c
src/ipvs.c
src/irq.c
src/java.c
src/load.c
src/log_logstash.c
src/logfile.c
src/lpar.c
src/lua.c
src/lvm.c
src/madwifi.c
src/match_regex.c
src/mbmon.c
src/md.c
src/memcachec.c
src/memcached.c
src/memory.c
src/mic.c
src/modbus.c
src/mqtt.c
src/multimeter.c
src/mysql.c
src/netapp.c
src/netlink.c
src/network.c
src/nfs.c
src/nginx.c
src/notify_email.c
src/ntpd.c
src/numa.c
src/nut.c
src/olsrd.c
src/onewire.c
src/openldap.c
src/openvpn.c
src/perl.c
src/pf.c
src/pinba.c
src/ping.c
src/postgresql.c
src/powerdns.c
src/processes.c
src/protocols.c
src/python.c
src/redis.c
src/routeros.c
src/rrdcached.c
src/rrdtool.c
src/sensors.c
src/serial.c
src/sigrok.c
src/smart.c
src/snmp.c
src/statsd.c
src/swap.c
src/table.c
src/tail.c
src/tail_csv.c
src/tape.c
src/target_replace.c
src/target_set.c
src/target_v5upgrade.c
src/tcpconns.c
src/teamspeak2.c
src/ted.c
src/testing.h
src/thermal.c
src/tokyotyrant.c
src/turbostat.c
src/types.db
src/unixsock.c
src/uptime.c
src/users.c
src/utils_cmd_flush.c
src/utils_cmd_flush.h
src/utils_cmd_getthreshold.c
src/utils_cmd_getval.c
src/utils_cmd_getval.h
src/utils_cmd_listval.c
src/utils_cmd_listval.h
src/utils_cmd_putval.c
src/utils_cmd_putval.h
src/utils_cmds.c [new file with mode: 0644]
src/utils_cmds.h [new file with mode: 0644]
src/utils_cmds_test.c [new file with mode: 0644]
src/utils_format_graphite.c
src/utils_format_graphite.h
src/utils_format_graphite_test.c [new file with mode: 0644]
src/utils_latency.c
src/utils_latency.h
src/utils_latency_config.c [new file with mode: 0644]
src/utils_latency_config.h [new file with mode: 0644]
src/utils_latency_test.c
src/utils_match.c [new file with mode: 0644]
src/utils_match.h [new file with mode: 0644]
src/utils_mount.c
src/utils_tail.c [new file with mode: 0644]
src/utils_tail.h [new file with mode: 0644]
src/utils_tail_match.c [new file with mode: 0644]
src/utils_tail_match.h [new file with mode: 0644]
src/utils_vl_lookup.c
src/utils_vl_lookup.h
src/utils_vl_lookup_test.c
src/valgrind.FreeBSD.suppress
src/varnish.c
src/virt.c
src/vmem.c
src/vserver.c
src/wireless.c
src/write_graphite.c
src/write_http.c
src/write_kafka.c
src/write_log.c
src/write_mongodb.c
src/write_prometheus.c [new file with mode: 0644]
src/write_redis.c
src/xencpu.c
src/xmms.c
src/zfs_arc.c
src/zone.c
src/zookeeper.c
version-gen.sh

diff --git a/AUTHORS b/AUTHORS
index 28220e7..8962e77 100644 (file)
--- a/AUTHORS
+++ b/AUTHORS
@@ -72,7 +72,7 @@ Aurélien Reynaud <collectd at wattapower.net>
  - LPAR plugin.
  - Various fixes for AIX, HP-UX and Solaris.
 
-Benjamin Gilbert <bgilbert at cs.cmu.edu>
+Benjamin Gilbert <bgilbert at backtick.net>
  - Improvements to the LVM plugin.
 
 Bert Vermeulen <bert at biot.com>
index eeb174b..791446a 100644 (file)
@@ -2,7 +2,7 @@
 
 Thanks for taking the time to contribute to the [collectd
 project](https://collectd.org/)! This document tries to give some guidance to
-make the process of contributing to *collectd* as pleasant and possible.
+make the process of contributing to *collectd* as pleasant as possible.
 
 ## Bug reports
 
@@ -19,7 +19,7 @@ following questions:
     [stack trace](https://collectd.org/wiki/index.php/Core_file).
 
 Please monitor your issue for a couple of days and reply to questions. To keep
-the project manageable have to do some housekeeping, meaning we will close
+the project manageable, we have to do some housekeeping; meaning we will close
 issues that have become stale.
 
 ## Code contributions
@@ -35,7 +35,7 @@ the mailing list have a tendency to fall through the cracks.
     coding style of the code around your changes.
 *   *Documentation:* New config options need to be documented in two places: the
     manpage (`src/collectd.conf.pod`) and the example config
-    (`src/collectd.conf.in`).
+    (`src/collectd.conf.in`). New plugins need to be added to the `README` file.
 *   *Continuous integration:* Once your PR is created, our continuous
     integration environment will try to build it on a number of platforms. If
     this reports a failure, please investigate and fix the problem. We will at
index e42b231..b8755d9 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,139 @@
+2017-06-06, Version 5.7.2
+       * Build system: The Notify Email plugin is no longer linked with
+         indirect dependencies. Thanks to Marc Fournier.
+       * collectd: A race condition when calculating a metric's rate has been
+         fixed. Thanks to Florian Forster. #1193
+       * AMQP, Exec, UnixSock, Write Kafka plugins: Parsing of the PUTVAL
+         command with multiple values has been fixed. Thanks to Florian
+         Forster. #2274
+       * AMQP plugin: The "ExchangeType" option is now also valid for
+         publishers. Thanks to Florian Forster. #2286
+       * BIND plugin: Fix parsing of the sample time provided by BIND.
+         Previously, the time was assumed to be in the local timezone when in
+         fact it was in UTC. Thanks to Ed Ravin. #1268
+       * BIND plugin: Memory leaks have been fixed. Thanks to Ruben Kerkhof.
+         #2303
+       * cURL-JSON plugin: Handling of arrays has been fixed. Thanks to Florian
+         Forster. #2266
+       * DPDKStat plugin: Error handling during initialization has been
+         improved. Thanks to Ruben Kerkhof.
+       * DPDKStat plugin: Handling of a number of metrics has been improved,
+         for example "rx_q0bytes". Thanks to Przemyslaw Szczerbik. #2167
+       * Intel RDT plugin: Configuration handling has been changed to be more
+         graceful. Thanks to Maryam Tahhan. #2165
+       * Log Logstash plugin: If writing the log entry fails, print it to
+         "STDERR" instead. Thanks to Marc Fournier.
+       * LogFile plugin: If writing to the file fails, print log messages on
+         "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
+       * Perl plugin: A potential double-free has been fixed. Thanks to Florian
+         Forster. #2278
+       * Processes plugin: A compilation error on AIX has been fixed. Thanks to
+         Pavel Rochnyack. #2210
+       * SMART plugin: A check for the "CAP_SYS_RAWIO" capability has been
+         added. Thanks to Marc Fournier.
+       * 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 Prometheus plugin: An incorrect use of "realloc(3)" has been
+         fixed. Thanks to Florian Forster. #2275
+
+2017-01-23, Version 5.7.1
+       * collectd: Handling of boolean configuration options has been unified.
+         Thanks to Sebastian Harl. #2083, #2098
+       * collectd: Reporting of internal statistics has been fixed. Thanks to
+         Florian Forster. #2108
+       * collectd, various plugins: Bugs and issues reported by scan-build and
+         coverity-scan have been fixed. Thanks to Ruben Kerkhof and Florian
+         Forster.
+       * Build system: Parallel build have been fixed. Thanks to Ruben Kerkhof.
+         #2110
+       * DPDKStat plugin: Portability issues and a double-close bug have been
+         fixed. Thanks to Ruben Kerkhof and Marc Fournier.
+       * Intel RDT plugin: A check for the libpqos library version has been
+         added. Thanks to Serhiy Pshyk.
+       * NetApp plugin: Compilation problems have been corrected. Thanks to
+         Florian Forster. #2120
+       * Write Prometheus plugin: A memory leak has been fixed. Thanks to Ruben
+         Kerkhof.
+
+2016-12-12, Version 5.7.0
+       * Documentation: The Turbostat plugin section has been improved. Thanks
+         to Florian Forster
+       * Documentation: The semantics of the "TypesDB" option have been
+         improved. Thanks to Florian Forster.
+       * collectd: A generic interface for parsing the text protocol has been
+         added. Thanks to Sebastian Harl. #1749
+       * collectd: Threads now get named, making them easier to track using
+         tools such as top, ps, etc. Thanks to Manuel Luis Sanmartín Rozada
+         and Marc Fournier. #547
+       * AMQP plugin, Write Graphite plugin, Write Kafka plugin: The new
+         "[Graphite]PreserveSeparator" option allows retaining the default dot
+         separator rather than escaping it. Thanks to Florian Forster. #419
+       * Battery plugin: A StateFS backend for gathering statistics has been
+         added. Thanks to Rinigus. #1795
+       * CPU plugin: CPU aggregation on AIX was fixed. Thanks to Chao Yang.
+         #1957
+       * Collectd::Unixsock: Fractional seconds support has been made more
+         robust. Thanks to Matthias Bethke. #2052
+       * DPDKStat plugin: This new plugin collects DPDK interface statistics.
+         Thanks to Maryam Tahhan, Harry van Haaren, Taras Chornyi and Kim
+         Jones. #1649
+       * gRPC plugin: The "DispatchValues" option has been renamed to
+         "PutValues". Thanks to Florian Forster.
+       * HDDTemp plugin: The 32 devices limit has been removed. Thanks to
+         Benjamin Gilbert. #631
+       * Hugepages plugin: This new plugin reports the number of used and free
+         hugepages on Linux. Thanks to Jaroslav Safka, Maryam Tahhan, Kim Jones
+         and Florian Forster. #1799
+       * Intel RDT plugin: This new plugin collects statistics exposed by
+         Intel's Resource Director Technology . Thanks to SerhiyX. #1970
+       * memcached plugin: The new "Address" option allows connecting to a
+         different server than specified by the "Host" option. Thanks to Pavel
+         Rochnyack. #1975
+       * nginx plugin: Support for reporting failed connections has been added.
+         Thanks to Pavel Rochnyack. #1609
+       * Perl plugin: Significant internal reworking has been made. The new
+         "RegisterLegacyFlush" option has been added. Thanks to Pavel
+         Rochnyack. #1731
+       * PostgreSQL plugin: Timestamps are now RFC 3339-formatted local time.
+         Thanks to Igor Peshansky and Dave Cunningham. #1918
+       * Processes plugin: Internal performance improvements have been made.
+         Thanks to Pavel Rochnyack. #1980, #1981
+       * RRDCacheD plugin: The plugin now tries to reconnect upon failed
+         operations. Thanks to Sebastian Harl. #1959
+       * SpamAssassin: The plugin can now run in Perl's "tainted mode" ("-T").
+         Thanks to Akos Vandra. #1962
+       * Tail plugin: Support for calculating latency distribution of matching
+         values has been added. Thanks to Pavel Rochnyack and Florian Forster.
+         #1700
+       * Tail plugin: The new "GaugePersist" option has been added. Thanks to
+         Florian Forster. #2015
+       * Target:Set, Target:Replace, Match:RegEx: MetaData support has been
+         added . Thanks to Igor Peshansky. #1922, #1923, #1930
+       * turbostat plugin: The new "LogicalCoreNames" option allows switching
+         to per-core naming rather than per-CPU. Thanks to Brock Johnson. #2056
+       * virt plugin: The new "BlockDeviceFormat" and
+         "BlockDeviceFormatBasename" options help controlling the names
+         reported for block-device metrics. Thanks to Deyan Chepishev. #2004
+       * Write Graphite plugin: A new "DropDuplicateFields" option has been
+         added. Thanks to Michael Leinartas. #1915
+       * Write Kafka plugin: The "Key Random" setting has been reintroduced.
+         Thanks to Florian Forster. #1977
+       * Write Log plugin: Support for formatting output in JSON has been
+         added, using the new "Format" configuration option. Thanks to Igor
+         Peshansky. #1924
+       * Write Prometheus plugin: This new plugin publishes values using an
+         embedded HTTP server, in a format compatible with Prometheus'
+         collectd_exporter. Thanks to Florian Forster. #1967
+
 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/NEWS b/NEWS
deleted file mode 100644 (file)
index e69de29..0000000
diff --git a/README b/README
index ee909d6..803fbaa 100644 (file)
--- a/README
+++ b/README
@@ -96,6 +96,10 @@ Features
       DNS traffic: Query types, response codes, opcodes and traffic/octets
       transferred.
 
+    - dpdkstat
+      Collect DPDK interface statistics.
+      See docs/BUILD.dpdkstat.md for detailed build instructions.
+
     - drbd
       Collect individual drbd resource statistics.
 
@@ -128,12 +132,23 @@ Features
     - gps
       Monitor gps related data through gpsd.
 
-    - grpc
-      Receive values over the network using the gRPC framework.
-
     - hddtemp
       Hard disk temperatures using hddtempd.
 
+    - hugepages
+      Report the number of used and free hugepages. More info on
+      hugepages can be found here:
+      https://www.kernel.org/doc/Documentation/vm/hugetlbpage.txt.
+
+    - intel_rdt
+      The intel_rdt plugin collects information provided by monitoring features
+      of Intel Resource Director Technology (Intel(R) RDT) like Cache Monitoring
+      Technology (CMT), Memory Bandwidth Monitoring (MBM). These features
+      provide information about utilization of shared resources like last level
+      cache occupancy, local memory bandwidth usage, remote memory bandwidth
+      usage, instructions per clock.
+      <https://01.org/packet-processing/cache-monitoring-technology-memory-bandwidth-monitoring-cache-allocation-technology-code-and-data>
+
     - interface
       Interface traffic: Number of octets, packets and errors for each
       interface.
@@ -159,7 +174,8 @@ Features
 
     - java
       Integrates a `Java Virtual Machine' (JVM) to execute plugins in Java
-      bytecode. See “Configuring with libjvm” below.
+      bytecode.
+      See docs/BUILD.java.md for detailed build instructions.
 
     - load
       System load average over the last 1, 5 and 15 minutes.
@@ -182,14 +198,14 @@ Features
       Queries very detailed usage statistics from wireless LAN adapters and
       interfaces that use the Atheros chipset and the MadWifi driver.
 
-    - mbmon
-      Motherboard sensors: temperature, fan speed and voltage information,
-      using mbmon(1).
-
     - md
       Linux software-RAID device information (number of active, failed, spare
       and missing disks).
 
+    - mbmon
+      Motherboard sensors: temperature, fan speed and voltage information,
+      using mbmon(1).
+
     - memcachec
       Query and parse data from a memcache daemon (memcached).
 
@@ -209,9 +225,6 @@ Features
       Reads values from Modbus/TCP enabled devices. Supports reading values
       from multiple "slaves" so gateway devices can be used.
 
-    - mqtt
-      Publishes and subscribes to MQTT topics.
-
     - multimeter
       Information provided by serial multimeters, such as the `Metex
       M-4650CR'.
@@ -390,7 +403,7 @@ Features
       CPU, memory, disk and network I/O statistics from virtual machines.
 
     - vmem
-      Virtual memory statistics, e. g. the number of page-ins/-outs or the
+      Virtual memory statistics, e.g. the number of page-ins/-outs or the
       number of pagefaults.
 
     - vserver
@@ -428,10 +441,16 @@ Features
       diskspace but is extremely portable and can be analysed with almost
       every program that can analyse anything. Even Microsoft's Excel..
 
+    - grpc
+      Send and receive values over the network using the gRPC framework.
+
     - lua
       It's possible to implement write plugins in Lua using the Lua
       plugin. See collectd-lua(5) for details.
 
+    - mqtt
+      Publishes and subscribes to MQTT topics.
+
     - network
       Send the data to a remote host to save the data somehow. This is useful
       for large setups where the data should be saved by a dedicated machine.
@@ -481,6 +500,10 @@ Features
     - write_mongodb
       Sends data to MongoDB, a NoSQL database.
 
+    - write_prometheus
+      Publish values using an embedded HTTP server, in a format compatible
+      with Prometheus' collectd_exporter.
+
     - write_redis
       Sends the values to a Redis key-value database server.
 
@@ -651,12 +674,22 @@ Prerequisites
 
   * Usual suspects: C compiler, linker, preprocessor, make, ...
 
+    collectd makes use of some common C99 features, e.g. compound literals and
+    mixed declarations, and therefore requires a C99 compatible compiler.
+
+    On Debian and Ubuntu, the "build-essential" package should pull in
+    everything that's necessary.
+
   * A POSIX-threads (pthread) implementation.
     Since gathering some statistics is slow (network connections, slow devices,
     etc) collectd is parallelized. The POSIX threads interface is being
     used and should be found in various implementations for hopefully all
     platforms.
 
+  * When building from the Git repository, flex (tokenizer) and bison (parser
+    generator) are required. Release tarballs include the generated files – you
+    don't need these packages in that case.
+
   * aerotools-ng (optional)
     Used by the `aquaero' plugin. Currently, the `libaquaero5' library, which
     is used by the `aerotools-ng' toolkit, is not compiled as a shared object
@@ -731,8 +764,8 @@ Prerequisites
 
   * libjvm (optional)
     Library that encapsulates the `Java Virtual Machine' (JVM). This library is
-    used by the `java' plugin to execute Java bytecode. See “Configuring with
-    libjvm” below.
+    used by the `java' plugin to execute Java bytecode.
+    See docs/BUILD.java.md for detailed build instructions.
     <http://openjdk.java.net/> (and others)
 
   * libldap (optional)
@@ -751,6 +784,10 @@ Prerequisites
     Used by the `memcachec' plugin to connect to a memcache daemon.
     <http://tangent.org/552/libmemcached.html>
 
+  * libmicrohttpd (optional)
+    Used by the write_prometheus plugin to run an http daemon.
+    <http://www.gnu.org/software/libmicrohttpd/>
+
   * libmnl (optional)
     Used by the `netlink' plugin.
     <http://www.netfilter.org/projects/libmnl/>
@@ -806,6 +843,11 @@ Prerequisites
     The PostgreSQL C client library used by the `postgresql' plugin.
     <http://www.postgresql.org/>
 
+  * libpqos (optional)
+    The PQoS library for Intel(R) Resource Director Technology used by the
+    `intel_rdt' plugin.
+    <https://github.com/01org/intel-cmt-cat>
+
   * libprotobuf, protoc 3.0+ (optional)
     Used by the `grpc' plugin to generate service stubs and code to handle
     network packets of collectd's protobuf-based network protocol.
@@ -926,44 +968,6 @@ Configuring / Compiling / Installing
   prefixed to all installation directories. This might be useful when creating
   packages for collectd.
 
-Configuring with libjvm
------------------------
-
-  To determine the location of the required files of a Java installation is not
-  an easy task, because the locations vary with your kernel (Linux, SunOS, …)
-  and with your architecture (x86, SPARC, …) and there is no ‘java-config’
-  script we could use. Configuration of the JVM library is therefore a bit
-  tricky.
-
-  The easiest way to use the `--with-java=$JAVA_HOME' option, where
-  `$JAVA_HOME' is usually something like:
-    /usr/lib/jvm/java-1.5.0-sun-1.5.0.14
-
-  The configure script will then use find(1) to look for the following files:
-
-    - jni.h
-    - jni_md.h
-    - libjvm.so
-
-  If found, appropriate CPP-flags and LD-flags are set and the following
-  library checks succeed.
-
-  If this doesn't work for you, you have the possibility to specify CPP-flags,
-  C-flags, LD-flags and LIBS for the ‘Java’ plugin by hand, using the
-  following environment variables:
-
-    - JAVA_CPPFLAGS
-    - JAVA_CFLAGS
-    - JAVA_LDFLAGS
-    - JAVA_LIBS
-
-  For example (shortened for demonstration purposes):
-
-    ./configure JAVA_CPPFLAGS="-I$JAVA_HOME/include -I$JAVA_HOME/include/linux"
-
-  Adding "-ljvm" to JAVA_LIBS is done automatically, you don't have to
-  do that.
-
 Generating the configure script
 -------------------------------
 
@@ -982,6 +986,7 @@ To generate the `configure` script, you'll need the following dependencies:
 
 The `build.sh' script takes no arguments.
 
+
 Crosscompiling
 --------------
 
@@ -1016,8 +1021,12 @@ Crosscompiling
 Contact
 -------
 
-  For questions, bug reports, development information and basically all other
-  concerns please send an email to collectd's mailing list at
+  Please use GitHub to report bugs and submit pull requests:
+  <https://github.com/collectd/collectd/>.
+  See CONTRIBUTING.md for details.
+
+  For questions, development information and basically all other concerns please
+  send an email to collectd's mailing list at
   <list at collectd.org>.
 
   For live discussion and more personal contact visit us in IRC, we're in
@@ -1031,5 +1040,3 @@ Author
   Sebastian tokkee Harl <sh at tokkee.org>,
   and many contributors (see `AUTHORS').
 
-  Please use GitHub reporting bugs and submitting pull requests.
-  See CONTRIBUTING.md for details.
diff --git a/TODO b/TODO
deleted file mode 100644 (file)
index 009eb7f..0000000
--- a/TODO
+++ /dev/null
@@ -1,21 +0,0 @@
-* Finalize the onewire plugin.
-* Custom notification messages?
-* Implement moving-average calculation for the threshold stuff.
-
-src/battery.c: commend not working code.
-
-Wishlist:
-* Port nfs module to solaris
-* Port tape module to Linux
-* Port the apple_sensors plugin to Linux/PPC.
-* Maybe look into porting the serial module
-* Build Darwin package
-* Maybe let the network plugin configure whether or not notifications should be
-  sent/received.
-* Maybe find a way for processes connected to the unixsock plugin to receive
-  notifications, too.
-
-http://developer.apple.com/documentation/DeviceDrivers/Conceptual/AccessingHardware/AH_IOKitLib_API/chapter_5_section_1.html
-http://developer.apple.com/documentation/DeviceDrivers/Conceptual/IOKitFundamentals/index.html#//apple_ref/doc/uid/TP0000011
-http://www.gauchosoft.com/Software/X%20Resource%20Graph/
-http://johannes.sipsolutions.net/PowerBook/Apple_Motion_Sensor_Specification/
index c1adf44..7e89e45 100644 (file)
@@ -172,7 +172,6 @@ sub plugin_call_all {
        my $type = shift;
 
        my %plugins;
-       my $interval;
 
        our $cb_name = undef;
 
@@ -181,13 +180,13 @@ sub plugin_call_all {
        }
 
        if (TYPE_LOG != $type) {
-               DEBUG ("Collectd::plugin_call: type = \"$type\" ("
+               DEBUG ("Collectd::plugin_call_all: type = \"$type\" ("
                        . $types{$type} . "), args=\""
                        . join(', ', map { defined($_) ? $_ : '<undef>' } @_) . "\"");
        }
 
        if (! defined $plugins[$type]) {
-               ERROR ("Collectd::plugin_call: unknown type \"$type\"");
+               ERROR ("Collectd::plugin_call_all: unknown type \"$type\"");
                return;
        }
 
@@ -196,21 +195,9 @@ sub plugin_call_all {
                %plugins = %{$plugins[$type]};
        }
 
-       $interval = plugin_get_interval ();
-
        foreach my $plugin (keys %plugins) {
-               my $p = $plugins{$plugin};
-
-               my $status = 0;
-
-               if ($p->{'wait_left'} > 0) {
-                       $p->{'wait_left'} -= $interval;
-               }
-
-               next if ($p->{'wait_left'} > 0);
-
-               $cb_name = $p->{'cb_name'};
-               $status = call_by_name (@_);
+               $cb_name = $plugins{$plugin};
+               my $status = call_by_name (@_);
 
                if (! $status) {
                        my $err = undef;
@@ -230,23 +217,7 @@ sub plugin_call_all {
                }
 
                if ($status) {
-                       $p->{'wait_left'} = 0;
-                       $p->{'wait_time'} = $interval;
-               }
-               elsif (TYPE_READ == $type) {
-                       if ($p->{'wait_time'} < $interval) {
-                               $p->{'wait_time'} = $interval;
-                       }
-
-                       $p->{'wait_left'} = $p->{'wait_time'};
-                       $p->{'wait_time'} *= 2;
-
-                       if ($p->{'wait_time'} > 86400) {
-                               $p->{'wait_time'} = 86400;
-                       }
-
-                       WARNING ("${plugin}->read() failed with status $status. "
-                               . "Will suspend it for $p->{'wait_left'} seconds.");
+                       #NOOP
                }
                elsif (TYPE_INIT == $type) {
                        ERROR ("${plugin}->init() failed with status $status. "
@@ -309,21 +280,29 @@ sub plugin_register {
        }
        elsif ((TYPE_DATASET != $type) && (! ref $data)) {
                my $pkg = scalar caller;
-
-               my %p : shared;
-
                if ($data !~ m/^$pkg\:\:/) {
                        $data = $pkg . "::" . $data;
                }
-
-               %p = (
-                       wait_time => plugin_get_interval (),
-                       wait_left => 0,
-                       cb_name   => $data,
-               );
-
+               if (TYPE_READ == $type) {
+                       return plugin_register_read($name, $data);
+               }
+               if (TYPE_WRITE == $type) {
+                       return plugin_register_write($name, $data);
+               }
+               if (TYPE_LOG == $type) {
+                       return plugin_register_log($name, $data);
+               }
+               if (TYPE_NOTIF == $type) {
+                       return plugin_register_notification($name, $data);
+               }
+               if (TYPE_FLUSH == $type) {
+                       #For collectd-5.6 only
+                       lock %{$plugins[$type]};
+                       $plugins[$type]->{$name} = $data;
+                       return plugin_register_flush($name, $data);
+               }
                lock %{$plugins[$type]};
-               $plugins[$type]->{$name} = \%p;
+               $plugins[$type]->{$name} = $data;
        }
        else {
                ERROR ("Collectd::plugin_register: Invalid data.");
@@ -351,6 +330,21 @@ sub plugin_unregister {
                lock %cf_callbacks;
                delete $cf_callbacks{$name};
        }
+       elsif (TYPE_READ == $type) {
+               return plugin_unregister_read ($name);
+       }
+       elsif (TYPE_WRITE == $type) {
+               return plugin_unregister_write($name);
+       }
+       elsif (TYPE_LOG == $type) {
+               return plugin_unregister_log ($name);
+       }
+       elsif (TYPE_NOTIF == $type) {
+               return plugin_unregister_notification($name);
+       }
+       elsif (TYPE_FLUSH == $type) {
+               return plugin_unregister_flush($name);
+       }
        elsif (defined $plugins[$type]) {
                lock %{$plugins[$type]};
                delete $plugins[$type]->{$name};
index 381b2d1..300d2c4 100644 (file)
@@ -55,9 +55,20 @@ sub daemon {
 }
 
 sub listval {
-    my $now = time;
+    my @timevals = (
+        1479835353.75,
+        1479835354.434,
+        1479835356,
+        1479835354,
+        1479835354,
+        1479835350.820,
+        1479835351,
+        1479835354.2,
+        1479835353,
+    );
+    my $i = 0;
     return print_nvalues(scalar @metrics) .
-    join('', map { "$now $_\n" } @metrics);
+    join('', map { $timevals[$i++ % @timevals] . " $_\n" } @metrics);
 }
 
 sub getval {
index 304bbbc..f9db922 100644 (file)
@@ -137,7 +137,7 @@ sub _parse_identifier
 
 sub _escape_argument
 {
-    my $arg = shift;
+       my $arg = shift;
 
        return $arg if $arg =~ /^\w+$/;
 
@@ -153,19 +153,19 @@ sub _socket_command {
 
        my $fh = $self->{sock} or confess ('object has no filehandle');
 
-    if($args) {
-        my $identifier = _create_identifier ($args) or return;
-           $command .= ' ' . _escape_argument ($identifier) . "\n";
-    } else {
-        $command .= "\n";
-    }
+       if($args) {
+               my $identifier = _create_identifier ($args) or return;
+               $command .= ' ' . _escape_argument ($identifier) . "\n";
+       } else {
+               $command .= "\n";
+       }
        _debug "-> $command";
        $fh->print($command);
 
        my $response = $fh->getline;
        chomp $response;
        _debug "<- $response\n";
-    return $response;
+       return $response;
 }
 
 # Read any remaining results from a socket and pass them to
@@ -188,7 +188,7 @@ sub _socket_chat
                my $entry = $fh->getline;
                chomp $entry;
                _debug "<- $entry\n";
-        $callback->($entry, $cbdata);
+               $callback->($entry, $cbdata);
        }
        return $cbdata;
 }
@@ -260,14 +260,14 @@ sub getval # {{{
        my %args = @_;
        my $ret = {};
 
-    my $msg = $self->_socket_command('GETVAL', \%args) or return;
-    $self->_socket_chat($msg, sub {
-            local $_ = shift;
-            my $ret = shift;
-            /^(\w+)=NaN$/ and $ret->{$1} = undef, return;
-            /^(\w+)=(.*)$/ and looks_like_number($2) and $ret->{$1} = 0 + $2, return;
-        }, $ret
-    );
+       my $msg = $self->_socket_command('GETVAL', \%args) or return;
+       $self->_socket_chat($msg, sub {
+                       local $_ = shift;
+                       my $ret = shift;
+                       /^(\w+)=NaN$/ and $ret->{$1} = undef, return;
+                       /^(\w+)=(.*)$/ and looks_like_number($2) and $ret->{$1} = 0 + $2, return;
+               }, $ret
+       );
        return $ret;
 } # }}} sub getval
 
@@ -284,17 +284,17 @@ sub getthreshold # {{{
        my %args = @_;
        my $ret = {};
 
-    my $msg = $self->_socket_command('GETTHRESHOLD', \%args) or return;
-    $self->_socket_chat($msg, sub {
-            local $_ = shift;
-            my $ret = shift;
-            my ( $key, $val );
-            ( $key, $val ) = /^\s*([^:]+):\s*(.*)/ and do {
-                  $key =~ s/\s*$//;
-                  $ret->{$key} = $val;
-            };
-        }, $ret
-    );
+       my $msg = $self->_socket_command('GETTHRESHOLD', \%args) or return;
+       $self->_socket_chat($msg, sub {
+                       local $_ = shift;
+                       my $ret = shift;
+                       my ( $key, $val );
+                       ( $key, $val ) = /^\s*([^:]+):\s*(.*)/ and do {
+                                 $key =~ s/\s*$//;
+                                 $ret->{$key} = $val;
+                       };
+               }, $ret
+       );
        return $ret;
 } # }}} sub getthreshold
 
@@ -319,7 +319,7 @@ sub putval
        my $fh = $self->{sock} or confess;
 
        my $interval = defined $args{interval} ?
-    ' interval=' . _escape_argument ($args{interval}) : '';
+       ' interval=' . _escape_argument ($args{interval}) : '';
 
        $identifier = _create_identifier (\%args) or return;
        if (!$args{values})
@@ -372,23 +372,23 @@ The returned data is in the same format as from C<listval>.
 sub listval_filter
 {
        my $self = shift;
-    my %args = @_;
+       my %args = @_;
        my @ret;
        my $nresults;
        my $fh = $self->{sock} or confess;
 
-    my $pattern =
-    (exists $args{host}              ? "$args{host}"             : '[^/]+') .
-    (exists $args{plugin}            ? "/$args{plugin}"          : '/[^/-]+') .
+       my $pattern =
+       (exists $args{host}              ? "$args{host}"             : '[^/]+') .
+       (exists $args{plugin}            ? "/$args{plugin}"          : '/[^/-]+') .
        (exists $args{plugin_instance}   ? "-$args{plugin_instance}" : '(?:-[^/]+)?') .
        (exists $args{type}              ? "/$args{type}"            : '/[^/-]+') .
        (exists $args{type_instance}     ? "-$args{type_instance}"   : '(?:-[^/]+)?');
-    $pattern = qr/^\d+ $pattern$/;
+       $pattern = qr/^\d+(?:\.\d+)? $pattern$/;
 
-    my $msg = $self->_socket_command('LISTVAL') or return;
+       my $msg = $self->_socket_command('LISTVAL') or return;
        ($nresults, $msg) = split / /, $msg, 2;
 
-    # This could use _socket_chat() but doesn't for speed reasons
+       # This could use _socket_chat() but doesn't for speed reasons
        if ($nresults < 0)
        {
                $self->{error} = $msg;
@@ -404,13 +404,13 @@ sub listval_filter
                my ($time, $ident) = split / /, $msg, 2;
 
                $ident = _parse_identifier ($ident);
-               $ident->{time} = int $time;
+               $ident->{time} = 0+$time;
 
                push (@ret, $ident);
-       } # for (i = 0 .. $status)
+       } # for (i = 0 .. $nresults)
 
        return @ret;
-} # listval
+} # listval_filter
 
 =item I<$res> = I<$self>-E<gt>B<listval> ()
 
@@ -427,10 +427,10 @@ sub listval
        my @ret;
        my $fh = $self->{sock} or confess;
 
-    my $msg = $self->_socket_command('LISTVAL') or return;
+       my $msg = $self->_socket_command('LISTVAL') or return;
        ($nresults, $msg) = split / /, $msg, 2;
 
-    # This could use _socket_chat() but doesn't for speed reasons
+       # This could use _socket_chat() but doesn't for speed reasons
        if ($nresults < 0)
        {
                $self->{error} = $msg;
@@ -446,10 +446,10 @@ sub listval
                my ($time, $ident) = split / /, $msg, 2;
 
                $ident = _parse_identifier ($ident);
-               $ident->{time} = int $time;
+               $ident->{time} = 0+$time;
 
                push (@ret, $ident);
-       } # for (i = 0 .. $status)
+       } # for (i = 0 .. $nresults)
 
        return @ret;
 } # listval
@@ -497,10 +497,10 @@ sub putnotif
 
        my $msg; # message sent to the socket
        
-    for my $arg (qw( message severity ))
-    {
-        cluck ("Need argument `$arg'"), return unless $args{$arg};
-    }
+       for my $arg (qw( message severity ))
+       {
+               cluck ("Need argument `$arg'"), return unless $args{$arg};
+       }
        $args{severity} = lc $args{severity};
        if (($args{severity} ne 'failure')
                && ($args{severity} ne 'warning')
@@ -554,9 +554,9 @@ sub flush
 
        my $fh = $self->{sock} or confess;
 
-       my $msg    = "FLUSH";
+       my $msg = "FLUSH";
 
-    $msg .= " timeout=$args{timeout}" if defined $args{timeout};
+       $msg .= " timeout=$args{timeout}" if defined $args{timeout};
 
        if ($args{plugins})
        {
@@ -587,11 +587,11 @@ sub flush
                                $self->_send_message($msg) or return;
                                $msg = $pre;
                        }
-            
+
                        $msg .= $ident_str;
                }
        }
-    
+
        return $self->_send_message($msg);
 }
 
@@ -637,4 +637,4 @@ Florian octo Forster E<lt>octo@collectd.orgE<gt>
 
 =cut
 1;
-# vim: set fdm=marker :
+# vim: set fdm=marker noexpandtab:
index 4e94f8e..4a4822a 100644 (file)
@@ -9,8 +9,6 @@ use Collectd::MockDaemon;
 my $path = mockd_start();
 END { mockd_stop(); }
 
-sub filter_time { return map { delete $_->{time}; $_ } @_ }
-
 sub test_query {
     my ($s, $attr, $results) = @_;
     my ($nresults, $resultdata) = @$results;
@@ -33,24 +31,24 @@ test_query($s, $_, $queries{$_}) for sort keys %queries;
 
 my @values = $s->listval;
 is(scalar @values, 4984, "Correct number of results from LISTVAL");
-delete $values[1234]{time};     # won't be constant
 is_deeply($values[1234], {
         type_instance => 'nice',
         plugin_instance => 21,
         plugin => 'cpu',
         type => 'cpu',
-        host => 'h2gdf6120'
+        host => 'h2gdf6120',
+        time => 1479835354.434,
     }, "Correct data returned for select element");
 @values = ();
 
-is_deeply([ filter_time $s->listval_filter() ] , [ filter_time $s->listval ], "listval_filter() w/o filter equivalent to listval()");
+is_deeply([ $s->listval_filter() ] , [ $s->listval ], "listval_filter() w/o filter equivalent to listval()");
 is_deeply(
-    [ filter_time $s->listval_filter(host => 'a1d8f6310', plugin => 'disk', plugin_instance => 'vda6') ],
+    [ $s->listval_filter(host => 'a1d8f6310', plugin => 'disk', plugin_instance => 'vda6') ],
     [
-        { 'plugin_instance' => 'vda6', 'type' => 'disk_merged', 'plugin' => 'disk', 'host' => 'a1d8f6310' },
-        { 'host' => 'a1d8f6310', 'plugin' => 'disk', 'plugin_instance' => 'vda6', 'type' => 'disk_octets' },
-        { 'type' => 'disk_ops', 'plugin_instance' => 'vda6', 'plugin' => 'disk', 'host' => 'a1d8f6310' },
-        { 'plugin' => 'disk', 'host' => 'a1d8f6310', 'type' => 'disk_time', 'plugin_instance' => 'vda6' }
+        { 'plugin_instance' => 'vda6', 'type' => 'disk_merged', 'plugin' => 'disk', 'host' => 'a1d8f6310', time => 1479835354.434 },
+        { 'host' => 'a1d8f6310', 'plugin' => 'disk', 'plugin_instance' => 'vda6', 'type' => 'disk_octets', time => 1479835356 },
+        { 'type' => 'disk_ops', 'plugin_instance' => 'vda6', 'plugin' => 'disk', 'host' => 'a1d8f6310', time => 1479835354 },
+        { 'plugin' => 'disk', 'host' => 'a1d8f6310', 'type' => 'disk_time', 'plugin_instance' => 'vda6', time => 1479835354 }
     ],
     "Correct result from listval_filter on <host>, <plugin> and <plugin_instance>"
 );
index 98f0a90..feed085 100644 (file)
@@ -214,7 +214,54 @@ AC_HEADER_SYS_WAIT
 AC_HEADER_DIRENT
 AC_HEADER_STDBOOL
 
-AC_CHECK_HEADERS(stdio.h errno.h math.h stdarg.h syslog.h fcntl.h signal.h assert.h sys/types.h sys/socket.h sys/select.h poll.h netdb.h arpa/inet.h sys/resource.h sys/param.h kstat.h regex.h sys/ioctl.h endian.h sys/isa_defs.h fnmatch.h libgen.h)
+AC_CHECK_HEADERS([ \
+  arpa/inet.h \
+  assert.h \
+  ctype.h \
+  endian.h \
+  errno.h \
+  fcntl.h \
+  fnmatch.h \
+  fs_info.h \
+  fshelp.h \
+  grp.h \
+  kstat.h \
+  kvm.h \
+  libgen.h \
+  limits.h \
+  locale.h \
+  math.h \
+  mntent.h \
+  mnttab.h \
+  netdb.h \
+  paths.h \
+  poll.h \
+  pthread_np.h \
+  pwd.h \
+  regex.h \
+  signal.h \
+  stdarg.h \
+  stdio.h \
+  sys/fs_types.h \
+  sys/fstyp.h \
+  sys/ioctl.h \
+  sys/isa_defs.h \
+  sys/mntent.h \
+  sys/mnttab.h \
+  sys/param.h \
+  sys/resource.h \
+  sys/select.h \
+  sys/socket.h \
+  sys/statfs.h \
+  sys/statvfs.h \
+  sys/types.h \
+  sys/un.h \
+  sys/vfs.h \
+  sys/vfstab.h \
+  sys/vmmeter.h \
+  syslog.h \
+  wordexp.h \
+])
 
 # For entropy plugin on newer NetBSD
 AC_CHECK_HEADERS(sys/rndio.h, [], [],
@@ -658,31 +705,6 @@ AC_CHECK_HEADERS(linux/un.h, [], [],
 #endif
 ])
 
-AC_CHECK_HEADERS([ \
-  ctype.h \
-  fs_info.h \
-  fshelp.h \
-  grp.h \
-  kvm.h \
-  limits.h \
-  locale.h \
-  mntent.h \
-  mnttab.h \
-  paths.h \
-  pwd.h \
-  sys/fs_types.h \
-  sys/fstyp.h \
-  sys/mntent.h \
-  sys/mnttab.h \
-  sys/statfs.h \
-  sys/statvfs.h \
-  sys/un.h \
-  sys/vfs.h \
-  sys/vfstab.h \
-  sys/vmmeter.h \
-  wordexp.h \
-])
-
 # --enable-xfs {{{
 AC_ARG_ENABLE([xfs],
   [AS_HELP_STRING([--enable-xfs], [xfs support in df plugin @<:@default=yes@:>@])],
@@ -1754,6 +1776,45 @@ AC_CHECK_MEMBERS([kstat_io_t.nwritten, kstat_io_t.writes, kstat_io_t.nwrites, ks
 #endif
        ])
 
+# check for pthread_setname_np
+SAVE_LDFLAGS="$LDFLAGS"
+LDFLAGS="$LDFLAGS -lpthread"
+
+AC_MSG_CHECKING([for pthread_setname_np])
+       have_pthread_setname_np="no"
+       AC_LINK_IFELSE([AC_LANG_PROGRAM(
+[[
+#define _GNU_SOURCE
+#include <pthread.h>
+]],
+[[
+        pthread_setname_np((pthread_t) {0}, "conftest");
+]]
+       )], [
+               have_pthread_setname_np="yes"
+               AC_DEFINE(HAVE_PTHREAD_SETNAME_NP, 1, [pthread_setname_np() is available.])
+       ])
+
+AC_MSG_RESULT([$have_pthread_setname_np])
+
+# check for pthread_set_name_np(3) (FreeBSD)
+AC_MSG_CHECKING([for pthread_set_name_np])
+       have_pthread_set_name_np="no"
+       AC_LINK_IFELSE([AC_LANG_PROGRAM(
+[[
+#include <pthread_np.h>
+]],
+[[
+        pthread_set_name_np((pthread_t) {0}, "conftest");
+]]
+       )], [
+               have_pthread_set_name_np="yes"
+               AC_DEFINE(HAVE_PTHREAD_SET_NAME_NP, 1, [pthread_set_name_np() is available.])
+       ])
+AC_MSG_RESULT([$have_pthread_set_name_np])
+
+LDFLAGS="$SAVE_LDFLAGS"
+
 #
 # Checks for libraries begin here
 #
@@ -2636,6 +2697,40 @@ then
 fi
 # }}}
 
+# --with-libdpdk {{{
+AC_ARG_VAR([LIBDPDK_CPPFLAGS], [Preprocessor flags for libdpdk])
+AC_ARG_VAR([LIBDPDK_LDFLAGS], [Linker flags for libdpdk])
+
+AC_ARG_WITH([libdpdk], [AS_HELP_STRING([--without-libdpdk], [Disable libdpdk.])])
+
+if test "x$with_libdpdk" != "xno"
+then
+       if test "x$LIBDPDK_CPPFLAGS" = "x"
+       then
+               LIBDPDK_CPPFLAGS="-I/usr/include/dpdk"
+       fi
+       SAVE_CPPFLAGS="$CPPFLAGS"
+       CPPFLAGS="$LIBDPDK_CPPFLAGS $CPPFLAGS"
+       AC_CHECK_HEADERS([rte_config.h],
+               [with_libdpdk="yes"],
+               [with_libdpdk="no (rte_config.h not found)"]
+       )
+       CPPFLAGS="$SAVE_CPPFLAGS"
+fi
+
+if test "x$with_libdpdk" = "xyes"
+then
+       SAVE_LDFLAGS="$LDFLAGS"
+       LDFLAGS="$LIBDPDK_LDFLAGS $LDFLAGS"
+       AC_CHECK_LIB([dpdk], [rte_eal_init],
+               [with_libdpdk="yes"],
+               [with_libdpdk="no (symbol 'rte_eal_init' not found)"]
+       )
+       LDFLAGS="$SAVE_LDFLAGS"
+fi
+
+# }}}
+
 # --with-java {{{
 with_java_home="$JAVA_HOME"
 if test "x$with_java_home" = "x"
@@ -3062,6 +3157,70 @@ fi
 AM_CONDITIONAL(BUILD_WITH_LIBMEMCACHED, test "x$with_libmemcached" = "xyes")
 # }}}
 
+# --with-libmicrohttpd {{{
+with_libmicrohttpd_cppflags=""
+with_libmicrohttpd_ldflags=""
+AC_ARG_WITH([libmicrohttpd], [AS_HELP_STRING([--with-libmicrohttpd@<:@=PREFIX@:>@], [Path to libmicrohttpd.])],
+  [
+    if test "x$withval" != "xno" && test "x$withval" != "xyes"
+    then
+      with_libmicrohttpd_cppflags="-I$withval/include"
+      with_libmicrohttpd_ldflags="-L$withval/lib"
+      with_libmicrohttpd="yes"
+    fi
+    if test "x$withval" = "xno"
+    then
+      with_libmicrohttpd="no (disabled on command line)"
+    fi
+  ],
+  [withval="yes"]
+)
+if test "x$withval" = "xyes"
+then
+PKG_CHECK_MODULES([MICROHTTPD], [libmicrohttpd],
+  [with_libmicrohttpd="yes"],
+  [with_libmicrohttpd="no (pkg-config could not find libmicrohttpd)"]
+)
+fi
+
+if test "x$MICROHTTPD_LIBS" = "x"
+then
+  MICROHTTPD_LIBS="-lmicrohttpd"
+fi
+
+SAVE_CPPFLAGS="$CPPFLAGS"
+SAVE_LDFLAGS="$LDFLAGS"
+SAVE_LIBS="$LIBS"
+CPPFLAGS="$with_libmicrohttpd_cppflags $MICROHTTPD_CFLAGS"
+LDFLAGS="$with_libmicrohttpd_ldflags $LDFLAGS"
+LIBS="$LIBS $MICROHTTPD_LIBS"
+
+if test "x$with_libmicrohttpd" = "xyes"
+then
+  AC_CHECK_HEADERS([microhttpd.h],
+                   [with_libmicrohttpd="yes"],
+                   [with_libmicrohttpd="no (<microhttpd.h> not found)"])
+fi
+
+if test "x$with_libmicrohttpd" = "xyes"
+then
+  AC_CHECK_LIB([microhttpd], [MHD_start_daemon],
+               [with_libmicrohttpd="yes"],
+               [with_libmicrohttpd="no (libmicrohttpd not found)"])
+fi
+
+CPPFLAGS="$SAVE_CPPFLAGS"
+LDFLAGS="$SAVE_LDFLAGS"
+LIBS="$SAVE_LIBS"
+
+BUILD_WITH_LIBMICROHTTPD_CPPFLAGS="$with_libmicrohttpd_cppflags $MICROHTTPD_CFLAGS"
+BUILD_WITH_LIBMICROHTTPD_LDFLAGS="$with_libmicrohttpd_ldflags"
+BUILD_WITH_LIBMICROHTTPD_LIBS="$MICROHTTPD_LIBS"
+AC_SUBST([BUILD_WITH_LIBMICROHTTPD_CPPFLAGS])
+AC_SUBST([BUILD_WITH_LIBMICROHTTPD_LDFLAGS])
+AC_SUBST([BUILD_WITH_LIBMICROHTTPD_LIBS])
+# }}}
+
 # --with-libmodbus {{{
 with_libmodbus_config=""
 with_libmodbus_cflags=""
@@ -4128,6 +4287,65 @@ fi
 AM_CONDITIONAL(BUILD_WITH_LIBPQ, test "x$with_libpq" = "xyes")
 # }}}
 
+# --with-libpqos {{{
+with_libpqos_cppflags=""
+with_libpqos_ldflags=""
+AC_ARG_WITH(libpqos, [AS_HELP_STRING([--with-libpqos@<:@=PREFIX@:>@], [Path to libpqos.])],
+[
+       if test "x$withval" != "xno" && test "x$withval" != "xyes"
+       then
+               with_libpqos_cppflags="-I$withval/include"
+               with_libpqos_ldflags="-L$withval/lib"
+               with_libpqos="yes"
+       else
+               with_libpqos="$withval"
+       fi
+],
+[
+       with_libpqos="yes"
+])
+if test "x$with_libpqos" = "xyes"
+then
+       SAVE_CPPFLAGS="$CPPFLAGS"
+       CPPFLAGS="$CPPFLAGS $with_libpqos_cppflags"
+
+       AC_CHECK_HEADERS(pqos.h, [with_libpqos="yes"], [with_libpqos="no (pqos.h not found)"])
+
+       CPPFLAGS="$SAVE_CPPFLAGS"
+fi
+if test "x$with_libpqos" = "xyes"
+then
+       SAVE_CPPFLAGS="$CPPFLAGS"
+       SAVE_LDFLAGS="$LDFLAGS"
+       CPPFLAGS="$CPPFLAGS $with_libpqos_cppflags"
+       LDFLAGS="$LDFLAGS $with_libpqos_ldflags"
+
+       AC_CHECK_LIB(pqos, pqos_init, [with_libpqos="yes"], [with_libpqos="no (Can't find libpqos)"])
+
+       CPPFLAGS="$SAVE_CPPFLAGS"
+       LDFLAGS="$SAVE_LDFLAGS"
+fi
+if test "x$with_libpqos" = "xyes"
+then
+  SAVE_CPPFLAGS="$CPPFLAGS"
+  CPPFLAGS="$CPPFLAGS $with_libpqos_cppflags"
+  AC_RUN_IFELSE([AC_LANG_PROGRAM(
+    [[#include <pqos.h>]],
+    [[return !(PQOS_VERSION >= 106)]])],
+    [with_libpqos="yes"], [with_libpqos="no (pqos library version 1.06 or higher is required)"])
+  CPPFLAGS="$SAVE_CPPFLAGS"
+fi
+if test "x$with_libpqos" = "xyes"
+then
+       BUILD_WITH_LIBPQOS_CPPFLAGS="$with_libpqos_cppflags"
+       BUILD_WITH_LIBPQOS_LDFLAGS="$with_libpqos_ldflags"
+       BUILD_WITH_LIBPQOS_LIBS="-lpqos"
+       AC_SUBST(BUILD_WITH_LIBPQOS_CPPFLAGS)
+       AC_SUBST(BUILD_WITH_LIBPQOS_LDFLAGS)
+       AC_SUBST(BUILD_WITH_LIBPQOS_LIBS)
+fi
+# }}}
+
 # --with-libprotobuf {{{
 with_libprotobuf_cppflags=""
 with_libprotobuf_ldflags=""
@@ -5807,12 +6025,15 @@ plugin_curl_xml="no"
 plugin_df="no"
 plugin_disk="no"
 plugin_drbd="no"
+plugin_dpdkstat="no"
 plugin_entropy="no"
 plugin_ethstat="no"
 plugin_fhcount="no"
 plugin_fscache="no"
 plugin_gps="no"
 plugin_grpc="no"
+plugin_hugepages="no"
+plugin_intel_rdt="no"
 plugin_interface="no"
 plugin_ipmi="no"
 plugin_ipvs="no"
@@ -5842,6 +6063,7 @@ plugin_virt="no"
 plugin_vmem="no"
 plugin_vserver="no"
 plugin_wireless="no"
+plugin_write_prometheus="no"
 plugin_xencpu="no"
 plugin_zfs_arc="no"
 plugin_zone="no"
@@ -5861,6 +6083,7 @@ then
        plugin_entropy="yes"
        plugin_fhcount="yes"
        plugin_fscache="yes"
+       plugin_hugepages="yes"
        plugin_interface="yes"
        plugin_ipc="yes"
        plugin_irq="yes"
@@ -6128,6 +6351,10 @@ fi
 if test "x$have_protoc_c" = "xyes" && test "x$with_libprotobuf_c" = "xyes"
 then
        plugin_pinba="yes"
+       if test "x$with_libmicrohttpd" = "xyes"
+       then
+               plugin_write_prometheus="yes"
+       fi
 fi
 
 # Mac OS X memory interface
@@ -6209,6 +6436,11 @@ then
   plugin_xencpu="yes"
 fi
 
+if test "x$with_libdpdk" = "xyes"
+then
+  plugin_dpdkstat="yes"
+fi
+
 m4_divert_once([HELP_ENABLE], [
 collectd plugins:])
 
@@ -6255,6 +6487,7 @@ AC_PLUGIN([dbi],                 [$with_libdbi],            [General database st
 AC_PLUGIN([df],                  [$plugin_df],              [Filesystem usage statistics])
 AC_PLUGIN([disk],                [$plugin_disk],            [Disk usage statistics])
 AC_PLUGIN([dns],                 [$with_libpcap],           [DNS traffic analysis])
+AC_PLUGIN([dpdkstat],            [$plugin_dpdkstat],        [Stats & Status from DPDK])
 AC_PLUGIN([drbd],                [$plugin_drbd],            [DRBD statistics])
 AC_PLUGIN([email],               [yes],                     [EMail statistics])
 AC_PLUGIN([entropy],             [$plugin_entropy],         [Entropy statistics])
@@ -6267,6 +6500,8 @@ AC_PLUGIN([gmond],               [$with_libganglia],        [Ganglia plugin])
 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_rdt],           [$with_libpqos],           [Intel RDT monitor plugin])
 AC_PLUGIN([interface],           [$plugin_interface],       [Interface traffic statistics])
 AC_PLUGIN([ipc],                 [$plugin_ipc],             [IPC statistics])
 AC_PLUGIN([ipmi],                [$plugin_ipmi],            [IPMI sensor statistics])
@@ -6364,6 +6599,7 @@ AC_PLUGIN([write_http],          [$with_libcurl],           [HTTP output plugin]
 AC_PLUGIN([write_kafka],         [$with_librdkafka],        [Kafka output plugin])
 AC_PLUGIN([write_log],           [yes],                     [Log output plugin])
 AC_PLUGIN([write_mongodb],       [$with_libmongoc],         [MongoDB output plugin])
+AC_PLUGIN([write_prometheus],    [$plugin_write_prometheus], [Prometheus write plugin])
 AC_PLUGIN([write_redis],         [$with_libhiredis],        [Redis output plugin])
 AC_PLUGIN([write_riemann],       [$with_libriemann_client], [Riemann output plugin])
 AC_PLUGIN([write_sensu],         [yes],                     [Sensu output plugin])
@@ -6594,6 +6830,7 @@ AC_MSG_RESULT([    libaquaero5 . . . . . $with_libaquaero5])
 AC_MSG_RESULT([    libatasmart . . . . . $with_libatasmart])
 AC_MSG_RESULT([    libcurl . . . . . . . $with_libcurl])
 AC_MSG_RESULT([    libdbi  . . . . . . . $with_libdbi])
+AC_MSG_RESULT([    libdpdk . . . . . . . $with_libdpdk])
 AC_MSG_RESULT([    libesmtp  . . . . . . $with_libesmtp])
 AC_MSG_RESULT([    libganglia  . . . . . $with_libganglia])
 AC_MSG_RESULT([    libgcrypt . . . . . . $with_libgcrypt])
@@ -6611,6 +6848,7 @@ AC_MSG_RESULT([    libldap . . . . . . . $with_libldap])
 AC_MSG_RESULT([    liblua  . . . . . . . $with_liblua])
 AC_MSG_RESULT([    liblvm2app  . . . . . $with_liblvm2app])
 AC_MSG_RESULT([    libmemcached  . . . . $with_libmemcached])
+AC_MSG_RESULT([    libmicrohttpd . . . . $with_libmicrohttpd])
 AC_MSG_RESULT([    libmnl  . . . . . . . $with_libmnl])
 AC_MSG_RESULT([    libmodbus . . . . . . $with_libmodbus])
 AC_MSG_RESULT([    libmongoc . . . . . . $with_libmongoc])
@@ -6627,6 +6865,7 @@ AC_MSG_RESULT([    libpcap . . . . . . . $with_libpcap])
 AC_MSG_RESULT([    libperfstat . . . . . $with_perfstat])
 AC_MSG_RESULT([    libperl . . . . . . . $with_libperl])
 AC_MSG_RESULT([    libpq . . . . . . . . $with_libpq])
+AC_MSG_RESULT([    libpqos . . . . . . . $with_libpqos])
 AC_MSG_RESULT([    libprotobuf . . . . . $with_libprotobuf])
 AC_MSG_RESULT([    libprotobuf-c . . . . $with_libprotobuf_c])
 AC_MSG_RESULT([    libpython . . . . . . $with_libpython])
@@ -6685,6 +6924,7 @@ AC_MSG_RESULT([    dbi . . . . . . . . . $enable_dbi])
 AC_MSG_RESULT([    df  . . . . . . . . . $enable_df])
 AC_MSG_RESULT([    disk  . . . . . . . . $enable_disk])
 AC_MSG_RESULT([    dns . . . . . . . . . $enable_dns])
+AC_MSG_RESULT([    dpdkstat  . . . . . . $enable_dpdkstat])
 AC_MSG_RESULT([    drbd  . . . . . . . . $enable_drbd])
 AC_MSG_RESULT([    email . . . . . . . . $enable_email])
 AC_MSG_RESULT([    entropy . . . . . . . $enable_entropy])
@@ -6697,6 +6937,8 @@ AC_MSG_RESULT([    gmond . . . . . . . . $enable_gmond])
 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_rdt. . . . .  . $enable_intel_rdt])
 AC_MSG_RESULT([    interface . . . . . . $enable_interface])
 AC_MSG_RESULT([    ipc . . . . . . . . . $enable_ipc])
 AC_MSG_RESULT([    ipmi  . . . . . . . . $enable_ipmi])
@@ -6793,6 +7035,7 @@ AC_MSG_RESULT([    write_http  . . . . . $enable_write_http])
 AC_MSG_RESULT([    write_kafka . . . . . $enable_write_kafka])
 AC_MSG_RESULT([    write_log . . . . . . $enable_write_log])
 AC_MSG_RESULT([    write_mongodb . . . . $enable_write_mongodb])
+AC_MSG_RESULT([    write_prometheus. . . $enable_write_prometheus])
 AC_MSG_RESULT([    write_redis . . . . . $enable_write_redis])
 AC_MSG_RESULT([    write_riemann . . . . $enable_write_riemann])
 AC_MSG_RESULT([    write_sensu . . . . . $enable_write_sensu])
index 1edcfc6..b53cf95 100644 (file)
@@ -152,8 +152,8 @@ sub check_end {
                #try at least $self->{main}->{conf}->{collectd_retries} to get a
                #connection
                for (my $i = 0; $i < $self->{main}->{conf}->{collectd_retries} ; ++$i) {
-                       last if $sock = new IO::Socket::UNIX
-                               ($self->{main}->{conf}->{collectd_socket});
+                       my ($socket_path) = $self->{main}->{conf}->{collectd_socket} =~ /(.*)/; # Untaint path, which can contain any characters.
+                       last if $sock = new IO::Socket::UNIX $socket_path;
                        #sleep a random value between 0 and 50 microsecs to try for a new
                        #thread
                        usleep(int(rand(50))); 
index aa8c49c..d95f10b 100644 (file)
@@ -78,20 +78,21 @@ static int my_init(void) {
 } /* static int my_init (void) */
 
 /*
- * This function is called in regular intervalls to collect the data.
+ * This is a utility function used by the read callback to populate a
+ * value_list_t and pass it to plugin_dispatch_values.
  */
-static int my_read(void) {
-  value_t values[1]; /* the size of this list should equal the number of
-                                            data sources */
+static int my_submit(gauge_t value) {
   value_list_t vl = VALUE_LIST_INIT;
 
-  /* do the magic to read the data */
-  values[0].gauge = random();
-
-  vl.values = values;
+  /* Convert the gauge_t to a value_t and add it to the value_list_t. */
+  vl.values = &(value_t){.gauge = value};
   vl.values_len = 1;
-  vl.time = time(NULL);
-  sstrncpy(vl.host, hostname_g, sizeof(vl.host));
+
+  /* Only set vl.time yourself if you update multiple metrics (i.e. you
+   * have multiple calls to plugin_dispatch_values()) and they need to all
+   * have the same timestamp. */
+  /* vl.time = cdtime(); */
+
   sstrncpy(vl.plugin, "myplugin", sizeof(vl.plugin));
 
   /* it is strongly recommended to use a type defined in the types.db file
@@ -102,7 +103,18 @@ static int my_read(void) {
 
   /* dispatch the values to collectd which passes them on to all registered
    * write functions */
-  plugin_dispatch_values(&vl);
+  return plugin_dispatch_values(&vl);
+}
+
+/*
+ * This function is called in regular intervalls to collect the data.
+ */
+static int my_read(void) {
+  /* do the magic to read the data */
+  gauge_t value = random();
+
+  if (my_submit(value) != 0)
+    WARNING("myplugin plugin: Dispatching a random value failed.");
 
   /* A return value != 0 indicates an error and the plugin will be skipped
    * for an increasing amount of time. */
index 5181293..1cf2e31 100644 (file)
@@ -73,6 +73,7 @@
 %define with_gmond 0%{!?_without_gmond:1}
 %define with_gps 0%{!?_without_gps:1}
 %define with_hddtemp 0%{!?_without_hddtemp:1}
+%define with_hugepages 0%{!?_without_hugepages:1}
 %define with_interface 0%{!?_without_interface:1}
 %define with_ipc 0%{!?_without_ipc:1}
 %define with_ipmi 0%{!?_without_ipmi:1}
 %define with_write_graphite 0%{!?_without_write_graphite:1}
 %define with_write_http 0%{!?_without_write_http:1}
 %define with_write_log 0%{!?_without_write_log:1}
+%define with_write_prometheus 0%{!?_without_write_prometheus:1}
 %define with_write_redis 0%{!?_without_write_redis:1}
 %define with_write_riemann 0%{!?_without_write_riemann:1}
 %define with_write_sensu 0%{!?_without_write_sensu:1}
 %define with_barometer 0%{!?_without_barometer:0}
 # plugin grpc disabled, requires protobuf-compiler >= 3.0
 %define with_grpc 0%{!?_without_grpc:0}
+# plugin dpdkstat disabled, requires libdpdk
+%define with_dpdkstat 0%{!?_without_dpdkstat:0}
 # plugin lpar disabled, requires AIX
 %define with_lpar 0%{!?_without_lpar:0}
+# plugin intel_rdt disabled, requires intel-cmt-cat
+%define with_intel_rdt 0%{!?_without_intel_rdt:0}
 # plugin mic disabled, requires Mic
 %define with_mic 0%{!?_without_mic:0}
 # plugin netapp disabled, requires libnetapp
 %define with_redis 0
 %define with_smart 0
 %define with_turbostat 0
+%define with_write_prometheus 0
 %define with_write_redis 0
 %define with_write_riemann 0
 %endif
 
 Summary:       Statistics collection and monitoring daemon
 Name:          collectd
-Version:       5.6.1
-Release:       4%{?dist}
+Version:       5.7.1
+Release:       3%{?dist}
 URL:           https://collectd.org
 Source:                https://collectd.org/files/%{name}-%{version}.tar.bz2
 License:       GPLv2
@@ -455,6 +462,17 @@ 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_rdt}
+%package intel_rdt
+Summary:       Intel RDT plugin for collectd
+Group:         System Environment/Daemons
+Requires:      %{name}%{?_isa} = %{version}-%{release}
+BuildRequires: intel-cmt-cat
+%description intel_rdt
+The intel_rdt plugin collects information provided by monitoring features of
+Intel Resource Director Technology (Intel(R) RDT).
+%endif
+
 %if %{with_ipmi}
 %package ipmi
 Summary:       IPMI plugin for collectd
@@ -816,6 +834,17 @@ BuildRequires: librdkafka-devel
 The write_kafka plugin sends values to kafka, a distributed messaging system.
 %endif
 
+%if %{with_write_prometheus}
+%package write_prometheus
+Summary:       Write-prometheus plugin for collectd
+Group:         System Environment/Daemons
+Requires:      %{name}%{?_isa} = %{version}-%{release}
+BuildRequires: libmicrohttpd-devel
+%description write_prometheus
+The Write Prometheus plugin exposes collected values using an embedded HTTP
+server, turning the collectd daemon into a Prometheus exporter.
+%endif
+
 %if %{with_write_redis}
 %package write_redis
 Summary:       Write-Redis plugin for collectd
@@ -1072,6 +1101,12 @@ Collectd utilities
 %define _with_drbd --disable-drbd
 %endif
 
+%if %{with_dpdkstat}
+%define _with_dpdkstat --enable-dpdkstat
+%else
+%define _with_dpdkstat --disable-dpdkstat
+%endif
+
 %if %{with_email}
 %define _with_email --enable-email
 %else
@@ -1138,6 +1173,18 @@ Collectd utilities
 %define _with_hddtemp --disable-hddtemp
 %endif
 
+%if %{with_hugepages}
+%define _with_hugepages --enable-hugepages
+%else
+%define _with_hugepages --disable-hugepages
+%endif
+
+%if %{with_intel_rdt}
+%define _with_intel_rdt --enable-intel_rdt
+%else
+%define _with_intel_rdt --disable-intel_rdt
+%endif
+
 %if %{with_interface}
 %define _with_interface --enable-interface
 %else
@@ -1659,6 +1706,12 @@ Collectd utilities
 %define _with_write_mongodb --disable-write_mongodb
 %endif
 
+%if %{with_write_prometheus}
+%define _with_write_prometheus --enable-write_prometheus
+%else
+%define _with_write_prometheus --disable-write_prometheus
+%endif
+
 %if %{with_write_redis}
 %define _with_write_redis --enable-write_redis
 %else
@@ -1755,6 +1808,7 @@ Collectd utilities
        %{?_with_disk} \
        %{?_with_dns} \
        %{?_with_drbd} \
+       %{?_with_dpdkstat} \
        %{?_with_email} \
        %{?_with_entropy} \
        %{?_with_ethstat} \
@@ -1766,6 +1820,8 @@ Collectd utilities
        %{?_with_gps} \
        %{?_with_grpc} \
        %{?_with_hddtemp} \
+       %{?_with_hugepages} \
+       %{?_with_intel_rdt} \
        %{?_with_interface} \
        %{?_with_ipc} \
        %{?_with_ipmi} \
@@ -1853,6 +1909,7 @@ Collectd utilities
        %{?_with_write_kafka} \
        %{?_with_write_log} \
        %{?_with_write_mongodb} \
+       %{?_with_write_prometheus} \
        %{?_with_write_redis} \
        %{?_with_write_riemann} \
        %{?_with_write_sensu} \
@@ -2031,6 +2088,9 @@ fi
 %if %{with_drbd}
 %{_libdir}/%{name}/drbd.so
 %endif
+%if %{with_dpdkstat}
+%{_libdir}/%{name}/dpdkstat.so
+%endif
 %if %{with_ethstat}
 %{_libdir}/%{name}/ethstat.so
 %endif
@@ -2049,6 +2109,9 @@ fi
 %if %{with_fscache}
 %{_libdir}/%{name}/fscache.so
 %endif
+%if %{with_hugepages}
+%{_libdir}/%{name}/hugepages.so
+%endif
 %if %{with_interface}
 %{_libdir}/%{name}/interface.so
 %endif
@@ -2308,6 +2371,11 @@ fi
 %{_libdir}/%{name}/hddtemp.so
 %endif
 
+%if %{with_intel_rdt}
+%files intel_rdt
+%{_libdir}/%{name}/intel_rdt.so
+%endif
+
 %if %{with_ipmi}
 %files ipmi
 %{_libdir}/%{name}/ipmi.so
@@ -2484,6 +2552,11 @@ fi
 %{_libdir}/%{name}/write_kafka.so
 %endif
 
+%if %{with_write_prometheus}
+%files write_prometheus
+%{_libdir}/%{name}/write_prometheus.so
+%endif
+
 %if %{with_write_redis}
 %files write_redis
 %{_libdir}/%{name}/write_redis.so
@@ -2516,14 +2589,23 @@ fi
 %doc contrib/
 
 %changelog
-* Sun Mar 05 2017 Ruben Kerkhof <ruben@rubenkerkhof.com> - 5.6.1-4
+* Sun Mar 05 2017 Ruben Kerkhof <ruben@rubenkerkhof.com> - 5.7.1-2
 - Don't enable XFS support on RHEL6, it is missing for i386
 
-* Wed Feb 22 2017 Ruben Kerkhof <ruben@rubenkerkhof.com> - 5.6.1-3
+* Wed Feb 22 2017 Ruben Kerkhof <ruben@rubenkerkhof.com> - 5.7.1-2
 - Enable XFS support in df plugin
+- Fix bogus date in changelog
+
+* Sun Jan 01 2017 Marc Fournier <marc.fournier@camptocamp.com> - 5.7.1-1
+- New upstream version
+
+* Tue Nov 29 2016 Ruben Kerkhof <ruben@rubenkerkhof.com> - 5.7.0-2
+- Disable redis plugin on RHEL 6, hiredis has been retired from EPEL6
 
-* Tue Nov 29 2016 Ruben Kerkhof <ruben@rubenkerkhof.com> - 5.6.1-2
-- Disable redis plugin on RHEL < 7, hiredis has been retired from EPEL6
+* Mon Oct 10 2016 Marc Fournier <marc.fournier@camptocamp.com> - 5.7.0-1
+- New PRE-RELEASE version
+- New plugins enabled by default: hugepages, write_prometheus
+- New plugins disabled by default: dpdkstat, intel_rdt
 
 * Mon Oct 10 2016 Victor Demonchy <v.demonchy@criteo.com> - 5.6.1-1
 - New upstream version
diff --git a/docs/BUILD.dpdkstat.md b/docs/BUILD.dpdkstat.md
new file mode 100644 (file)
index 0000000..8991a61
--- /dev/null
@@ -0,0 +1,245 @@
+# The dpdkstat plugin
+
+**Data Plane Development Kit** (DPDK) is a set of drivers and libraries for fast
+packet processing.
+
+## Summary
+
+The *dpdkstat plugin* has the following requirements:
+
+ * DPDK 16.04 or later
+ * GCC 4.9 or later
+
+You can also build with GCC 4.8 (e.g. Ubuntu 14.04) if you specify the SSSE3
+instruction set manually:
+
+    make -j CFLAGS+='-mssse3'
+
+## Building DPDK
+
+ *  Setup the build environment:
+
+    Ensure that you have GCC 4.9 or later. Ubuntu 14.04, for example, has GCC
+    4.8 by default and requires an upgrade:
+
+        add-apt-repository ppa:ubuntu-toolchain-r/test
+        apt-get update
+        apt-get install gcc-4.9
+
+    If you know that the platform that you wish to run collectd on supports the
+    SSSE3 instruction set, GCC 4.8 also works if you enable SSSE3 manually:
+
+        make -j CFLAGS+='-mssse3'
+
+ *  Clone DPDK:
+
+        git clone git://dpdk.org/dpdk
+
+ *  Checkout the [DPDK system
+    requirements](http://dpdk.org/doc/guides/linux_gsg/sys_reqs.html) and make
+    sure you have the required tools and hugepage setup as specified there.
+
+    **Note:** It's recommended to use the 1GB hugepage setup for best
+    performance, please follow the instruction for "Reserving Hugepages for DPDK
+    Use" in the link above.
+
+    However if you plan on configuring 2MB hugepages on the fly please ensure to
+    add appropriate commands to reserve hugepages in a system startup script if
+    collectd is booted at system startup time. These commands include:
+
+        mkdir -p /mnt/huge
+        mount -t hugetlbfs nodev /mnt/huge
+        echo 64 > /sys/devices/system/node/node0/hugepages/hugepages-2048kB/nr_hugepages
+
+ *  To configure the DPDK build for the combined shared library and enable autoload
+    of pmd drivers modify `config/common_base` in your DPDK as follows
+
+        #
+        # Compile to share library
+        #
+        -CONFIG_RTE_BUILD_SHARED_LIB=n
+        +CONFIG_RTE_BUILD_SHARED_LIB=y
+
+        # Default driver path (or "" to disable)
+        -CONFIG_RTE_EAL_PMD_PATH=""
+        +CONFIG_RTE_EAL_PMD_PATH="/usr/lib/dpdk-pmd/"
+
+ *  Prepare the configuration for the appropriate target as specified at:
+    http://dpdk.org/doc/guides/linux_gsg/build_dpdk.html.
+
+    For example:
+
+        make config T=x86_64-native-linuxapp-gcc
+
+ *  Build the target:
+
+        make
+
+ *  Install DPDK to `/usr`
+
+        sudo make install prefix=/usr
+
+ *  Create dpdk-pmd folder
+
+        mkdir -p /usr/lib/dpdk-pmd
+
+ *  Create symlinks to pmd drivers
+
+        find /usr/lib -type f -name 'librte_pmd*' | while read path ; do ln -s $path /usr/lib/dpdk-pmd/`echo $path | grep -o 'librte_.*so'` ;  done
+
+    **Note 1:** You must run make install as the configuration of collectd with
+    DPDK expects DPDK to be installed somewhere.
+
+    **Note 2:** If you don't specify a prefix then DPDK will be installed in
+    `/usr/local/`.
+
+    **Note 3:** If you are not root then use sudo to make install DPDK to the
+    appropriate location.
+
+    **Note 4:** You **MUST** create symlink to a NIC driver lib. This way collectd
+    will be able to work with device bound to dpdk.
+
+ *  Check that the DPDK library has been installed in `/usr/lib` or `/lib`:
+
+        ls /usr/lib | grep dpdk
+
+ *  Bind the interfaces to use with dpdkstat to DPDK:
+
+    DPDK devices can be setup with either the VFIO (for DPDK 1.7+) or UIO
+    modules.
+
+    **Note:** UIO requires inserting an out of tree driver `igb_uio.ko` that is
+    available in DPDK.
+
+    **UIO Setup:**
+
+     *  Insert `uio.ko`:
+
+            sudo modprobe uio
+
+     *  Insert `igb_uio.ko`:
+
+            sudo insmod $DPDK_BUILD/kmod/igb_uio.ko
+
+     *  Bind network device to `igb_uio`:
+
+            sudo $DPDK_DIR/tools/dpdk_nic_bind.py --bind=igb_uio eth1
+
+    **VFIO Setup:**
+
+     *  VFIO needs to be supported in the kernel and the BIOS. More information
+        can be found at: http://dpdk.org/doc/guides/linux_gsg/build_dpdk.html.
+     *  Insert the `vfio-pci.ko` module:
+
+            modprobe vfio-pci
+
+     *  Set the correct permissions for the VFIO device:
+
+            sudo /usr/bin/chmod a+x /dev/vfio
+            sudo /usr/bin/chmod 0666 /dev/vfio/*
+
+     *  Bind the network device to `vfio-pci`:
+
+            sudo $DPDK_DIR/tools/dpdk_nic_bind.py --bind=vfio-pci eth1
+
+        **Note:** Please ensure to add appropriate commands to bind the network
+        interfaces to DPDK in a system startup script if collectd is booted at
+        system startup time.
+
+     *  Run `ldconfig` to update the shared library cache.
+
+### Static library
+
+To build static DPDK library for use with collectd:
+
+ *  To configure DPDK to build the combined static library `libdpdk.a` ensure
+    that `CONFIG_RTE_BUILD_SHARED_LIB` is set to “n” in `config/common_base` in
+    your DPDK as follows:
+
+        #
+        # Compile to share library
+        #
+        CONFIG_RTE_BUILD_SHARED_LIB=n
+
+ *  Prepare the configuration for the appropriate target as specified at:
+    http://dpdk.org/doc/guides/linux_gsg/build_dpdk.html.
+
+    For example:
+
+        make config T=x86_64-native-linuxapp-gcc
+
+ *  Build the target using `-fPIC`:
+
+        make EXTRA_CFLAGS=-fPIC -j
+
+ *  Install DPDK to `/usr`:
+
+        sudo make install prefix=/usr
+
+## Build collectd with DPDK
+
+**Note:** DPDK 16.04 is the minimum version and currently supported version of
+DPDK required for the dpdkstat plugin. This is to allow the plugin to take
+advantage of functions added to detect if the DPDK primary process is alive.
+
+
+**Note:** The *Address-Space Layout Randomization* (ASLR) security feature in
+Linux should be disabled, in order for the same hugepage memory mappings to be
+present in all DPDK multi-process applications. Note that this has security
+implications.
+
+ *  To disable ASLR:
+
+        echo 0 > /proc/sys/kernel/randomize_va_space
+
+ *  To fully enable ASLR:
+
+        echo 2 > /proc/sys/kernel/randomize_va_space
+
+See also: http://dpdk.org/doc/guides/prog_guide/multi_proc_support.html
+
+ *  Generate the build script as specified below. (i.e. run `build.sh`).
+ *  Configure collectd with the DPDK shared library:
+
+        ./configure --with-libdpdk=/usr
+
+### Build with the static DPDK library
+
+To configure collectd with the DPDK static library:
+
+ *  Run *configure* with the following CFLAGS:
+
+        ./configure --with-libdpdk=/usr CFLAGS=" -lpthread -Wl,--whole-archive -Wl,-ldpdk -Wl,-lm -Wl,-lrt -Wl,-lpcap -Wl,-ldl -Wl,--no-whole-archive"
+
+ *  Make sure that dpdk and dpdkstat are enabled in the *configure* output.
+
+    Expected output:
+
+        Libraries:
+        ...
+        libdpdk  . . . . . . . . yes
+        
+        Modules:
+        ...
+        dpdkstat . . . . . . .yes
+
+ *  Build collectd:
+
+        make -j && make -j install.
+
+    **Note:** As mentioned above, if you are building on Ubuntu 14.04 with
+    GCC <= 4.8.X, you need to use:
+
+        make -j CFLAGS+='-mssse3' && make -j install
+
+## Caveats
+
+ *  The same PCI device configuration should be passed to the primary process as
+    the secondary process uses the same port indexes as the primary.
+ *  A blacklist / whitelist of NICs isn't supported yet.
+
+## License
+
+The *dpdkstat plugin* is copyright (c) 2016 *Intel Corporation* and licensed
+under the *MIT license*. Full licensing terms can be found in the file
+`COPYING`.
diff --git a/docs/BUILD.java.md b/docs/BUILD.java.md
new file mode 100644 (file)
index 0000000..2f4c0c2
--- /dev/null
@@ -0,0 +1,50 @@
+# Building with Java
+
+This file gives some background and hints how the *java plugin* needs to be
+configured.
+
+## Dependencies
+
+The *java plugin* requires a version of Java with *Java Native Interface* (JNI)
+**1.2** or later.
+
+## Configure and flags
+
+To determine the location of the required files of a Java installation is not an
+easy task, because the locations vary with your kernel (Linux, SunOS, …) and
+with your architecture (x86, SPARC, …) and there is no `java-config` script we
+could use. Configuration of the JVM library is therefore a bit tricky.
+
+The easiest way to use the `--with-java="${JAVA_HOME}"` option, where
+`JAVA_HOME` is usually something like:
+
+    /usr/lib/jvm/java-1.5.0-sun-1.5.0.14
+
+The configure script will then use *find(1)* to look for the following files:
+
+ *  `jni.h`
+ *  `jni_md.h`
+ *  `libjvm.so`
+
+If found, appropriate CPP-flags and LD-flags are set and the following library
+checks succeed.
+
+If this doesn't work for you, you have the possibility to specify CPP-flags,
+C-flags, LD-flags and LIBS for the *java plugin* by hand, using the following
+environment variables:
+
+ *  `JAVA_CPPFLAGS`
+ *  `JAVA_CFLAGS`
+ *  `JAVA_LDFLAGS`
+ *  `JAVA_LIBS`
+
+For example (shortened for demonstration purposes):
+
+    ./configure JAVA_CPPFLAGS="-I$JAVA_HOME/include -I$JAVA_HOME/include/linux"
+
+Adding `-ljvm` to JAVA_LIBS is done automatically, you don't have to do that.
+
+## License
+
+The *java plugin* is licensed under the *GNU General Public License, version 2*.
+Full licensing terms can be found in the file `COPYING`.
index 3c0bfd7..62d3bed 100644 (file)
@@ -1 +1 @@
-EXTRA_DIST = collectd.proto types.proto
+EXTRA_DIST = collectd.proto types.proto prometheus.proto
index c706ea1..83d2128 100644 (file)
@@ -30,25 +30,24 @@ option go_package = "collectd.org/rpc/proto";
 import "types.proto";
 
 service Collectd {
-  // DispatchValues reads the value lists from the DispatchValuesRequest stream.
+  // PutValues reads the value lists from the PutValuesRequest stream.
   // The gRPC server embedded into collectd will inject them into the system
   // just like the network plugin.
-  rpc DispatchValues(stream DispatchValuesRequest)
-      returns(DispatchValuesResponse);
+  rpc PutValues(stream PutValuesRequest) returns(PutValuesResponse);
 
   // QueryValues returns a stream of matching value lists from collectd's
   // internal cache.
   rpc QueryValues(QueryValuesRequest) returns(stream QueryValuesResponse);
 }
 
-// The arguments to DispatchValues.
-message DispatchValuesRequest {
+// The arguments to PutValues.
+message PutValuesRequest {
   // value_list is the metric to be sent to the server.
   collectd.types.ValueList value_list = 1;
 }
 
-// The response from DispatchValues.
-message DispatchValuesResponse {}
+// The response from PutValues.
+message PutValuesResponse {}
 
 // The arguments to QueryValues.
 message QueryValuesRequest {
diff --git a/proto/prometheus.proto b/proto/prometheus.proto
new file mode 100644 (file)
index 0000000..17adaf4
--- /dev/null
@@ -0,0 +1,72 @@
+// Copyright 2013 Prometheus Team
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+syntax = "proto2";
+
+package io.prometheus.client;
+option java_package = "io.prometheus.client";
+
+message LabelPair {
+  optional string name = 1;
+  optional string value = 2;
+}
+
+enum MetricType {
+  COUNTER = 0; GAUGE = 1; SUMMARY = 2; UNTYPED = 3; HISTOGRAM = 4;
+}
+
+message Gauge { optional double value = 1; }
+
+message Counter { optional double value = 1; }
+
+message Quantile {
+  optional double quantile = 1;
+  optional double value = 2;
+}
+
+message Summary {
+  optional uint64 sample_count = 1;
+  optional double sample_sum = 2;
+  repeated Quantile quantile = 3;
+}
+
+message Untyped { optional double value = 1; }
+
+message Histogram {
+  optional uint64 sample_count = 1;
+  optional double sample_sum = 2;
+  repeated Bucket bucket =
+      3; // Ordered in increasing order of upper_bound, +Inf bucket is optional.
+}
+
+message Bucket {
+  optional uint64 cumulative_count = 1; // Cumulative in increasing order.
+  optional double upper_bound = 2;      // Inclusive.
+}
+
+message Metric {
+  repeated LabelPair label = 1;
+  optional Gauge gauge = 2;
+  optional Counter counter = 3;
+  optional Summary summary = 4;
+  optional Untyped untyped = 5;
+  optional Histogram histogram = 7;
+  optional int64 timestamp_ms = 6;
+}
+
+message MetricFamily {
+  optional string name = 1;
+  optional string help = 2;
+  optional MetricType type = 3;
+  repeated Metric metric = 4;
+}
index f8dc283..11bc17d 100644 (file)
@@ -27,6 +27,15 @@ noinst_LTLIBRARIES =
 check_PROGRAMS =
 TESTS =
 
+noinst_LTLIBRARIES += libformat_graphite.la
+libformat_graphite_la_SOURCES  = utils_format_graphite.c utils_format_graphite.h
+libformat_graphite_la_CPPFLAGS = $(AM_CPPFLAGS)
+libformat_graphite_la_LDFLAGS  = $(AM_LDFLAGS)
+check_PROGRAMS += test_format_graphite
+TESTS += test_format_graphite
+test_format_graphite_SOURCES = utils_format_graphite_test.c testing.h
+test_format_graphite_LDADD = libformat_graphite.la daemon/libmetadata.la daemon/libplugin_mock.la -lm
+
 noinst_LTLIBRARIES += libformat_json.la
 libformat_json_la_SOURCES   = utils_format_json.c utils_format_json.h
 libformat_json_la_CPPFLAGS  = $(AM_CPPFLAGS)
@@ -43,12 +52,29 @@ test_format_json_LDADD = libformat_json.la daemon/libmetadata.la daemon/libplugi
 endif
 
 noinst_LTLIBRARIES += liblatency.la
-liblatency_la_SOURCES = utils_latency.c utils_latency.h
+liblatency_la_SOURCES = utils_latency.c utils_latency.h utils_latency_config.c utils_latency_config.h
+liblatency_la_LIBADD = daemon/libcommon.la -lm
 check_PROGRAMS += test_utils_latency
 TESTS += test_utils_latency
 test_utils_latency_SOURCES = utils_latency_test.c testing.h
 test_utils_latency_LDADD = liblatency.la daemon/libplugin_mock.la -lm
 
+noinst_LTLIBRARIES += libcmds.la
+libcmds_la_SOURCES = utils_cmds.c utils_cmds.h \
+                     utils_cmd_flush.c utils_cmd_flush.h \
+                     utils_cmd_getthreshold.c utils_cmd_getthreshold.h \
+                     utils_cmd_getval.c utils_cmd_getval.h \
+                     utils_cmd_listval.c utils_cmd_listval.h \
+                     utils_cmd_putnotif.c utils_cmd_putnotif.h \
+                     utils_cmd_putval.c utils_cmd_putval.h \
+                     utils_parse_option.c utils_parse_option.h
+libcmds_la_LIBADD = daemon/libcommon.la daemon/libmetadata.la -lm
+check_PROGRAMS += test_utils_cmds
+TESTS += test_utils_cmds
+test_utils_cmds_SOURCES = utils_cmds_test.c testing.h
+test_utils_cmds_LDADD = libcmds.la \
+                        daemon/libplugin_mock.la
+
 noinst_LTLIBRARIES += liblookup.la
 liblookup_la_SOURCES = utils_vl_lookup.c utils_vl_lookup.h
 liblookup_la_LIBADD = daemon/libavltree.la
@@ -153,13 +179,11 @@ endif
 
 if BUILD_PLUGIN_AMQP
 pkglib_LTLIBRARIES += amqp.la
-amqp_la_SOURCES = amqp.c \
-                 utils_cmd_putval.c utils_cmd_putval.h \
-                 utils_parse_option.c utils_parse_option.h \
-                 utils_format_graphite.c utils_format_graphite.h
+amqp_la_SOURCES = amqp.c
 amqp_la_LDFLAGS = $(PLUGIN_LDFLAGS) $(BUILD_WITH_LIBRABBITMQ_LDFLAGS)
 amqp_la_CPPFLAGS = $(AM_CPPFLAGS) $(BUILD_WITH_LIBRABBITMQ_CPPFLAGS)
-amqp_la_LIBADD = $(BUILD_WITH_LIBRABBITMQ_LIBS) libformat_json.la
+amqp_la_LIBADD = $(BUILD_WITH_LIBRABBITMQ_LIBS) \
+                 libcmds.la libformat_graphite.la libformat_json.la
 endif
 
 if BUILD_PLUGIN_APACHE
@@ -213,7 +237,7 @@ endif
 
 if BUILD_PLUGIN_BATTERY
 pkglib_LTLIBRARIES += battery.la
-battery_la_SOURCES = battery.c
+battery_la_SOURCES = battery.c battery_statefs.c
 battery_la_LDFLAGS = $(PLUGIN_LDFLAGS)
 if BUILD_WITH_LIBIOKIT
 battery_la_LDFLAGS += -framework IOKit
@@ -310,10 +334,11 @@ endif
 if BUILD_PLUGIN_CURL
 pkglib_LTLIBRARIES += curl.la
 curl_la_SOURCES = curl.c \
-                 utils_curl_stats.c utils_curl_stats.h
+                  utils_curl_stats.c utils_curl_stats.h \
+                  utils_match.c utils_match.h
 curl_la_LDFLAGS = $(PLUGIN_LDFLAGS)
 curl_la_CFLAGS = $(AM_CFLAGS) $(BUILD_WITH_LIBCURL_CFLAGS)
-curl_la_LIBADD = $(BUILD_WITH_LIBCURL_LIBS)
+curl_la_LIBADD = $(BUILD_WITH_LIBCURL_LIBS) liblatency.la
 endif
 
 if BUILD_PLUGIN_CURL_JSON
@@ -391,6 +416,14 @@ dns_la_LDFLAGS = $(PLUGIN_LDFLAGS)
 dns_la_LIBADD = -lpcap
 endif
 
+if BUILD_PLUGIN_DPDKSTAT
+pkglib_LTLIBRARIES += dpdkstat.la
+dpdkstat_la_SOURCES = dpdkstat.c
+dpdkstat_la_CPPFLAGS = $(AM_CPPFLAGS) $(LIBDPDK_CPPFLAGS)
+dpdkstat_la_LDFLAGS = $(PLUGIN_LDFLAGS) $(LIBDPDK_LDFLAGS)
+dpdkstat_la_LIBADD = -ldpdk
+endif
+
 if BUILD_PLUGIN_DRBD
 pkglib_LTLIBRARIES += drbd.la
 drbd_la_SOURCES = drbd.c
@@ -411,11 +444,9 @@ endif
 
 if BUILD_PLUGIN_EXEC
 pkglib_LTLIBRARIES += exec.la
-exec_la_SOURCES = exec.c \
-                 utils_cmd_putnotif.c utils_cmd_putnotif.h \
-                 utils_cmd_putval.c utils_cmd_putval.h \
-                 utils_parse_option.h utils_parse_option.c
+exec_la_SOURCES = exec.c
 exec_la_LDFLAGS = $(PLUGIN_LDFLAGS)
+exec_la_LIBADD = libcmds.la
 endif
 
 if BUILD_PLUGIN_ETHSTAT
@@ -471,6 +502,12 @@ hddtemp_la_LIBADD += -lsocket
 endif
 endif
 
+if BUILD_PLUGIN_HUGEPAGES
+pkglib_LTLIBRARIES += hugepages.la
+hugepages_la_SOURCES = hugepages.c
+hugepages_la_LDFLAGS = $(PLUGIN_LDFLAGS)
+endif
+
 if BUILD_PLUGIN_INTERFACE
 pkglib_LTLIBRARIES += interface.la
 interface_la_SOURCES = interface.c
@@ -647,10 +684,11 @@ endif
 
 if BUILD_PLUGIN_MEMCACHEC
 pkglib_LTLIBRARIES += memcachec.la
-memcachec_la_SOURCES = memcachec.c
+memcachec_la_SOURCES = memcachec.c \
+                       utils_match.c utils_match.h
 memcachec_la_LDFLAGS = $(PLUGIN_LDFLAGS) $(BUILD_WITH_LIBMEMCACHED_LDFLAGS)
 memcachec_la_CPPFLAGS = $(AM_CPPFLAGS) $(BUILD_WITH_LIBMEMCACHED_CPPFLAGS)
-memcachec_la_LIBADD = $(BUILD_WITH_LIBMEMCACHED_LIBS)
+memcachec_la_LIBADD = $(BUILD_WITH_LIBMEMCACHED_LIBS) liblatency.la
 endif
 
 if BUILD_PLUGIN_MEMCACHED
@@ -949,6 +987,14 @@ protocols_la_SOURCES = protocols.c
 protocols_la_LDFLAGS = $(PLUGIN_LDFLAGS)
 endif
 
+if BUILD_PLUGIN_INTEL_RDT
+pkglib_LTLIBRARIES += intel_rdt.la
+intel_rdt_la_SOURCES = intel_rdt.c
+intel_rdt_la_LDFLAGS = $(PLUGIN_LDFLAGS) $(BUILD_WITH_LIBPQOS_LDFLAGS)
+intel_rdt_la_CFLAGS = $(AM_CFLAGS) $(BUILD_WITH_LIBPQOS_CPPFLAGS)
+intel_rdt_la_LIBADD = $(BUILD_WITH_LIBPQOS_LIBS)
+endif
+
 if BUILD_PLUGIN_REDIS
 pkglib_LTLIBRARIES += redis.la
 redis_la_SOURCES = redis.c
@@ -1025,7 +1071,7 @@ if BUILD_PLUGIN_STATSD
 pkglib_LTLIBRARIES += statsd.la
 statsd_la_SOURCES = statsd.c
 statsd_la_LDFLAGS = $(PLUGIN_LDFLAGS)
-statsd_la_LIBADD = liblatency.la -lm
+statsd_la_LIBADD = liblatency.la
 endif
 
 if BUILD_PLUGIN_SWAP
@@ -1067,13 +1113,18 @@ endif
 
 if BUILD_PLUGIN_TAIL
 pkglib_LTLIBRARIES += tail.la
-tail_la_SOURCES = tail.c
+tail_la_SOURCES = tail.c \
+                  utils_match.c utils_match.h \
+                  utils_tail.c utils_tail.h \
+                  utils_tail_match.c utils_tail_match.h
 tail_la_LDFLAGS = $(PLUGIN_LDFLAGS)
+tail_la_LIBADD = liblatency.la
 endif
 
 if BUILD_PLUGIN_TAIL_CSV
 pkglib_LTLIBRARIES += tail_csv.la
-tail_csv_la_SOURCES = tail_csv.c
+tail_csv_la_SOURCES = tail_csv.c \
+                      utils_tail.c utils_tail.h
 tail_csv_la_LDFLAGS = $(PLUGIN_LDFLAGS)
 endif
 
@@ -1167,15 +1218,9 @@ endif
 
 if BUILD_PLUGIN_UNIXSOCK
 pkglib_LTLIBRARIES += unixsock.la
-unixsock_la_SOURCES = unixsock.c \
-                     utils_cmd_flush.h utils_cmd_flush.c \
-                     utils_cmd_getval.h utils_cmd_getval.c \
-                     utils_cmd_getthreshold.h utils_cmd_getthreshold.c \
-                     utils_cmd_listval.h utils_cmd_listval.c \
-                     utils_cmd_putval.h utils_cmd_putval.c \
-                     utils_cmd_putnotif.h utils_cmd_putnotif.c \
-                     utils_parse_option.h utils_parse_option.c
+unixsock_la_SOURCES = unixsock.c
 unixsock_la_LDFLAGS = $(PLUGIN_LDFLAGS)
+unixsock_la_LIBADD =  libcmds.la
 endif
 
 if BUILD_PLUGIN_UPTIME
@@ -1249,9 +1294,9 @@ endif
 
 if BUILD_PLUGIN_WRITE_GRAPHITE
 pkglib_LTLIBRARIES += write_graphite.la
-write_graphite_la_SOURCES = write_graphite.c \
-                        utils_format_graphite.c utils_format_graphite.h
+write_graphite_la_SOURCES = write_graphite.c
 write_graphite_la_LDFLAGS = $(PLUGIN_LDFLAGS)
+write_graphite_la_LIBADD = libformat_graphite.la
 endif
 
 if BUILD_PLUGIN_WRITE_HTTP
@@ -1266,19 +1311,18 @@ endif
 if BUILD_PLUGIN_WRITE_KAFKA
 pkglib_LTLIBRARIES += write_kafka.la
 write_kafka_la_SOURCES = write_kafka.c \
-                        utils_format_graphite.c utils_format_graphite.h \
-                        utils_cmd_putval.c utils_cmd_putval.h \
-                        utils_crc32.c utils_crc32.h
+                         utils_crc32.c utils_crc32.h
 write_kafka_la_CPPFLAGS = $(AM_CPPFLAGS) $(BUILD_WITH_LIBRDKAFKA_CPPFLAGS)
 write_kafka_la_LDFLAGS = $(PLUGIN_LDFLAGS) $(BUILD_WITH_LIBRDKAFKA_LDFLAGS)
-write_kafka_la_LIBADD = $(BUILD_WITH_LIBRDKAFKA_LIBS) libformat_json.la
+write_kafka_la_LIBADD = $(BUILD_WITH_LIBRDKAFKA_LIBS) \
+                        libcmds.la libformat_graphite.la libformat_json.la
 endif
 
 if BUILD_PLUGIN_WRITE_LOG
 pkglib_LTLIBRARIES += write_log.la
-write_log_la_SOURCES = write_log.c \
-                        utils_format_graphite.c utils_format_graphite.h
+write_log_la_SOURCES = write_log.c
 write_log_la_LDFLAGS = $(PLUGIN_LDFLAGS)
+write_log_la_LIBADD = libformat_graphite.la libformat_json.la
 endif
 
 if BUILD_PLUGIN_WRITE_MONGODB
@@ -1289,6 +1333,15 @@ write_mongodb_la_LDFLAGS = $(PLUGIN_LDFLAGS) $(BUILD_WITH_LIBMONGOC_LDFLAGS)
 write_mongodb_la_LIBADD = -lmongoc
 endif
 
+if BUILD_PLUGIN_WRITE_PROMETHEUS
+pkglib_LTLIBRARIES += write_prometheus.la
+write_prometheus_la_SOURCES = write_prometheus.c
+nodist_write_prometheus_la_SOURCES = prometheus.pb-c.c prometheus.pb-c.h
+write_prometheus_la_CPPFLAGS = $(AM_CPPFLAGS) $(BUILD_WITH_LIBPROTOBUF_C_CPPFLAGS) $(BUILD_WITH_LIBMICROHTTPD_CPPFLAGS)
+write_prometheus_la_LDFLAGS = $(PLUGIN_LDFLAGS) $(BUILD_WITH_LIBPROTOBUF_C_LDFLAGS) $(BUILD_WITH_LIBMICROHTTPD_LDFLAGS)
+write_prometheus_la_LIBADD = $(BUILD_WITH_LIBPROTOBUF_C_LIBS) $(BUILD_WITH_LIBMICROHTTPD_LIBS)
+endif
+
 if BUILD_PLUGIN_WRITE_REDIS
 pkglib_LTLIBRARIES += write_redis.la
 write_redis_la_SOURCES = write_redis.c
@@ -1431,6 +1484,15 @@ pinba.pb-c.c pinba.pb-c.h: pinba.proto
        $(AM_V_PROTOC_C)$(PROTOC_C) -I$(srcdir) --c_out . $(srcdir)/pinba.proto
 endif
 
+# Protocol buffer for the "write_prometheus" plugin.
+if BUILD_PLUGIN_WRITE_PROMETHEUS
+CLEANFILES += prometheus.pb-c.c prometheus.pb-c.h
+BUILT_SOURCES += prometheus.pb-c.c prometheus.pb-c.h
+
+prometheus.pb-c.c prometheus.pb-c.h: $(top_srcdir)/proto/prometheus.proto
+       $(AM_V_PROTOC_C)$(PROTOC_C) -I$(top_srcdir)/proto --c_out=$(builddir) $(top_srcdir)/proto/prometheus.proto
+endif
+
 install-exec-hook:
        $(mkinstalldirs) $(DESTDIR)$(sysconfdir)
        if test -e $(DESTDIR)$(sysconfdir)/collectd.conf; \
index ec94183..4e20d0c 100644 (file)
@@ -38,7 +38,7 @@
 
 struct aggregation_s /* {{{ */
 {
-  identifier_t ident;
+  lookup_identifier_t ident;
   unsigned int group_by;
 
   unsigned int regex_fields;
@@ -62,7 +62,7 @@ typedef struct agg_instance_s agg_instance_t;
 struct agg_instance_s /* {{{ */
 {
   pthread_mutex_t lock;
-  identifier_t ident;
+  lookup_identifier_t ident;
 
   int ds_type;
 
index 9578e61..c54a1e0 100644 (file)
@@ -570,9 +570,9 @@ static int camqp_read_body(camqp_config_t *conf, /* {{{ */
   } /* while (received < body_size) */
 
   if (strcasecmp("text/collectd", content_type) == 0) {
-    status = handle_putval(stderr, body);
+    status = cmd_handle_putval(stderr, body);
     if (status != 0)
-      ERROR("amqp plugin: handle_putval failed with status %i.", status);
+      ERROR("amqp plugin: cmd_handle_putval failed with status %i.", status);
     return (status);
   } else if (strcasecmp("application/json", content_type) == 0) {
     ERROR("amqp plugin: camqp_read_body: Parsing JSON data has not "
@@ -636,24 +636,20 @@ static void *camqp_subscribe_thread(void *user_data) /* {{{ */
 
     status = camqp_connect(conf);
     if (status != 0) {
-      struct timespec ts_interval;
       ERROR("amqp plugin: camqp_connect failed. "
             "Will sleep for %.3f seconds.",
             CDTIME_T_TO_DOUBLE(interval));
-      CDTIME_T_TO_TIMESPEC(interval, &ts_interval);
-      nanosleep(&ts_interval, /* remaining = */ NULL);
+      nanosleep(&CDTIME_T_TO_TIMESPEC(interval), /* remaining = */ NULL);
       continue;
     }
 
     status = amqp_simple_wait_frame(conf->connection, &frame);
     if (status < 0) {
-      struct timespec ts_interval;
       ERROR("amqp plugin: amqp_simple_wait_frame failed. "
             "Will sleep for %.3f seconds.",
             CDTIME_T_TO_DOUBLE(interval));
       camqp_close_connection(conf);
-      CDTIME_T_TO_TIMESPEC(interval, &ts_interval);
-      nanosleep(&ts_interval, /* remaining = */ NULL);
+      nanosleep(&CDTIME_T_TO_TIMESPEC(interval), /* remaining = */ NULL);
       continue;
     }
 
@@ -695,7 +691,7 @@ static int camqp_subscribe_init(camqp_config_t *conf) /* {{{ */
   memset(tmp, 0, sizeof(*tmp));
 
   status = plugin_thread_create(tmp, /* attr = */ NULL, camqp_subscribe_thread,
-                                conf);
+                                conf, "amqp subscribe");
   if (status != 0) {
     char errbuf[1024];
     ERROR("amqp plugin: pthread_create failed: %s",
@@ -777,9 +773,9 @@ static int camqp_write(const data_set_t *ds, const value_list_t *vl, /* {{{ */
   }
 
   if (conf->format == CAMQP_FORMAT_COMMAND) {
-    status = create_putval(buffer, sizeof(buffer), ds, vl);
+    status = cmd_create_putval(buffer, sizeof(buffer), ds, vl);
     if (status != 0) {
-      ERROR("amqp plugin: create_putval failed with status %i.", status);
+      ERROR("amqp plugin: cmd_create_putval failed with status %i.", status);
       return (status);
     }
   } else if (conf->format == CAMQP_FORMAT_JSON) {
@@ -935,6 +931,10 @@ static int camqp_config_connection(oconfig_item_t *ci, /* {{{ */
     else if ((strcasecmp("GraphiteAlwaysAppendDS", child->key) == 0) && publish)
       status = cf_util_get_flag(child, &conf->graphite_flags,
                                 GRAPHITE_ALWAYS_APPEND_DS);
+    else if ((strcasecmp("GraphitePreserveSeparator", child->key) == 0) &&
+             publish)
+      status = cf_util_get_flag(child, &conf->graphite_flags,
+                                GRAPHITE_PRESERVE_SEPARATOR);
     else if ((strcasecmp("GraphitePrefix", child->key) == 0) && publish)
       status = cf_util_get_string(child, &conf->prefix);
     else if ((strcasecmp("GraphitePostfix", child->key) == 0) && publish)
@@ -980,11 +980,12 @@ static int camqp_config_connection(oconfig_item_t *ci, /* {{{ */
 
   if (publish) {
     char cbname[128];
-    user_data_t ud = {conf, camqp_config_free};
-
     ssnprintf(cbname, sizeof(cbname), "amqp/%s", conf->name);
 
-    status = plugin_register_write(cbname, camqp_write, &ud);
+    status = plugin_register_write(
+        cbname, camqp_write, &(user_data_t){
+                                 .data = conf, .free_func = camqp_config_free,
+                             });
     if (status != 0) {
       camqp_config_free(conf);
       return (status);
index 50784e5..29c36cc 100644 (file)
@@ -217,19 +217,19 @@ static int config_add(oconfig_item_t *ci) {
   }
 
   if (status == 0) {
-    user_data_t ud = {.data = st, .free_func = apache_free};
-
     char callback_name[3 * DATA_MAX_NAME_LEN];
 
     ssnprintf(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 = */ &ud);
+    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) {
@@ -356,8 +356,8 @@ static void submit_value(const char *type, const char *type_instance,
   vl.values = &value;
   vl.values_len = 1;
 
-  sstrncpy(vl.host, (st->host != NULL) ? st->host : hostname_g,
-           sizeof(vl.host));
+  if (st->host != NULL)
+    sstrncpy(vl.host, st->host, sizeof(vl.host));
 
   sstrncpy(vl.plugin, "apache", sizeof(vl.plugin));
   if (st->name != NULL)
@@ -371,17 +371,13 @@ static void submit_value(const char *type, const char *type_instance,
 } /* void submit_value */
 
 static void submit_derive(const char *type, const char *type_instance,
-                          derive_t c, apache_t *st) {
-  value_t v;
-  v.derive = c;
-  submit_value(type, type_instance, v, st);
+                          derive_t d, apache_t *st) {
+  submit_value(type, type_instance, (value_t){.derive = d}, st);
 } /* void submit_derive */
 
 static void submit_gauge(const char *type, const char *type_instance, gauge_t g,
                          apache_t *st) {
-  value_t v;
-  v.gauge = g;
-  submit_value(type, type_instance, v, st);
+  submit_value(type, type_instance, (value_t){.gauge = g}, st);
 } /* void submit_gauge */
 
 static void submit_scoreboard(char *buf, apache_t *st) {
index 7a8aff4..3b8b03c 100644 (file)
@@ -385,19 +385,13 @@ static int apcups_config(oconfig_item_t *ci) {
 
 static void apc_submit_generic(const char *type, const char *type_inst,
                                gauge_t value) {
-  value_t values[1];
-  value_list_t vl = VALUE_LIST_INIT;
-
   if (isnan(value))
     return;
 
-  values[0].gauge = value;
-
-  vl.values = values;
+  value_list_t vl = VALUE_LIST_INIT;
+  vl.values = &(value_t){.gauge = value};
   vl.values_len = 1;
-  sstrncpy(vl.host, hostname_g, sizeof(vl.host));
   sstrncpy(vl.plugin, "apcups", sizeof(vl.plugin));
-  sstrncpy(vl.plugin_instance, "", sizeof(vl.plugin_instance));
   sstrncpy(vl.type, type, sizeof(vl.type));
   sstrncpy(vl.type_instance, type_inst, sizeof(vl.type_instance));
 
index d15bec4..2f8cccd 100644 (file)
@@ -76,18 +76,11 @@ static int as_init(void) {
 }
 
 static void as_submit(const char *type, const char *type_instance, double val) {
-  value_t values[1];
   value_list_t vl = VALUE_LIST_INIT;
 
-  DEBUG("type = %s; type_instance = %s; val = %f;", type, type_instance, val);
-
-  values[0].gauge = val;
-
-  vl.values = values;
+  vl.values = &(value_t){.gauge = val};
   vl.values_len = 1;
-  sstrncpy(vl.host, hostname_g, sizeof(vl.host));
   sstrncpy(vl.plugin, "apple_sensors", sizeof(vl.plugin));
-  sstrncpy(vl.plugin_instance, "", sizeof(vl.plugin_instance));
   sstrncpy(vl.type, type, sizeof(vl.type));
   sstrncpy(vl.type_instance, type_instance, sizeof(vl.type_instance));
 
index 412f300..4a78f68 100644 (file)
@@ -54,19 +54,15 @@ static int aquaero_shutdown(void) {
 static void aquaero_submit(const char *type, const char *type_instance,
                            double value) {
   const char *instance = conf_device ? conf_device : "default";
-  value_t values[1];
   value_list_t vl = VALUE_LIST_INIT;
 
   /* Don't report undefined values. */
   if (value == AQ5_FLOAT_UNDEF)
     return;
 
-  values[0].gauge = value;
-
-  vl.values = values;
+  vl.values = &(value_t){.gauge = value};
   vl.values_len = 1;
 
-  sstrncpy(vl.host, hostname_g, sizeof(vl.host));
   sstrncpy(vl.plugin, "aquaero", sizeof(vl.plugin));
   sstrncpy(vl.plugin_instance, instance, sizeof(vl.plugin_instance));
   sstrncpy(vl.type, type, sizeof(vl.type));
index c41c896..74cef33 100644 (file)
@@ -110,14 +110,10 @@ static int config_keys_num = STATIC_ARRAY_SIZE(config_keys);
 static int ascent_submit_gauge(const char *plugin_instance, /* {{{ */
                                const char *type, const char *type_instance,
                                gauge_t value) {
-  value_t values[1];
   value_list_t vl = VALUE_LIST_INIT;
 
-  values[0].gauge = value;
-
-  vl.values = values;
+  vl.values = &(value_t){.gauge = value};
   vl.values_len = 1;
-  sstrncpy(vl.host, hostname_g, sizeof(vl.host));
   sstrncpy(vl.plugin, "ascent", sizeof(vl.plugin));
 
   if (plugin_instance != NULL)
index 2983779..f698005 100644 (file)
@@ -1416,7 +1416,6 @@ static int MPL115_collectd_barometer_read(void) {
 
   norm_pressure = abs_to_mean_sea_level_pressure(pressure);
 
-  sstrncpy(vl.host, hostname_g, sizeof(vl.host));
   sstrncpy(vl.plugin, "barometer", sizeof(vl.plugin));
   sstrncpy(vl.plugin_instance, "mpl115", sizeof(vl.plugin_instance));
 
@@ -1476,7 +1475,6 @@ static int MPL3115_collectd_barometer_read(void) {
 
   norm_pressure = abs_to_mean_sea_level_pressure(pressure);
 
-  sstrncpy(vl.host, hostname_g, sizeof(vl.host));
   sstrncpy(vl.plugin, "barometer", sizeof(vl.plugin));
   sstrncpy(vl.plugin_instance, "mpl3115", sizeof(vl.plugin_instance));
 
@@ -1536,7 +1534,6 @@ static int BMP085_collectd_barometer_read(void) {
 
   norm_pressure = abs_to_mean_sea_level_pressure(pressure);
 
-  sstrncpy(vl.host, hostname_g, sizeof(vl.host));
   sstrncpy(vl.plugin, "barometer", sizeof(vl.plugin));
   sstrncpy(vl.plugin_instance, "bmp085", sizeof(vl.plugin_instance));
 
index 48691ce..78e96b2 100644 (file)
 #define SYSFS_FACTOR 0.000001
 #endif /* KERNEL_LINUX */
 
+int battery_read_statefs(
+    void); /* defined in battery_statefs; used by StateFS backend */
+
 static _Bool report_percent = 0;
 static _Bool report_degraded = 0;
+static _Bool query_statefs = 0;
 
 static void battery_submit2(char const *plugin_instance, /* {{{ */
                             char const *type, char const *type_instance,
                             gauge_t value) {
-  value_t values[1];
   value_list_t vl = VALUE_LIST_INIT;
 
-  values[0].gauge = value;
-
-  vl.values = values;
+  vl.values = &(value_t){.gauge = value};
   vl.values_len = 1;
-  sstrncpy(vl.host, hostname_g, sizeof(vl.host));
   sstrncpy(vl.plugin, "battery", sizeof(vl.plugin));
   sstrncpy(vl.plugin_instance, plugin_instance, sizeof(vl.plugin_instance));
   sstrncpy(vl.type, type, sizeof(vl.type));
@@ -316,6 +316,9 @@ static int battery_read(void) /* {{{ */
   gauge_t capacity_full = NAN;   /* Total capacity */
   gauge_t capacity_design = NAN; /* Full design capacity */
 
+  if (query_statefs)
+    return battery_read_statefs();
+
 #if HAVE_IOKIT_PS_IOPOWERSOURCES_H
   get_via_io_power_sources(&charge_rel, &current, &voltage);
 #endif
@@ -341,43 +344,19 @@ static int battery_read(void) /* {{{ */
 static int sysfs_file_to_buffer(char const *dir, /* {{{ */
                                 char const *power_supply, char const *basename,
                                 char *buffer, size_t buffer_size) {
-  int status;
-  FILE *fp;
   char filename[PATH_MAX];
+  int status;
 
   ssnprintf(filename, sizeof(filename), "%s/%s/%s", dir, power_supply,
             basename);
 
-  /* No file isn't the end of the world -- not every system will be
-   * reporting the same set of statistics */
-  if (access(filename, R_OK) != 0)
-    return ENOENT;
-
-  fp = fopen(filename, "r");
-  if (fp == NULL) {
-    status = errno;
-    if (status != ENOENT) {
-      char errbuf[1024];
-      WARNING("battery plugin: fopen (%s) failed: %s", filename,
-              sstrerror(status, errbuf, sizeof(errbuf)));
-    }
+  status = (int)read_file_contents(filename, buffer, buffer_size - 1);
+  if (status < 0)
     return status;
-  }
 
-  if (fgets(buffer, buffer_size, fp) == NULL) {
-    status = errno;
-    if (status != ENODEV) {
-      char errbuf[1024];
-      WARNING("battery plugin: fgets (%s) failed: %s", filename,
-              sstrerror(status, errbuf, sizeof(errbuf)));
-    }
-    fclose(fp);
-    return status;
-  }
+  buffer[status] = '\0';
 
   strstripnewline(buffer);
-
-  fclose(fp);
   return 0;
 } /* }}} int sysfs_file_to_buffer */
 
@@ -387,7 +366,7 @@ static int sysfs_file_to_gauge(char const *dir, /* {{{ */
                                char const *power_supply, char const *basename,
                                gauge_t *ret_value) {
   int status;
-  char buffer[32] = "";
+  char buffer[32];
 
   status =
       sysfs_file_to_buffer(dir, power_supply, basename, buffer, sizeof(buffer));
@@ -711,6 +690,9 @@ static int battery_read(void) /* {{{ */
 {
   int status;
 
+  if (query_statefs)
+    return battery_read_statefs();
+
   DEBUG("battery plugin: Trying sysfs ...");
   status = read_sysfs();
   if (status == 0)
@@ -739,6 +721,8 @@ static int battery_config(oconfig_item_t *ci) {
       cf_util_get_boolean(child, &report_percent);
     else if (strcasecmp("ReportDegraded", child->key) == 0)
       cf_util_get_boolean(child, &report_degraded);
+    else if (strcasecmp("QueryStateFS", child->key) == 0)
+      cf_util_get_boolean(child, &query_statefs);
     else
       WARNING("battery plugin: Ignoring unknown "
               "configuration option \"%s\".",
diff --git a/src/battery_statefs.c b/src/battery_statefs.c
new file mode 100644 (file)
index 0000000..4b9baf4
--- /dev/null
@@ -0,0 +1,122 @@
+/**
+ * collectd - src/statefs_battery.c
+ * Copyright (C) 2016 rinigus
+ *
+ *
+The MIT License (MIT)
+
+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:
+ *   rinigus <http://github.com/rinigus>
+
+ Battery stats are collected from StateFS Battery namespace. Reported
+ units are as follows:
+
+ capacity %
+ charge %
+ current A
+ energy Wh
+ power W
+ temperature C
+ timefull and timelow seconds
+ voltage V
+
+ Provider at
+ https://git.merproject.org/mer-core/statefs-providers/blob/master/src/power_udev/provider_power_udev.cpp
+
+ **/
+
+#include "common.h"
+#include "plugin.h"
+#include "collectd.h"
+
+#include <stdio.h>
+
+#define STATEFS_ROOT "/run/state/namespaces/Battery/"
+
+static void battery_submit(const char *type, gauge_t value,
+                           const char *type_instance) {
+  value_list_t vl = VALUE_LIST_INIT;
+
+  vl.values = &(value_t){.gauge = value};
+  vl.values_len = 1;
+  sstrncpy(vl.plugin, "battery", sizeof(vl.plugin));
+  /* statefs supports 1 battery at present */
+  sstrncpy(vl.plugin_instance, "0", sizeof(vl.plugin_instance));
+  sstrncpy(vl.type, type, sizeof(vl.type));
+  if (type_instance != NULL)
+    sstrncpy(vl.type_instance, type_instance, sizeof(vl.type_instance));
+  plugin_dispatch_values(&vl);
+}
+
+/* cannot be static, is referred to from battery.c */
+int battery_read_statefs(void) {
+  value_t v;
+  int success = 0;
+
+  if (parse_value_file(STATEFS_ROOT "ChargePercentage", &v, DS_TYPE_GAUGE) ==
+      0) {
+    battery_submit("charge", v.gauge, NULL);
+    success++;
+  } else if (parse_value_file(STATEFS_ROOT "Capacity", &v, DS_TYPE_GAUGE) ==
+             0) {
+    // Use capacity as a charge estimate if ChargePercentage is not available
+    battery_submit("charge", v.gauge, NULL);
+    success++;
+  } else {
+    WARNING("battery plugin: Neither \"" STATEFS_ROOT "ChargePercentage\" "
+            "nor \"" STATEFS_ROOT "Capacity\" could be read.");
+  }
+
+  struct {
+    char *path;
+    char *type;
+    char *type_instance;
+    gauge_t factor;
+  } metrics[] = {
+      {STATEFS_ROOT "Current", "current", NULL, 1e-6},        // from uA to A
+      {STATEFS_ROOT "Energy", "energy_wh", NULL, 1e-6},       // from uWh to Wh
+      {STATEFS_ROOT "Power", "power", NULL, 1e-6},            // from uW to W
+      {STATEFS_ROOT "Temperature", "temperature", NULL, 0.1}, // from 10xC to C
+      {STATEFS_ROOT "TimeUntilFull", "duration", "full", 1.0},
+      {STATEFS_ROOT "TimeUntilLow", "duration", "low", 1.0},
+      {STATEFS_ROOT "Voltage", "voltage", NULL, 1e-6}, // from uV to V
+  };
+
+  for (size_t i = 0; i < STATIC_ARRAY_SIZE(metrics); i++) {
+    if (parse_value_file(metrics[i].path, &v, DS_TYPE_GAUGE) != 0) {
+      WARNING("battery plugin: Reading \"%s\" failed.", metrics[i].path);
+      continue;
+    }
+
+    battery_submit(metrics[i].type, v.gauge * metrics[i].factor,
+                   metrics[i].type_instance);
+    success++;
+  }
+
+  if (success == 0) {
+    ERROR("battery plugin: statefs backend: none of the statistics are "
+          "available");
+    return (-1);
+  }
+
+  return (0);
+}
index fc1fd92..8a3148c 100644 (file)
@@ -138,7 +138,7 @@ static const translation_info_t nsstats_translation_table[] = /* {{{ */
         {"ReqBadSIG", "dns_request", "BadSIG"},
         {"ReqTCP", "dns_request", "TCP"},
         /* Rejects */
-        {"AuthQryRej", "dns_reject", "authorative"},
+        {"AuthQryRej", "dns_reject", "authoritative"},
         {"RecQryRej", "dns_reject", "recursive"},
         {"XfrRej", "dns_reject", "transfer"},
         {"UpdateRej", "dns_reject", "update"},
@@ -149,11 +149,11 @@ static const translation_info_t nsstats_translation_table[] = /* {{{ */
         {"RespTSIG", "dns_response", "TSIG"},
         {"RespSIG0", "dns_response", "SIG0"},
         /* Queries */
-        {"QryAuthAns", "dns_query", "authorative"},
+        {"QryAuthAns", "dns_query", "authoritative"},
         {"QryNoauthAns", "dns_query", "nonauth"},
         {"QryReferral", "dns_query", "referral"},
         {"QryRecursion", "dns_query", "recursion"},
-        {"QryDuplicate", "dns_query", "dupliate"},
+        {"QryDuplicate", "dns_query", "duplicate"},
         {"QryDropped", "dns_query", "dropped"},
         {"QryFailure", "dns_query", "failure"},
         /* Response codes */
@@ -163,13 +163,13 @@ static const translation_info_t nsstats_translation_table[] = /* {{{ */
         {"QryFORMERR", "dns_rcode", "tx-FORMERR"},
         {"QryNXDOMAIN", "dns_rcode", "tx-NXDOMAIN"}
 #if 0
-  { "XfrReqDone",      "type", "type_instance"       },
-  { "UpdateReqFwd",    "type", "type_instance"       },
-  { "UpdateRespFwd",   "type", "type_instance"       },
-  { "UpdateFwdFail",   "type", "type_instance"       },
-  { "UpdateDone",      "type", "type_instance"       },
-  { "UpdateFail",      "type", "type_instance"       },
-  { "UpdateBadPrereq", "type", "type_instance"       },
+  { "XfrReqDone",      "type",         "type_instance" },
+  { "UpdateReqFwd",    "type",         "type_instance" },
+  { "UpdateRespFwd",   "type",         "type_instance" },
+  { "UpdateFwdFail",   "type",         "type_instance" },
+  { "UpdateDone",      "type",         "type_instance" },
+  { "UpdateFail",      "type",         "type_instance" },
+  { "UpdateBadPrereq", "type",         "type_instance" },
 #endif
 };
 static int nsstats_translation_table_length =
@@ -246,16 +246,12 @@ static int memsummary_translation_table_length =
 
 static void submit(time_t ts, const char *plugin_instance, /* {{{ */
                    const char *type, const char *type_instance, value_t value) {
-  value_t values[1];
   value_list_t vl = VALUE_LIST_INIT;
 
-  values[0] = value;
-
-  vl.values = values;
+  vl.values = &value;
   vl.values_len = 1;
   if (config_parse_time)
     vl.time = TIME_T_TO_CDTIME_T(ts);
-  sstrncpy(vl.host, hostname_g, sizeof(vl.host));
   sstrncpy(vl.plugin, "bind", sizeof(vl.plugin));
   if (plugin_instance) {
     sstrncpy(vl.plugin_instance, plugin_instance, sizeof(vl.plugin_instance));
index 2be17fb..4e435b3 100644 (file)
@@ -1027,7 +1027,6 @@ static int cconn_process_data(struct cconn *io, yajl_struct *yajl,
   }
 
   vtmp->vlist = (value_list_t)VALUE_LIST_INIT;
-  sstrncpy(vtmp->vlist.host, hostname_g, sizeof(vtmp->vlist.host));
   sstrncpy(vtmp->vlist.plugin, "ceph", sizeof(vtmp->vlist.plugin));
   sstrncpy(vtmp->vlist.plugin_instance, io->d->name,
            sizeof(vtmp->vlist.plugin_instance));
@@ -1301,15 +1300,22 @@ static int cconn_main_loop(uint32_t request_type) {
   struct timeval end_tv;
   struct cconn io_array[g_num_daemons];
 
-  DEBUG("ceph plugin: entering cconn_main_loop(request_type = %d)",
+  DEBUG("ceph plugin: entering cconn_main_loop(request_type = %" PRIu32 ")",
         request_type);
 
+  if (g_num_daemons < 1) {
+    ERROR("ceph plugin: No daemons configured. See the \"Daemon\" config "
+          "option.");
+    return ENOENT;
+  }
+
   /* create cconn array */
-  memset(io_array, 0, sizeof(io_array));
-  for (size_t i = 0; i < g_num_daemons; ++i) {
-    io_array[i].d = g_daemons[i];
-    io_array[i].request_type = request_type;
-    io_array[i].state = CSTATE_UNCONNECTED;
+  for (size_t i = 0; i < g_num_daemons; i++) {
+    io_array[i] = (struct cconn){
+        .d = g_daemons[i],
+        .request_type = request_type,
+        .state = CSTATE_UNCONNECTED,
+    };
   }
 
   /** Calculate the time at which we should give up */
index 97ec64b..3f7d4f4 100644 (file)
@@ -40,7 +40,6 @@ cgroups_submit_one(char const *plugin_instance, char const *type_instance,
 
   vl.values = &value;
   vl.values_len = 1;
-  sstrncpy(vl.host, hostname_g, sizeof(vl.host));
   sstrncpy(vl.plugin, "cgroups", sizeof(vl.plugin));
   sstrncpy(vl.plugin_instance, plugin_instance, sizeof(vl.plugin_instance));
   sstrncpy(vl.type, "cpu", sizeof(vl.type));
index a10e5f6..11310b1 100644 (file)
@@ -598,21 +598,13 @@ static double ntohf(tFloat p_float) {
 
 static void chrony_push_data(const char *p_type, const char *p_type_inst,
                              double p_value) {
-  value_t values[1];
   value_list_t vl = VALUE_LIST_INIT;
 
-  values[0].gauge =
-      p_value; /* TODO: Check type??? (counter, gauge, derive, absolute) */
-
-  vl.values = values;
+  vl.values = &(value_t){.gauge = p_value};
   vl.values_len = 1;
 
   /* XXX: Shall g_chrony_host/g_chrony_port be reflected in the plugin's output?
    */
-  /* hostname_g is set in daemon/collectd.c (from config, via gethostname or by
-   * resolving localhost) */
-  /* defined as: char hostname_g[DATA_MAX_NAME_LEN]; (never NULL) */
-  sstrncpy(vl.host, hostname_g, sizeof(vl.host));
   sstrncpy(vl.plugin, PLUGIN_NAME_SHORT, sizeof(vl.plugin));
   if (g_chrony_plugin_instance != NULL) {
     sstrncpy(vl.plugin_instance, g_chrony_plugin_instance,
index 0102e92..9af3c0c 100644 (file)
@@ -75,6 +75,20 @@ Adds I<Dir> to the B<@INC> array. This is the same as using the B<-IDir>
 command line option or B<use lib Dir> in the source code. Please note that it
 only has effect on plugins loaded after this option.
 
+=item B<RegisterLegacyFlush> I<true|false>
+
+The C<Perl plugin> used to register one flush callback (called B<"perl">) and
+call all Perl-based flush handlers when this callback was called. Newer versions
+of the plugin wrap the Perl flush handlers and register them directly with the
+daemon I<in addition> to the legacy B<"perl"> callback. This allows to call
+specific Perl flush handlers, but has the downside that flushing I<all> plugins
+now calls the Perl flush handlers twice (once directly and once via the legacy
+callback). Unfortunately, removing the B<"perl"> callback would break backwards
+compatibility.
+
+This option allows you to disable the legacy B<"perl"> flush callback if you care
+about the double call and don't call the B<"perl"> callback in your setup.
+
 =back
 
 =head1 WRITING YOUR OWN PLUGINS
@@ -759,18 +773,6 @@ dispatched by the perl plugin after upgrades.
 
 =back
 
-=head1 KNOWN BUGS
-
-=over 4
-
-=item *
-
-Currently, it is not possible to flush a single Perl plugin only. You can
-either flush all Perl plugins or none at all and you have to use C<perl> as
-plugin name when doing so.
-
-=back
-
 =head1 SEE ALSO
 
 L<collectd(1)>,
index f0ac287..e5b9643 100644 (file)
 #@BUILD_PLUGIN_DF_TRUE@LoadPlugin df
 #@BUILD_PLUGIN_DISK_TRUE@LoadPlugin disk
 #@BUILD_PLUGIN_DNS_TRUE@LoadPlugin dns
+#@BUILD_PLUGIN_DPDKSTAT_TRUE@LoadPlugin dpdkstat
 #@BUILD_PLUGIN_DRBD_TRUE@LoadPlugin drbd
 #@BUILD_PLUGIN_EMAIL_TRUE@LoadPlugin email
 #@BUILD_PLUGIN_ENTROPY_TRUE@LoadPlugin entropy
 #@BUILD_PLUGIN_GPS_TRUE@LoadPlugin gps
 #@BUILD_PLUGIN_GRPC_TRUE@LoadPlugin grpc
 #@BUILD_PLUGIN_HDDTEMP_TRUE@LoadPlugin hddtemp
+#@BUILD_PLUGIN_HUGEPAGES_TRUE@LoadPlugin hugepages
+#@BUILD_PLUGIN_INTEL_RDT_TRUE@LoadPlugin intel_rdt
 @BUILD_PLUGIN_INTERFACE_TRUE@@BUILD_PLUGIN_INTERFACE_TRUE@LoadPlugin interface
 #@BUILD_PLUGIN_IPC_TRUE@LoadPlugin ipc
 #@BUILD_PLUGIN_IPMI_TRUE@LoadPlugin ipmi
 #@BUILD_PLUGIN_WRITE_KAFKA_TRUE@LoadPlugin write_kafka
 #@BUILD_PLUGIN_WRITE_LOG_TRUE@LoadPlugin write_log
 #@BUILD_PLUGIN_WRITE_MONGODB_TRUE@LoadPlugin write_mongodb
+#@BUILD_PLUGIN_WRITE_PROMETHEUS_TRUE@LoadPlugin write_prometheus
 #@BUILD_PLUGIN_WRITE_REDIS_TRUE@LoadPlugin write_redis
 #@BUILD_PLUGIN_WRITE_RIEMANN_TRUE@LoadPlugin write_riemann
 #@BUILD_PLUGIN_WRITE_SENSU_TRUE@LoadPlugin write_sensu
 #<Plugin "battery">
 #  ValuesPercentage false
 #  ReportDegraded false
+#  QueryStateFS false
 #</Plugin>
 
 #<Plugin "bind">
 #      SelectNumericQueryTypes true
 #</Plugin>
 
+#<Plugin dpdkstat>
+#       Interval 1
+#       Coremask "0xf"
+#       ProcessType "secondary"
+#       FilePrefix "rte"
+#       EnabledPortMask 0xffff
+#       PortName "interface1"
+#       PortName "interface2"
+#</Plugin>
+
 #<Plugin email>
 #      SocketFile "@localstatedir@/run/@PACKAGE_NAME@-email"
 #      SocketGroup "collectd"
 #  Port "7634"
 #</Plugin>
 
+#<Plugin hugepages>
+#    ReportPerNodeHP  true
+#    ReportRootHP     true
+#    ValuesPages      true
+#    ValuesBytes      false
+#    ValuesPercentage false
+#</Plugin>
+
+#<Plugin "intel_rdt">
+#  Cores "0-2"
+#</Plugin>
+
 #<Plugin interface>
 #      Interface "eth0"
 #      IgnoreSelected false
 
 #<Plugin memcached>
 #      <Instance "local">
-#              Host "127.0.0.1"
+#              #Host "memcache.example.com"
+#              Address "127.0.0.1"
 #              Port "11211"
 #      </Instance>
 #</Plugin>
 #      Instance "local_user"
 #    </Match>
 #  </File>
+#  <File "/var/log/nginx/apache-time.log">
+#    #Use the following log format in nginx:
+#    #log_format response_time '[$host] "$upstream_response_time" ...'
+#    Instance "apache"
+#    <Match>
+#      Regex "^\\S+ \"([0-9.]+)\""
+#      <DSType Distribution>
+#        Percentile 80    # -> latency-foo-80
+#        Percentile 95    # -> latency-foo-95
+#        Percentile 99    # -> latency-foo-99
+#        Bucket 0   0.1   # -> bucket-latency-foo-0_0.1
+#        Bucket 0.1 0.2   # -> bucket-latency-foo-0.1_0.2
+#        Bucket 0.2 0.5   # -> bucket-latency-foo-0.2_0.5
+#        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
+#      </DSType>
+#      Type "latency"
+#      Instance "foo"
+#    </Match>
+#  </File>
 #</Plugin>
 
 #<Plugin tail_csv>
 #      RefreshInterval 60
 #      Domain "name"
 #      BlockDevice "name:device"
+#      BlockDeviceFormat target
+#      BlockDeviceFormatBasename false
 #      InterfaceDevice "name:device"
 #      IgnoreSelected false
 #      HostnameFormat name
 #    AlwaysAppendDS false
 #    EscapeCharacter "_"
 #    SeparateInstances false
+#    PreserveSeparator false
+#    DropDuplicateFields false
 #  </Node>
 #</Plugin>
 
 #      </Node>
 #</Plugin>
 
+#<Plugin write_prometheus>
+#      Port "9103"
+#</Plugin>
+
 #<Plugin write_redis>
 #      <Node "example">
 #              Host "localhost"
index f88430a..881ae20 100644 (file)
@@ -555,6 +555,7 @@ B<Synopsis:>
  #   GraphiteEscapeChar "_"
  #   GraphiteSeparateInstances false
  #   GraphiteAlwaysAppendDS false
+ #   GraphitePreserveSeparator false
    </Publish>
 
    # Receive values from an AMQP broker
@@ -729,6 +730,12 @@ If set to B<true>, append the name of the I<Data Source> (DS) to the "metric"
 identifier. If set to B<false> (the default), this is only done when there is
 more than one DS.
 
+=item B<GraphitePreserveSeparator> B<false>|B<true>
+
+If set to B<false> (the default) the C<.> (dot) character is replaced with
+I<GraphiteEscapeChar>. Otherwise, if set to B<true>, the C<.> (dot) character
+is preserved, i.e. passed through.
+
 =back
 
 =head2 Plugin C<apache>
@@ -1114,6 +1121,13 @@ When set to B<true>, the battery plugin will report three values: B<charged>
 and "remaining capacity") and B<degraded> (difference between "design capacity"
 and "last full capacity").
 
+=item B<QueryStateFS> B<false>|B<true>
+
+When set to B<true>, the battery plugin will only read statistics
+related to battery performance as exposed by StateFS at
+/run/state. StateFS is used in Mer-based Sailfish OS, for
+example.
+
 =back
 
 =head2 Plugin C<bind>
@@ -2379,6 +2393,67 @@ Enabled by default, collects unknown (and thus presented as numeric only) query
 
 =back
 
+=head2 Plugin C<dpdkstat>
+
+The I<dpdkstat plugin> collects information about DPDK interfaces using the
+extended NIC stats API in DPDK.
+
+B<Synopsis:>
+
+ <Plugin "dpdkstat">
+    Coremask "0x4"
+    MemoryChannels "4"
+    ProcessType "secondary"
+    FilePrefix "rte"
+    EnabledPortMask 0xffff
+    PortName "interface1"
+    PortName "interface2"
+ </Plugin>
+
+B<Options:>
+
+=over 4
+
+=item B<Coremask> I<Mask>
+
+A string containing an hexadecimal bit mask of the cores to run on. Note that
+core numbering can change between platforms and should be determined beforehand.
+
+=item B<Memorychannels> I<Channels>
+
+A string containing a number of memory channels per processor socket.
+
+=item B<ProcessType> I<type>
+
+A string containing the type of DPDK process instance.
+
+=item B<FilePrefix> I<File>
+
+The prefix text used for hugepage filenames. The filename will be set to
+/var/run/.<prefix>_config where prefix is what is passed in by the user.
+
+=item B<SocketMemory> I<MB>
+
+A string containing amount of Memory to allocate from hugepages on specific
+sockets in MB
+
+=item B<EnabledPortMask> I<Mask>
+
+A hexidecimal bit mask of the DPDK ports which should be enabled. A mask
+of 0x0 means that all ports will be disabled. A bitmask of all Fs means
+that all ports will be enabled. This is an optional argument - default
+is all ports enabled.
+
+=item B<PortName> I<Name>
+
+A string containing an optional name for the enabled DPDK ports. Each PortName
+option should contain only one port name; specify as many PortName options as
+desired. Default naming convention will be used if PortName is blank. If there
+are less PortName options than there are enabled ports, the default naming
+convention will be used for the additional ports.
+
+=back
+
 =head2 Plugin C<email>
 
 =over 4
@@ -2829,6 +2904,101 @@ TCP-Port to connect to. Defaults to B<7634>.
 
 =back
 
+=head2 Plugin C<hugepages>
+
+To collect B<hugepages> information, collectd reads directories
+"/sys/devices/system/node/*/hugepages" and
+"/sys/kernel/mm/hugepages".
+Reading of these directories can be disabled by the following
+options (default is enabled).
+
+=over 4
+
+=item B<ReportPerNodeHP> B<true>|B<false>
+
+If enabled, information will be collected from the hugepage
+counters in "/sys/devices/system/node/*/hugepages".
+This is used to check the per-node hugepage statistics on
+a NUMA system.
+
+=item B<ReportRootHP> B<true>|B<false>
+
+If enabled, information will be collected from the hugepage
+counters in "/sys/kernel/mm/hugepages".
+This can be used on both NUMA and non-NUMA systems to check
+the overall hugepage statistics.
+
+=item B<ValuesPages> B<true>|B<false>
+
+Whether to report hugepages metrics in number of pages.
+Defaults to B<true>.
+
+=item B<ValuesBytes> B<false>|B<true>
+
+Whether to report hugepages metrics in bytes.
+Defaults to B<false>.
+
+=item B<ValuesPercentage> B<false>|B<true>
+
+Whether to report hugepages metrics as percentage.
+Defaults to B<false>.
+
+=back
+
+=head2 Plugin C<intel_rdt>
+
+The I<intel_rdt> plugin collects information provided by monitoring features of
+Intel Resource Director Technology (Intel(R) RDT) like Cache Monitoring
+Technology (CMT), Memory Bandwidth Monitoring (MBM). These features provide
+information about utilization of shared resources. CMT monitors last level cache
+occupancy (LLC). MBM supports two types of events reporting local and remote
+memory bandwidth. Local memory bandwidth (MBL) reports the bandwidth of
+accessing memory associated with the local socket. Remote memory bandwidth (MBR)
+reports the bandwidth of accessing the remote socket. Also this technology
+allows to monitor instructions per clock (IPC).
+Monitor events are hardware dependant. Monitoring capabilities are detected on
+plugin initialization and only supported events are monitored.
+
+B<Synopsis:>
+
+  <Plugin "intel_rdt">
+    Cores "0-2" "3,4,6" "8-10,15"
+  </Plugin>
+
+B<Options:>
+
+=over 4
+
+=item B<Interval> I<seconds>
+
+The interval within which to retrieve statistics on monitored events in seconds.
+For milliseconds divide the time by 1000 for example if the desired interval
+is 50ms, set interval to 0.05. Due to limited capacity of counters it is not
+recommended to set interval higher than 1 sec.
+
+=item B<Cores> I<cores groups>
+
+All events are reported on a per core basis. Monitoring of the events can be
+configured for group of cores (aggregated statistics). This field defines groups
+of cores on which to monitor supported events. The field is represented as list
+of strings with core group values. Each string represents a list of cores in a
+group. Allowed formats are:
+    0,1,2,3
+    0-10,20-18
+    1,3,5-8,10,0x10-12
+
+If an empty string is provided as value for this field default cores
+configuration is applied - a separate group is created for each core.
+
+=back
+
+B<Note:> By default global interval is used to retrieve statistics on monitored
+events. To configure a plugin specific interval use B<Interval> option of the
+intel_rdt <LoadPlugin> block. For milliseconds divide the time by 1000 for
+example if the desired interval is 50ms, set interval to 0.05.
+Due to limited capacity of counters it is not recommended to set interval higher
+than 1 sec.
+
 =head2 Plugin C<interface>
 
 =over 4
@@ -3250,11 +3420,12 @@ interpreted. For a description of match blocks, please see L<"Plugin tail">.
 
 The B<memcached plugin> connects to a memcached server and queries statistics
 about cache utilization, memory and bandwidth used.
-L<http://www.danga.com/memcached/>
+L<http://memcached.org/>
 
  <Plugin "memcached">
    <Instance "name">
-     Host "memcache.example.com"
+     #Host "memcache.example.com"
+     Address "127.0.0.1"
      Port 11211
    </Instance>
  </Plugin>
@@ -3267,16 +3438,25 @@ following options are allowed:
 
 =item B<Host> I<Hostname>
 
-Hostname to connect to. Defaults to B<127.0.0.1>.
+Sets the B<host> field of dispatched values. Defaults to the global hostname
+setting.
+For backwards compatibility, values are also dispatched with the global
+hostname when B<Host> is set to B<127.0.0.1> or B<localhost> and B<Address> is
+not set.
+
+=item B<Address> I<Address>
+
+Hostname or IP to connect to. For backwards compatibility, defaults to the
+value of B<Host> or B<127.0.0.1> if B<Host> is unset.
 
 =item B<Port> I<Port>
 
-TCP-Port to connect to. Defaults to B<11211>.
+TCP port to connect to. Defaults to B<11211>.
 
 =item B<Socket> I<Path>
 
 Connect to I<memcached> using the UNIX domain socket at I<Path>. If this
-setting is given, the B<Host> and B<Port> settings are ignored.
+setting is given, the B<Address> and B<Port> settings are ignored.
 
 =back
 
@@ -5723,7 +5903,7 @@ values are made available through those parameters:
 
 =item B<$1>
 
-The timestamp of the queried value as a floating point number.
+The timestamp of the queried value as an RFC 3339-formatted local time.
 
 =item B<$2>
 
@@ -6939,6 +7119,15 @@ user using (extended) regular expressions, as described in L<regex(7)>.
         Type "counter"
         Instance "local_user"
       </Match>
+      <Match>
+        Regex "l=([0-9]*\\.[0-9]*)"
+        <DSType "Distribution">
+          Percentile 99
+          Bucket 0 100
+        </DSType>
+        Type "latency"
+        Instance "foo"
+      </Match>
     </File>
   </Plugin>
 
@@ -7001,6 +7190,13 @@ Use the greatest number only.
 
 Use the last number found.
 
+=item B<GaugePersist>
+
+Use the last number found. The number is not reset at the end of an interval.
+It is continously reported until another number is matched. This is intended
+for cases in which only state changes are reported, for example a thermometer
+that only reports the temperature when it changes.
+
 =item B<CounterSet>
 
 =item B<DeriveSet>
@@ -7030,14 +7226,74 @@ Increase the internal counter by one. These B<DSType> are the only ones that do
 not use the matched subexpression, but simply count the number of matched
 lines. Thus, you may use a regular expression without submatch in this case.
 
+=item B<Distribution>
+
+Type to do calculations based on the distribution of values, primarily
+calculating percentiles. This is primarily geared towards latency, but can be
+used for other metrics as well. The range of values tracked with this setting
+must be in the range (0–2^34) and can be fractional. Please note that neither
+zero nor 2^34 are inclusive bounds, i.e. zero I<cannot> be handled by a
+distribution.
+
+This option must be used together with the B<Percentile> and/or B<Bucket>
+options.
+
+B<Synopsis:>
+
+  <DSType "Distribution">
+    Percentile 99
+    Bucket 0 100
+  </DSType>
+
+=over 4
+
+=item B<Percentile> I<Percent>
+
+Calculate and dispatch the configured percentile, i.e. compute the value, so
+that I<Percent> of all matched values are smaller than or equal to the computed
+latency.
+
+Metrics are reported with the I<type> B<Type> (the value of the above option)
+and the I<type instance> C<[E<lt>InstanceE<gt>-]E<lt>PercentE<gt>>.
+
+This option may be repeated to calculate more than one percentile.
+
+=item B<Bucket> I<lower_bound> I<upper_bound>
+
+Export the number of values (a C<DERIVE>) falling within the given range. Both,
+I<lower_bound> and I<upper_bound> may be a fractional number, such as B<0.5>.
+Each B<Bucket> option specifies an interval C<(I<lower_bound>,
+I<upper_bound>]>, i.e. the range I<excludes> the lower bound and I<includes>
+the upper bound. I<lower_bound> and I<upper_bound> may be zero, meaning no
+lower/upper bound.
+
+To export the entire (0–inf) range without overlap, use the upper bound of the
+previous range as the lower bound of the following range. In other words, use
+the following schema:
+
+  Bucket   0   1
+  Bucket   1   2
+  Bucket   2   5
+  Bucket   5  10
+  Bucket  10  20
+  Bucket  20  50
+  Bucket  50   0
+
+Metrics are reported with the I<type> C<bucket> and the I<type instance>
+C<E<lt>TypeE<gt>[-E<lt>InstanceE<gt>]-E<lt>lower_boundE<gt>_E<lt>upper_boundE<gt>>.
+
+This option may be repeated to calculate more than one rate.
+
 =back
 
-As you'd expect the B<Gauge*> types interpret the submatch as a floating point
-number, using L<strtod(3)>. The B<Counter*> and B<AbsoluteSet> types interpret
-the submatch as an unsigned integer using L<strtoull(3)>. The B<Derive*> types
-interpret the submatch as a signed integer using L<strtoll(3)>. B<CounterInc>
-and B<DeriveInc> do not use the submatch at all and it may be omitted in this
-case.
+=back
+
+The B<Gauge*> and B<Distribution> types interpret the submatch as a floating
+point number, using L<strtod(3)>. The B<Counter*> and B<AbsoluteSet> types
+interpret the submatch as an unsigned integer using L<strtoull(3)>. The
+B<Derive*> types interpret the submatch as a signed integer using
+L<strtoll(3)>. B<CounterInc> and B<DeriveInc> do not use the submatch at all
+and it may be omitted in this case.
 
 =item B<Type> I<Type>
 
@@ -7294,7 +7550,7 @@ couple metrics: number of records, and database size on disk.
 
 =item B<Host> I<Hostname/IP>
 
-The hostname or ip which identifies the server.
+The hostname or IP which identifies the server.
 Default: B<127.0.0.1>
 
 =item B<Port> I<Service/Port>
@@ -7308,61 +7564,60 @@ Default: B<1978>
 =head2 Plugin C<turbostat>
 
 The I<Turbostat plugin> reads CPU frequency and C-state residency on modern
-Intel processors by using the new Model Specific Registers.
+Intel processors by using I<Model Specific Registers>.
 
 =over 4
 
 =item B<CoreCstates> I<Bitmask(Integer)>
 
-Bitmask of the list of core C states supported by the processor.
+Bit mask of the list of core C-states supported by the processor.
 This option should only be used if the automated detection fails.
-Default value extracted from the cpu model and family.
+Default value extracted from the CPU model and family.
 
 Currently supported C-states (by this plugin): 3, 6, 7
 
-Example: (1<<3)+(1<<6)+(1<<7) = 392 for all states
+B<Example:>
+
+  All states (3, 6 and 7):
+  (1<<3) + (1<<6) + (1<<7) = 392
 
 =item B<PackageCstates> I<Bitmask(Integer)>
 
-Bitmask of the list of pacages C states supported by the processor.
-This option should only be used if the automated detection fails.
-Default value extracted from the cpu model and family.
+Bit mask of the list of packages C-states supported by the processor. This
+option should only be used if the automated detection fails. Default value
+extracted from the CPU model and family.
 
 Currently supported C-states (by this plugin): 2, 3, 6, 7, 8, 9, 10
 
-Example: (1<<2)+(1<<3)+(1<<6)+(1<<7) = 396 for states 2, 3, 6 and 7
-
-=item B<SystemManagementInterrupt> I<true>|I<false>
+B<Example:>
 
-Boolean enabling the collection of the I/O System-Management Interrupt
-counter'. This option should only be used if the automated detection
-fails or if you want to disable this feature.
+  States 2, 3, 6 and 7:
+  (1<<2) + (1<<3) + (1<<6) + (1<<7) = 396
 
-=item B<DigitalTemperatureSensor> I<true>|I<false>
+=item B<SystemManagementInterrupt> I<true>|I<false>
 
-Boolean enabling the collection of the temperature of each core.
-This option should only be used if the automated detectionfails or
-if you want to disable this feature.
+Boolean enabling the collection of the I/O System-Management Interrupt counter.
+This option should only be used if the automated detection fails or if you want
+to disable this feature.
 
 =item B<DigitalTemperatureSensor> I<true>|I<false>
 
-Boolean enabling the collection of the temperature of each package.
-This option should only be used if the automated detectionfails or
-if you want to disable this feature.
+Boolean enabling the collection of the temperature of each core. This option
+should only be used if the automated detection fails or if you want to disable
+this feature.
 
 =item B<TCCActivationTemp> I<Temperature>
 
-Thermal Control Circuit Activation Temperature of the installed
-CPU. This temperature is used when collecting the temperature of
-cores or packages. This option should only be used if the automated
-detection fails. Default value extracted from B<MSR_IA32_TEMPERATURE_TARGET>
+I<Thermal Control Circuit Activation Temperature> of the installed CPU. This
+temperature is used when collecting the temperature of cores or packages. This
+option should only be used if the automated detection fails. Default value
+extracted from B<MSR_IA32_TEMPERATURE_TARGET>.
 
 =item B<RunningAveragePowerLimit> I<Bitmask(Integer)>
 
-Bitmask of the list of elements to be thermally monitored. This option
-should only be used if the automated detection fails or if you want to
-disable some collections. The different bits of this bitmask accepted
-by this plugin are:
+Bit mask of the list of elements to be thermally monitored. This option should
+only be used if the automated detection fails or if you want to disable some
+collections. The different bits of this bit mask accepted by this plugin are:
 
 =over 4
 
@@ -7376,6 +7631,14 @@ by this plugin are:
 
 =back
 
+=item B<LogicalCoreNames> I<true>|I<false>
+
+Boolean enabling the use of logical core numbering for per core statistics.
+When enabled, C<cpuE<lt>nE<gt>> is used as plugin instance, where I<n> is a
+sequential number assigned by the kernel. Otherwise, C<coreE<lt>nE<gt>> is used
+where I<n> is the n-th core of the socket, causing name conflicts when there is
+more than one socket.
+
 =back
 
 =head2 Plugin C<unixsock>
@@ -7657,6 +7920,49 @@ Example:
 Ignore all I<hdb> devices on any domain, but other block devices (eg. I<hda>)
 will be collected.
 
+=item B<BlockDeviceFormat> B<target>|B<source>
+
+If I<BlockDeviceFormat> is set to B<target>, the default, then the device name
+seen by the guest will be used for reporting metrics. 
+This corresponds to the C<E<lt>targetE<gt>> node in the XML definition of the
+domain.
+
+If I<BlockDeviceFormat> is set to B<source>, then metrics will be reported
+using the path of the source, e.g. an image file.
+This corresponds to the C<E<lt>sourceE<gt>> node in the XML definition of the
+domain.
+
+B<Example:>
+
+If the domain XML have the following device defined:
+
+  <disk type='block' device='disk'>
+    <driver name='qemu' type='raw' cache='none' io='native' discard='unmap'/>
+    <source dev='/var/lib/libvirt/images/image1.qcow2'/>
+    <target dev='sda' bus='scsi'/>
+    <boot order='2'/>
+    <address type='drive' controller='0' bus='0' target='0' unit='0'/>
+  </disk>
+
+Setting C<BlockDeviceFormat target> will cause the I<type instance> to be set
+to C<sda>.
+Setting C<BlockDeviceFormat source> will cause the I<type instance> to be set
+to C<var_lib_libvirt_images_image1.qcow2>.
+
+=item B<BlockDeviceFormatBasename> B<false>|B<true>
+
+The B<BlockDeviceFormatBasename> controls whether the full path or the
+L<basename(1)> of the source is being used as the I<type instance> when
+B<BlockDeviceFormat> is set to B<source>. Defaults to B<false>.
+
+B<Example:>
+
+Assume the device path (source tag) is C</var/lib/libvirt/images/image1.qcow2>.
+Setting C<BlockDeviceFormatBasename false> will cause the I<type instance> to
+be set to C<var_lib_libvirt_images_image1.qcow2>.
+Setting C<BlockDeviceFormatBasename true> will cause the I<type instance> to be
+set to C<image1.qcow2>.
+
 =item B<HostnameFormat> B<name|uuid|hostname|...>
 
 When the virt plugin logs data, it sets the hostname of the collected data
@@ -7823,6 +8129,38 @@ If set to B<true>, append the name of the I<Data Source> (DS) to the "metric"
 identifier. If set to B<false> (the default), this is only done when there is
 more than one DS.
 
+=item B<PreserveSeparator> B<false>|B<true>
+
+If set to B<false> (the default) the C<.> (dot) character is replaced with
+I<EscapeCharacter>. Otherwise, if set to B<true>, the C<.> (dot) character
+is preserved, i.e. passed through.
+
+=item B<DropDuplicateFields> B<false>|B<true>
+
+If set to B<true>, detect and remove duplicate components in Graphite metric
+names. For example, the metric name  C<host.load.load.shortterm> will
+be shortened to C<host.load.shortterm>.
+
+=back
+
+=head2 Plugin C<write_log>
+
+The C<write_log> plugin writes metrics as INFO log messages.
+
+This plugin supports two output formats: I<Graphite> and I<JSON>.
+
+Synopsis:
+
+ <Plugin write_log>
+   Format Graphite
+ </Plugin>
+
+=over 4
+
+=item B<Format> I<Format>
+
+The output format to use. Can be one of C<Graphite> or C<JSON>.
+
 =back
 
 =head2 Plugin C<write_tsdb>
@@ -7931,6 +8269,41 @@ want to use authentication all three fields must be set.
 
 =back
 
+=head2 Plugin C<write_prometheus>
+
+The I<write_prometheus plugin> implements a tiny webserver that can be scraped
+using I<Prometheus>.
+
+B<Options:>
+
+=over 4
+
+=item B<Port> I<Port>
+
+Port the embedded webserver should listen on. Defaults to B<9103>.
+
+=item B<StalenessDelta> I<Seconds>
+
+Time in seconds after which I<Prometheus> considers a metric "stale" if it
+hasn't seen any update for it. This value must match the setting in Prometheus.
+It defaults to B<300> seconds (5 minutes), same as Prometheus.
+
+B<Background:>
+
+I<Prometheus> has a global setting, C<StalenessDelta>, which controls after
+which time a metric without updates is considered "stale". This setting
+effectively puts an upper limit on the interval in which metrics are reported.
+
+When the I<write_prometheus plugin> encounters a metric with an interval
+exceeding this limit, it will inform you, the user, and provide the metric to
+I<Prometheus> B<without> a timestamp. That causes I<Prometheus> to consider the
+metric "fresh" each time it is scraped, with the time of the scrape being
+considered the time of the update. The result is that there appear more
+datapoints in I<Prometheus> than were actually created, but at least the metric
+doesn't disappear periodically.
+
+=back
+
 =head2 Plugin C<write_http>
 
 This output plugin submits values to an HTTP server using POST requests and
@@ -8164,6 +8537,18 @@ path component, for example C<host.cpu.0.cpu.idle>. If set to B<false> (the
 default), the plugin and plugin instance (and likewise the type and type
 instance) are put into one component, for example C<host.cpu-0.cpu-idle>.
 
+=item B<GraphiteAlwaysAppendDS> B<true>|B<false>
+
+If set to B<true>, append the name of the I<Data Source> (DS) to the "metric"
+identifier. If set to B<false> (the default), this is only done when there is
+more than one DS.
+
+=item B<GraphitePreserveSeparator> B<false>|B<true>
+
+If set to B<false> (the default) the C<.> (dot) character is replaced with
+I<GraphiteEscapeChar>. Otherwise, if set to B<true>, the C<.> (dot) character
+is preserved, i.e. passed through.
+
 =item B<StoreRates> B<true>|B<false>
 
 If set to B<true> (the default), convert counter values to rates. If set to
@@ -9098,6 +9483,8 @@ Available options:
 
 =item B<TypeInstance> I<Regex>
 
+=item B<MetaData> I<String> I<Regex>
+
 Match values where the given regular expressions match the various fields of
 the identifier of a value. If multiple regular expressions are given, B<all>
 regexen must match for a value to match.
@@ -9386,6 +9773,10 @@ Available options:
 
 =item B<TypeInstance> I<Regex> I<Replacement>
 
+=item B<MetaData> I<String> I<Regex> I<Replacement>
+
+=item B<DeleteMetaData> I<String> I<Regex>
+
 Match the appropriate field with the given regular expression I<Regex>. If the
 regular expression matches, that part that matches is replaced with
 I<Replacement>. If multiple places of the input buffer match a given regular
@@ -9424,9 +9815,37 @@ Available options:
 
 =item B<MetaData> I<String> I<String>
 
-Set the appropriate field to the given string. The strings for plugin instance
-and type instance may be empty, the strings for host and plugin may not be
-empty. It's currently not possible to set the type of a value this way.
+Set the appropriate field to the given string. The strings for plugin instance,
+type instance, and meta data may be empty, the strings for host and plugin may
+not be empty. It's currently not possible to set the type of a value this way.
+
+The following placeholders will be replaced by an appropriate value:
+
+=over 4
+
+=item B<%{host}>
+
+=item B<%{plugin}>
+
+=item B<%{plugin_instance}>
+
+=item B<%{type}>
+
+=item B<%{type_instance}>
+
+These placeholders are replaced by the identifier field of the same name.
+
+=item B<%{meta:>I<name>B<}>
+
+These placeholders are replaced by the meta data value with the given name.
+
+=back
+
+Please note that these placeholders are B<case sensitive>!
+
+=item B<DeleteMetaData> I<String>
+
+Delete the named meta data field.
 
 =back
 
index 69170d7..8a9200d 100644 (file)
@@ -56,7 +56,6 @@ static void conntrack_submit(const char *type, const char *type_instance,
 
   vl.values = &conntrack;
   vl.values_len = 1;
-  sstrncpy(vl.host, hostname_g, sizeof(vl.host));
   sstrncpy(vl.plugin, "conntrack", sizeof(vl.plugin));
   sstrncpy(vl.type, type, sizeof(vl.type));
   if (type_instance != NULL)
@@ -67,55 +66,23 @@ static void conntrack_submit(const char *type, const char *type_instance,
 
 static int conntrack_read(void) {
   value_t conntrack, conntrack_max, conntrack_pct;
-  FILE *fh;
-  char buffer[64] = {0};
-  size_t buffer_len;
 
-  fh = fopen(old_files ? CONNTRACK_FILE_OLD : CONNTRACK_FILE, "r");
-  if (fh == NULL)
+  char const *path = old_files ? CONNTRACK_FILE_OLD : CONNTRACK_FILE;
+  if (parse_value_file(path, &conntrack, DS_TYPE_GAUGE) != 0) {
+    ERROR("conntrack plugin: Reading \"%s\" failed.", path);
     return (-1);
-
-  if (fgets(buffer, sizeof(buffer), fh) == NULL) {
-    fclose(fh);
-    return (-1);
-  }
-  fclose(fh);
-
-  /* strip trailing newline. */
-  buffer_len = strlen(buffer);
-  while ((buffer_len > 0) && isspace((int)buffer[buffer_len - 1])) {
-    buffer[buffer_len - 1] = 0;
-    buffer_len--;
   }
 
-  if (parse_value(buffer, &conntrack, DS_TYPE_GAUGE) != 0)
-    return (-1);
-
-  conntrack_submit("conntrack", NULL, conntrack);
-
-  fh = fopen(old_files ? CONNTRACK_MAX_FILE_OLD : CONNTRACK_MAX_FILE, "r");
-  if (fh == NULL)
+  path = old_files ? CONNTRACK_MAX_FILE_OLD : CONNTRACK_MAX_FILE;
+  if (parse_value_file(path, &conntrack_max, DS_TYPE_GAUGE) != 0) {
+    ERROR("conntrack plugin: Reading \"%s\" failed.", path);
     return (-1);
-
-  memset(buffer, 0, sizeof(buffer));
-  if (fgets(buffer, sizeof(buffer), fh) == NULL) {
-    fclose(fh);
-    return (-1);
-  }
-  fclose(fh);
-
-  /* strip trailing newline. */
-  buffer_len = strlen(buffer);
-  while ((buffer_len > 0) && isspace((int)buffer[buffer_len - 1])) {
-    buffer[buffer_len - 1] = 0;
-    buffer_len--;
   }
 
-  if (parse_value(buffer, &conntrack_max, DS_TYPE_GAUGE) != 0)
-    return (-1);
+  conntrack_pct.gauge = (conntrack.gauge / conntrack_max.gauge) * 100;
 
+  conntrack_submit("conntrack", NULL, conntrack);
   conntrack_submit("conntrack", "max", conntrack_max);
-  conntrack_pct.gauge = (conntrack.gauge / conntrack_max.gauge) * 100;
   conntrack_submit("percent", "used", conntrack_pct);
 
   return (0);
index d91c073..1d73f04 100644 (file)
 #endif
 
 static void cs_submit(derive_t context_switches) {
-  value_t values[1];
   value_list_t vl = VALUE_LIST_INIT;
 
-  values[0].derive = (derive_t)context_switches;
-
-  vl.values = values;
+  vl.values = &(value_t){.derive = context_switches};
   vl.values_len = 1;
-  sstrncpy(vl.host, hostname_g, sizeof(vl.host));
   sstrncpy(vl.plugin, "contextswitch", sizeof(vl.plugin));
   sstrncpy(vl.type, "contextswitch", sizeof(vl.type));
 
index 80692e7..9091fef 100644 (file)
--- a/src/cpu.c
+++ b/src/cpu.c
@@ -156,6 +156,12 @@ static int maxcpu;
 /* #endif  HAVE_LIBSTATGRAB */
 
 #elif defined(HAVE_PERFSTAT)
+#define TOTAL_IDLE 0
+#define TOTAL_USER 1
+#define TOTAL_SYS 2
+#define TOTAL_WAIT 3
+#define TOTAL_STAT_NUM 4
+static value_to_rate_state_t total_conv[TOTAL_STAT_NUM];
 static perfstat_cpu_t *perfcpu;
 static int numcpu;
 static int pnumcpu;
@@ -309,15 +315,11 @@ static int init(void) {
 
 static void submit_value(int cpu_num, int cpu_state, const char *type,
                          value_t value) {
-  value_t values[1];
   value_list_t vl = VALUE_LIST_INIT;
 
-  memcpy(&values[0], &value, sizeof(value));
-
-  vl.values = values;
+  vl.values = &value;
   vl.values_len = 1;
 
-  sstrncpy(vl.host, hostname_g, sizeof(vl.host));
   sstrncpy(vl.plugin, "cpu", sizeof(vl.plugin));
   sstrncpy(vl.type, type, sizeof(vl.type));
   sstrncpy(vl.type_instance, cpu_state_names[cpu_state],
@@ -329,24 +331,18 @@ static void submit_value(int cpu_num, int cpu_state, const char *type,
   plugin_dispatch_values(&vl);
 }
 
-static void submit_percent(int cpu_num, int cpu_state, gauge_t percent) {
-  value_t value;
-
+static void submit_percent(int cpu_num, int cpu_state, gauge_t value) {
   /* This function is called for all known CPU states, but each read
    * method will only report a subset. The remaining states are left as
    * NAN and we ignore them here. */
-  if (isnan(percent))
+  if (isnan(value))
     return;
 
-  value.gauge = percent;
-  submit_value(cpu_num, cpu_state, "percent", value);
+  submit_value(cpu_num, cpu_state, "percent", (value_t){.gauge = value});
 }
 
-static void submit_derive(int cpu_num, int cpu_state, derive_t derive) {
-  value_t value;
-
-  value.derive = derive;
-  submit_value(cpu_num, cpu_state, "cpu", value);
+static void submit_derive(int cpu_num, int cpu_state, derive_t value) {
+  submit_value(cpu_num, cpu_state, "cpu", (value_t){.derive = value});
 }
 
 /* Takes the zero-index number of a CPU and makes sure that the module-global
@@ -386,6 +382,24 @@ static cpu_state_t *get_cpu_state(size_t cpu_num, size_t state) /* {{{ */
   return (&cpu_states[index]);
 } /* }}} cpu_state_t *get_cpu_state */
 
+#if defined(HAVE_PERFSTAT) /* {{{ */
+/* populate global aggregate cpu rate */
+static int total_rate(gauge_t *sum_by_state, size_t state, derive_t d,
+                      value_to_rate_state_t *conv, cdtime_t now) {
+  gauge_t rate = NAN;
+  int status =
+      value_to_rate(&rate, (value_t){.derive = d}, DS_TYPE_DERIVE, now, conv);
+  if (status != 0)
+    return (status);
+
+  sum_by_state[state] = rate;
+
+  if (state != COLLECTD_CPU_STATE_IDLE)
+    RATE_ADD(sum_by_state[COLLECTD_CPU_STATE_ACTIVE], sum_by_state[state]);
+  return (0);
+}
+#endif /* }}} HAVE_PERFSTAT */
+
 /* Populates the per-CPU COLLECTD_CPU_STATE_ACTIVE rate and the global
  * rate_by_state
  * array. */
@@ -415,6 +429,31 @@ static void aggregate(gauge_t *sum_by_state) /* {{{ */
     RATE_ADD(sum_by_state[COLLECTD_CPU_STATE_ACTIVE],
              this_cpu_states[COLLECTD_CPU_STATE_ACTIVE].rate);
   }
+
+#if defined(HAVE_PERFSTAT) /* {{{ */
+  cdtime_t now = cdtime();
+  perfstat_cpu_total_t cputotal = {0};
+
+  if (!perfstat_cpu_total(NULL, &cputotal, sizeof(cputotal), 1)) {
+    char errbuf[1024];
+    WARNING("cpu plugin: perfstat_cpu_total: %s",
+            sstrerror(errno, errbuf, sizeof(errbuf)));
+    return;
+  }
+
+  /* Reset COLLECTD_CPU_STATE_ACTIVE */
+  sum_by_state[COLLECTD_CPU_STATE_ACTIVE] = NAN;
+
+  /* Physical Processor Utilization */
+  total_rate(sum_by_state, COLLECTD_CPU_STATE_IDLE, (derive_t)cputotal.pidle,
+             &total_conv[TOTAL_IDLE], now);
+  total_rate(sum_by_state, COLLECTD_CPU_STATE_USER, (derive_t)cputotal.puser,
+             &total_conv[TOTAL_USER], now);
+  total_rate(sum_by_state, COLLECTD_CPU_STATE_SYSTEM, (derive_t)cputotal.psys,
+             &total_conv[TOTAL_SYS], now);
+  total_rate(sum_by_state, COLLECTD_CPU_STATE_WAIT, (derive_t)cputotal.pwait,
+             &total_conv[TOTAL_WAIT], now);
+#endif /* }}} HAVE_PERFSTAT */
 } /* }}} void aggregate */
 
 /* Commits (dispatches) the values for one CPU or the global aggregation.
@@ -443,17 +482,13 @@ static void cpu_commit_one(int cpu_num, /* {{{ */
 } /* }}} void cpu_commit_one */
 
 /* Commits the number of cores */
-static void cpu_commit_num_cpu(gauge_t num_cpu) /* {{{ */
+static void cpu_commit_num_cpu(gauge_t value) /* {{{ */
 {
-  value_t values[1];
   value_list_t vl = VALUE_LIST_INIT;
 
-  values[0].gauge = num_cpu;
-
-  vl.values = values;
+  vl.values = &(value_t){.gauge = value};
   vl.values_len = 1;
 
-  sstrncpy(vl.host, hostname_g, sizeof(vl.host));
   sstrncpy(vl.plugin, "cpu", sizeof(vl.plugin));
   sstrncpy(vl.type, "count", sizeof(vl.type));
 
index 80f586d..c3080cb 100644 (file)
@@ -25,8 +25,6 @@
 #include "common.h"
 #include "plugin.h"
 
-#define MODULE_NAME "cpufreq"
-
 static int num_cpu = 0;
 
 static int cpufreq_init(void) {
@@ -57,15 +55,11 @@ static int cpufreq_init(void) {
   return (0);
 } /* int cpufreq_init */
 
-static void cpufreq_submit(int cpu_num, double value) {
-  value_t values[1];
+static void cpufreq_submit(int cpu_num, value_t value) {
   value_list_t vl = VALUE_LIST_INIT;
 
-  values[0].gauge = value;
-
-  vl.values = values;
+  vl.values = &value;
   vl.values_len = 1;
-  sstrncpy(vl.host, hostname_g, sizeof(vl.host));
   sstrncpy(vl.plugin, "cpufreq", sizeof(vl.plugin));
   sstrncpy(vl.type, "cpufreq", sizeof(vl.type));
   ssnprintf(vl.type_instance, sizeof(vl.type_instance), "%i", cpu_num);
@@ -74,43 +68,21 @@ static void cpufreq_submit(int cpu_num, double value) {
 }
 
 static int cpufreq_read(void) {
-  int status;
-  unsigned long long val;
-  FILE *fp;
-  char filename[256];
-  char buffer[16];
-
   for (int i = 0; i < num_cpu; i++) {
-    status = ssnprintf(filename, sizeof(filename),
-                       "/sys/devices/system/cpu/cpu%d/cpufreq/"
-                       "scaling_cur_freq",
-                       i);
-    if ((status < 1) || ((unsigned int)status >= sizeof(filename)))
-      return (-1);
-
-    if ((fp = fopen(filename, "r")) == NULL) {
-      char errbuf[1024];
-      WARNING("cpufreq: fopen (%s): %s", filename,
-              sstrerror(errno, errbuf, sizeof(errbuf)));
-      return (-1);
-    }
-
-    if (fgets(buffer, 16, fp) == NULL) {
-      char errbuf[1024];
-      WARNING("cpufreq: fgets: %s", sstrerror(errno, errbuf, sizeof(errbuf)));
-      fclose(fp);
-      return (-1);
-    }
-
-    if (fclose(fp)) {
-      char errbuf[1024];
-      WARNING("cpufreq: fclose: %s", sstrerror(errno, errbuf, sizeof(errbuf)));
+    char filename[PATH_MAX];
+    ssnprintf(filename, sizeof(filename),
+              "/sys/devices/system/cpu/cpu%d/cpufreq/scaling_cur_freq", i);
+
+    value_t v;
+    if (parse_value_file(filename, &v, DS_TYPE_GAUGE) != 0) {
+      WARNING("cpufreq plugin: Reading \"%s\" failed.", filename);
+      continue;
     }
 
-    /* You're seeing correctly: The file is reporting kHz values.. */
-    val = atoll(buffer) * 1000;
+    /* convert kHz to Hz */
+    v.gauge *= 1000.0;
 
-    cpufreq_submit(i, val);
+    cpufreq_submit(i, v);
   }
 
   return (0);
index a1c3644..62988fb 100644 (file)
 #include "plugin.h"
 
 static void cpusleep_submit(derive_t cpu_sleep) {
-  value_t values[1];
   value_list_t vl = VALUE_LIST_INIT;
 
-  values[0].derive = cpu_sleep;
-
-  vl.values = values;
+  vl.values = &(value_t){.derive = cpu_sleep};
   vl.values_len = 1;
-  sstrncpy(vl.host, hostname_g, sizeof(vl.host));
   sstrncpy(vl.plugin, "cpusleep", sizeof(vl.plugin));
   sstrncpy(vl.type, "total_time_in_ms", sizeof(vl.type));
 
index fc3af6d..26ca7e7 100644 (file)
@@ -561,15 +561,11 @@ static int cc_init(void) /* {{{ */
 } /* }}} int cc_init */
 
 static void cc_submit(const web_page_t *wp, const web_match_t *wm, /* {{{ */
-                      const cu_match_value_t *mv) {
-  value_t values[1];
+                      value_t value) {
   value_list_t vl = VALUE_LIST_INIT;
 
-  values[0] = mv->value;
-
-  vl.values = values;
+  vl.values = &value;
   vl.values_len = 1;
-  sstrncpy(vl.host, hostname_g, sizeof(vl.host));
   sstrncpy(vl.plugin, "curl", sizeof(vl.plugin));
   sstrncpy(vl.plugin_instance, wp->instance, sizeof(vl.plugin_instance));
   sstrncpy(vl.type, wm->type, sizeof(vl.type));
@@ -581,14 +577,10 @@ static void cc_submit(const web_page_t *wp, const web_match_t *wm, /* {{{ */
 
 static void cc_submit_response_code(const web_page_t *wp, long code) /* {{{ */
 {
-  value_t values[1];
   value_list_t vl = VALUE_LIST_INIT;
 
-  values[0].gauge = code;
-
-  vl.values = values;
+  vl.values = &(value_t){.gauge = (gauge_t)code};
   vl.values_len = 1;
-  sstrncpy(vl.host, hostname_g, sizeof(vl.host));
   sstrncpy(vl.plugin, "curl", sizeof(vl.plugin));
   sstrncpy(vl.plugin_instance, wp->instance, sizeof(vl.plugin_instance));
   sstrncpy(vl.type, "response_code", sizeof(vl.type));
@@ -597,15 +589,11 @@ static void cc_submit_response_code(const web_page_t *wp, long code) /* {{{ */
 } /* }}} void cc_submit_response_code */
 
 static void cc_submit_response_time(const web_page_t *wp, /* {{{ */
-                                    cdtime_t response_time) {
-  value_t values[1];
+                                    gauge_t response_time) {
   value_list_t vl = VALUE_LIST_INIT;
 
-  values[0].gauge = CDTIME_T_TO_DOUBLE(response_time);
-
-  vl.values = values;
+  vl.values = &(value_t){.gauge = response_time};
   vl.values_len = 1;
-  sstrncpy(vl.host, hostname_g, sizeof(vl.host));
   sstrncpy(vl.plugin, "curl", sizeof(vl.plugin));
   sstrncpy(vl.plugin_instance, wp->instance, sizeof(vl.plugin_instance));
   sstrncpy(vl.type, "response_time", sizeof(vl.type));
@@ -633,7 +621,7 @@ static int cc_read_page(web_page_t *wp) /* {{{ */
   }
 
   if (wp->response_time)
-    cc_submit_response_time(wp, cdtime() - start);
+    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);
 
@@ -664,7 +652,7 @@ static int cc_read_page(web_page_t *wp) /* {{{ */
       continue;
     }
 
-    cc_submit(wp, wm, mv);
+    cc_submit(wp, wm, mv->value);
     match_value_reset(mv);
   } /* for (wm = wp->matches; wm != NULL; wm = wm->next) */
 
index c0f9346..8f6382b 100644 (file)
@@ -715,10 +715,11 @@ static int cj_config_add_url(oconfig_item_t *ci) /* {{{ */
     cb_name = ssnprintf_alloc("curl_json-%s-%s", db->instance,
                               db->url ? db->url : db->sock);
 
-    user_data_t ud = {.data = db, .free_func = cj_free};
-
     plugin_register_complex_read(/* group = */ NULL, cb_name, cj_read,
-                                 /* interval = */ db->interval, &ud);
+                                 /* interval = */ db->interval,
+                                 &(user_data_t){
+                                     .data = db, .free_func = cj_free,
+                                 });
     sfree(cb_name);
   } else {
     cj_free(db);
index 23aa855..7c422c7 100644 (file)
@@ -967,10 +967,11 @@ static int cx_config_add_url(oconfig_item_t *ci) /* {{{ */
 
     cb_name = ssnprintf_alloc("curl_xml-%s-%s", db->instance, db->url);
 
-    user_data_t ud = {.data = db, .free_func = cx_free};
-
     plugin_register_complex_read(/* group = */ "curl_xml", cb_name, cx_read,
-                                 /* interval = */ 0, &ud);
+                                 /* interval = */ 0,
+                                 &(user_data_t){
+                                     .data = db, .free_func = cx_free,
+                                 });
     sfree(cb_name);
   } else {
     cx_free(db);
index cb62c64..5207943 100644 (file)
@@ -61,10 +61,7 @@ collectd_SOURCES = collectd.c collectd.h \
                   utils_ignorelist.c utils_ignorelist.h \
                   utils_llist.c utils_llist.h \
                   utils_random.c utils_random.h \
-                  utils_tail_match.c utils_tail_match.h \
-                  utils_match.c utils_match.h \
                   utils_subst.c utils_subst.h \
-                  utils_tail.c utils_tail.h \
                   utils_time.c utils_time.h \
                   types_list.c types_list.h \
                   utils_threshold.c utils_threshold.h
index b947349..6b7c413 100644 (file)
@@ -92,7 +92,7 @@ static int init_hostname(void) {
   int status;
 
   str = global_option_get("Hostname");
-  if (str != NULL) {
+  if ((str != NULL) && (str[0] != 0)) {
     sstrncpy(hostname_g, str, sizeof(hostname_g));
     return (0);
   }
@@ -304,7 +304,6 @@ static int do_loop(void) {
   wait_until = cdtime() + interval;
 
   while (loop == 0) {
-    struct timespec ts_wait = {0, 0};
     cdtime_t now;
 
 #if HAVE_LIBKSTAT
@@ -323,7 +322,7 @@ static int do_loop(void) {
       continue;
     }
 
-    CDTIME_T_TO_TIMESPEC(wait_until - now, &ts_wait);
+    struct timespec ts_wait = CDTIME_T_TO_TIMESPEC(wait_until - now);
     wait_until = wait_until + interval;
 
     while ((loop == 0) && (nanosleep(&ts_wait, &ts_wait) != 0)) {
@@ -546,7 +545,7 @@ int main(int argc, char **argv) {
    * something wrong.
    */
   if (init_global_variables() != 0)
-    return (1);
+    exit(EXIT_FAILURE);
 
   if (test_config)
     return (0);
index 2be2e8e..ec5c7ab 100644 (file)
@@ -271,8 +271,10 @@ ssize_t swrite(int fd, const void *buf, size_t count) {
   ptr = (const char *)buf;
   nleft = count;
 
-  if (fd < 0)
-    return (-1);
+  if (fd < 0) {
+    errno = EINVAL;
+    return errno;
+  }
 
   /* checking for closed peer connection */
   pfd.fd = fd;
@@ -281,10 +283,9 @@ ssize_t swrite(int fd, const void *buf, size_t count) {
   if (poll(&pfd, 1, 0) > 0) {
     char buffer[32];
     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 -1;
+      /* 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;
     }
   }
 
@@ -295,7 +296,7 @@ ssize_t swrite(int fd, const void *buf, size_t count) {
       continue;
 
     if (status < 0)
-      return (status);
+      return errno ? errno : status;
 
     nleft = nleft - ((size_t)status);
     ptr = ptr + ((size_t)status);
@@ -325,44 +326,58 @@ int strsplit(char *string, char **fields, size_t size) {
 
 int strjoin(char *buffer, size_t buffer_size, char **fields, size_t fields_num,
             const char *sep) {
-  size_t avail;
-  char *ptr;
-  size_t sep_len;
+  size_t avail = 0;
+  char *ptr = buffer;
+  size_t sep_len = 0;
 
-  if ((buffer_size < 1) || (fields_num == 0))
-    return (-1);
+  size_t buffer_req = 0;
+
+  if (((fields_num != 0) && (fields == NULL)) ||
+      ((buffer_size != 0) && (buffer == NULL)))
+    return (-EINVAL);
+
+  if (buffer != NULL)
+    buffer[0] = 0;
 
-  memset(buffer, 0, buffer_size);
-  ptr = buffer;
-  avail = buffer_size - 1;
+  if (buffer_size != 0)
+    avail = buffer_size - 1;
 
-  sep_len = 0;
   if (sep != NULL)
     sep_len = strlen(sep);
 
   for (size_t i = 0; i < fields_num; i++) {
-    size_t field_len;
+    size_t field_len = strlen(fields[i]);
 
-    if ((i > 0) && (sep_len > 0)) {
-      if (avail < sep_len)
-        return (-1);
+    if (i != 0)
+      buffer_req += sep_len;
+    buffer_req += field_len;
+
+    if ((i != 0) && (sep_len > 0)) {
+      if (sep_len >= avail) {
+        /* prevent subsequent iterations from writing to the
+         * buffer. */
+        avail = 0;
+        continue;
+      }
 
       memcpy(ptr, sep, sep_len);
+
       ptr += sep_len;
       avail -= sep_len;
     }
 
-    field_len = strlen(fields[i]);
-    if (avail < field_len)
-      return (-1);
+    if (field_len > avail)
+      field_len = avail;
 
     memcpy(ptr, fields[i], field_len);
     ptr += field_len;
+
     avail -= field_len;
+    if (ptr != NULL)
+      *ptr = 0;
   }
 
-  assert(buffer[buffer_size - 1] == 0);
-  return ((int)strlen(buffer));
+  return (int)buffer_req;
 }
 
 int escape_string(char *buffer, size_t buffer_size) {
@@ -916,7 +931,7 @@ int format_values(char *ret, size_t ret_len, /* {{{ */
 
 int parse_identifier(char *str, char **ret_host, char **ret_plugin,
                      char **ret_plugin_instance, char **ret_type,
-                     char **ret_type_instance) {
+                     char **ret_type_instance, char *default_host) {
   char *hostname = NULL;
   char *plugin = NULL;
   char *plugin_instance = NULL;
@@ -934,10 +949,17 @@ int parse_identifier(char *str, char **ret_host, char **ret_plugin,
   plugin++;
 
   type = strchr(plugin, '/');
-  if (type == NULL)
-    return (-1);
-  *type = '\0';
-  type++;
+  if (type == NULL) {
+    if (default_host == NULL)
+      return (-1);
+    /* else: no host specified; use default */
+    type = plugin;
+    plugin = hostname;
+    hostname = default_host;
+  } else {
+    *type = '\0';
+    type++;
+  }
 
   plugin_instance = strchr(plugin, '-');
   if (plugin_instance != NULL) {
@@ -975,7 +997,8 @@ int parse_identifier_vl(const char *str, value_list_t *vl) /* {{{ */
   sstrncpy(str_copy, str, sizeof(str_copy));
 
   status = parse_identifier(str_copy, &host, &plugin, &plugin_instance, &type,
-                            &type_instance);
+                            &type_instance,
+                            /* default_host = */ NULL);
   if (status != 0)
     return (status);
 
@@ -1102,6 +1125,26 @@ int parse_values(char *buffer, value_list_t *vl, const data_set_t *ds) {
   return (0);
 } /* int parse_values */
 
+int parse_value_file(char const *path, value_t *ret_value, int ds_type) {
+  FILE *fh;
+  char buffer[256];
+
+  fh = fopen(path, "r");
+  if (fh == NULL)
+    return (-1);
+
+  if (fgets(buffer, sizeof(buffer), fh) == NULL) {
+    fclose(fh);
+    return (-1);
+  }
+
+  fclose(fh);
+
+  strstripnewline(buffer);
+
+  return parse_value(buffer, ret_value, ds_type);
+} /* int parse_value_file */
+
 #if !HAVE_GETPWNAM_R
 int getpwnam_r(const char *name, struct passwd *pwbuf, char *buf, size_t buflen,
                struct passwd **pwbufp) {
@@ -1422,18 +1465,16 @@ void set_sock_opts(int sockfd) /* {{{ */
   int status;
   int socktype;
 
-  socklen_t socklen = sizeof(socklen_t);
-  int so_keepalive = 1;
-
-  status = getsockopt(sockfd, SOL_SOCKET, SO_TYPE, &socktype, &socklen);
+  status = getsockopt(sockfd, SOL_SOCKET, SO_TYPE, &socktype,
+                      &(socklen_t){sizeof(socktype)});
   if (status != 0) {
     WARNING("set_sock_opts: failed to determine socket type");
     return;
   }
 
   if (socktype == SOCK_STREAM) {
-    status = setsockopt(sockfd, SOL_SOCKET, SO_KEEPALIVE, &so_keepalive,
-                        sizeof(so_keepalive));
+    status =
+        setsockopt(sockfd, SOL_SOCKET, SO_KEEPALIVE, &(int){1}, sizeof(int));
     if (status != 0)
       WARNING("set_sock_opts: failed to set socket keepalive flag");
 
index 2a0b9ee..8947c57 100644 (file)
@@ -145,10 +145,12 @@ int strsplit(char *string, char **fields, size_t size);
  *   is equivalent to the Perl built-in `join'.
  *
  * PARAMETERS
- *   `dst'         Buffer where the result is stored.
+ *   `dst'         Buffer where the result is stored. Can be NULL if you need to
+ *                 determine the required buffer size only.
  *   `dst_len'     Length of the destination buffer. No more than this many
  *                 bytes will be written to the memory pointed to by `dst',
- *                 including the trailing null-byte.
+ *                 including the trailing null-byte. Must be zero if dst is
+ *                 NULL.
  *   `fields'      Array of strings to be joined.
  *   `fields_num'  Number of elements in the `fields' array.
  *   `sep'         String to be inserted between any two elements of `fields'.
@@ -156,9 +158,10 @@ int strsplit(char *string, char **fields, size_t size);
  *                 Instead of passing "" (empty string) one can pass NULL.
  *
  * RETURN VALUE
- *   Returns the number of characters in `dst', NOT including the trailing
- *   null-byte. If an error occurred (empty array or `dst' too small) a value
- *   smaller than zero will be returned.
+ *   Returns the number of characters in the resulting string, excluding a
+ *   tailing null byte. If this value is greater than or equal to "dst_len", the
+ *   result in "dst" is truncated (but still null terminated). On error a
+ *   negative value is returned.
  */
 int strjoin(char *dst, size_t dst_len, char **fields, size_t fields_num,
             const char *sep);
@@ -314,11 +317,17 @@ int format_values(char *ret, size_t ret_len, const data_set_t *ds,
 
 int parse_identifier(char *str, char **ret_host, char **ret_plugin,
                      char **ret_plugin_instance, char **ret_type,
-                     char **ret_type_instance);
+                     char **ret_type_instance, char *default_host);
 int parse_identifier_vl(const char *str, value_list_t *vl);
 int parse_value(const char *value, value_t *ret_value, int ds_type);
 int parse_values(char *buffer, value_list_t *vl, const data_set_t *ds);
 
+/* parse_value_file reads "path" and parses its content as an integer or
+ * floating point, depending on "ds_type". On success, the value is stored in
+ * "ret_value" and zero is returned. On failure, a non-zero value is returned.
+ */
+int parse_value_file(char const *path, value_t *ret_value, int ds_type);
+
 #if !HAVE_GETPWNAM_R
 int getpwnam_r(const char *name, struct passwd *pwbuf, char *buf, size_t buflen,
                struct passwd **pwbufp);
index 9ad6a2a..0c96945 100644 (file)
@@ -145,44 +145,50 @@ DEF_TEST(strsplit) {
 }
 
 DEF_TEST(strjoin) {
-  char buffer[16];
-  char *fields[4];
-  int status;
-
-  fields[0] = "foo";
-  fields[1] = "bar";
-  fields[2] = "baz";
-  fields[3] = "qux";
-
-  status = strjoin(buffer, sizeof(buffer), fields, 2, "!");
-  OK(status == 7);
-  EXPECT_EQ_STR("foo!bar", buffer);
-
-  status = strjoin(buffer, sizeof(buffer), fields, 1, "!");
-  OK(status == 3);
-  EXPECT_EQ_STR("foo", buffer);
-
-  status = strjoin(buffer, sizeof(buffer), fields, 0, "!");
-  OK(status < 0);
+  struct {
+    char **fields;
+    size_t fields_num;
+    char *separator;
 
-  status = strjoin(buffer, sizeof(buffer), fields, 2, "rcht");
-  OK(status == 10);
-  EXPECT_EQ_STR("foorchtbar", buffer);
+    int want_return;
+    char *want_buffer;
+  } cases[] = {
+      /* Normal case. */
+      {(char *[]){"foo", "bar"}, 2, "!", 7, "foo!bar"},
+      /* One field only. */
+      {(char *[]){"foo"}, 1, "!", 3, "foo"},
+      /* No fields at all. */
+      {NULL, 0, "!", 0, ""},
+      /* Longer separator. */
+      {(char *[]){"foo", "bar"}, 2, "rcht", 10, "foorchtbar"},
+      /* Empty separator. */
+      {(char *[]){"foo", "bar"}, 2, "", 6, "foobar"},
+      /* NULL separator. */
+      {(char *[]){"foo", "bar"}, 2, NULL, 6, "foobar"},
+      /* buffer not large enough -> string is truncated. */
+      {(char *[]){"aaaaaa", "bbbbbb", "c!"}, 3, "-", 16, "aaaaaa-bbbbbb-c"},
+      /* buffer not large enough -> last field fills buffer completely. */
+      {(char *[]){"aaaaaaa", "bbbbbbb", "!"}, 3, "-", 17, "aaaaaaa-bbbbbbb"},
+      /* buffer not large enough -> string does *not* end in separator. */
+      {(char *[]){"aaaa", "bbbb", "cccc", "!"}, 4, "-", 16, "aaaa-bbbb-cccc"},
+      /* buffer not large enough -> string does not end with partial
+         separator. */
+      {(char *[]){"aaaaaa", "bbbbbb", "!"}, 3, "+-", 17, "aaaaaa+-bbbbbb"},
+  };
 
-  status = strjoin(buffer, sizeof(buffer), fields, 4, "");
-  OK(status == 12);
-  EXPECT_EQ_STR("foobarbazqux", buffer);
+  for (size_t i = 0; i < STATIC_ARRAY_SIZE(cases); i++) {
+    char buffer[16];
+    int status;
 
-  status = strjoin(buffer, sizeof(buffer), fields, 4, "!");
-  OK(status == 15);
-  EXPECT_EQ_STR("foo!bar!baz!qux", buffer);
+    memset(buffer, 0xFF, sizeof(buffer));
+    status = strjoin(buffer, sizeof(buffer), cases[i].fields,
+                     cases[i].fields_num, cases[i].separator);
+    EXPECT_EQ_INT(cases[i].want_return, status);
+    EXPECT_EQ_STR(cases[i].want_buffer, buffer);
+  }
 
-  fields[0] = "0123";
-  fields[1] = "4567";
-  fields[2] = "8901";
-  fields[3] = "2345";
-  status = strjoin(buffer, sizeof(buffer), fields, 4, "-");
-  OK(status < 0);
+  /* use (NULL, 0) to determine required buffer size. */
+  EXPECT_EQ_INT(3, strjoin(NULL, 0, (char *[]){"a", "b"}, 2, "-"));
 
   return (0);
 }
@@ -351,18 +357,21 @@ DEF_TEST(value_to_rate) {
   };
 
   for (size_t i = 0; i < STATIC_ARRAY_SIZE(cases); i++) {
-    value_to_rate_state_t state = {cases[i].v0,
-                                   TIME_T_TO_CDTIME_T(cases[i].t0)};
+    cdtime_t t0 = TIME_T_TO_CDTIME_T(cases[i].t0);
+    value_to_rate_state_t state = {
+        .last_value = cases[i].v0, .last_time = t0,
+    };
     gauge_t got;
 
     if (cases[i].t0 == 0) {
-      OK(value_to_rate(&got, cases[i].v1, cases[i].ds_type,
-                       TIME_T_TO_CDTIME_T(cases[i].t1), &state) == EAGAIN);
+      EXPECT_EQ_INT(EAGAIN,
+                    value_to_rate(&got, cases[i].v1, cases[i].ds_type,
+                                  TIME_T_TO_CDTIME_T(cases[i].t1), &state));
       continue;
     }
 
-    OK(value_to_rate(&got, cases[i].v1, cases[i].ds_type,
-                     TIME_T_TO_CDTIME_T(cases[i].t1), &state) == 0);
+    EXPECT_EQ_INT(0, value_to_rate(&got, cases[i].v1, cases[i].ds_type,
+                                   TIME_T_TO_CDTIME_T(cases[i].t1), &state));
     EXPECT_EQ_DOUBLE(cases[i].want, got);
   }
 
index 18c4e22..583d819 100644 (file)
 
 #include "collectd.h"
 
+#include "common.h"
 #include "meta_data.h"
 #include "plugin.h"
 
+#define MD_MAX_NONSTRING_CHARS 128
+
 /*
  * Data types
  */
@@ -684,4 +687,64 @@ int meta_data_get_boolean(meta_data_t *md, /* {{{ */
   return (0);
 } /* }}} int meta_data_get_boolean */
 
+int meta_data_as_string(meta_data_t *md, /* {{{ */
+                        const char *key, char **value) {
+  meta_entry_t *e;
+  char *actual;
+  char buffer[MD_MAX_NONSTRING_CHARS]; /* For non-string types. */
+  char *temp;
+  int type;
+
+  if ((md == NULL) || (key == NULL) || (value == NULL))
+    return (-EINVAL);
+
+  pthread_mutex_lock(&md->lock);
+
+  e = md_entry_lookup(md, key);
+  if (e == NULL) {
+    pthread_mutex_unlock(&md->lock);
+    return (-ENOENT);
+  }
+
+  type = e->type;
+
+  switch (type) {
+  case MD_TYPE_STRING:
+    actual = e->value.mv_string;
+    break;
+  case MD_TYPE_SIGNED_INT:
+    ssnprintf(buffer, sizeof(buffer), "%" PRIi64, e->value.mv_signed_int);
+    actual = buffer;
+    break;
+  case MD_TYPE_UNSIGNED_INT:
+    ssnprintf(buffer, sizeof(buffer), "%" PRIu64, e->value.mv_unsigned_int);
+    actual = buffer;
+    break;
+  case MD_TYPE_DOUBLE:
+    ssnprintf(buffer, sizeof(buffer), GAUGE_FORMAT, e->value.mv_double);
+    actual = buffer;
+    break;
+  case MD_TYPE_BOOLEAN:
+    actual = e->value.mv_boolean ? "true" : "false";
+    break;
+  default:
+    pthread_mutex_unlock(&md->lock);
+    ERROR("meta_data_as_string: unknown type %d for key `%s'", type, key);
+    return (-ENOENT);
+  }
+
+  pthread_mutex_unlock(&md->lock);
+
+  temp = md_strdup(actual);
+  if (temp == NULL) {
+    pthread_mutex_unlock(&md->lock);
+    ERROR("meta_data_as_string: md_strdup failed for key `%s'.", key);
+    return (-ENOMEM);
+  }
+
+  *value = temp;
+
+  return (0);
+} /* }}} int meta_data_as_string */
+
 /* vim: set sw=2 sts=2 et fdm=marker : */
index 50584ef..3ef3360 100644 (file)
@@ -65,5 +65,8 @@ int meta_data_get_unsigned_int(meta_data_t *md, const char *key,
 int meta_data_get_double(meta_data_t *md, const char *key, double *value);
 int meta_data_get_boolean(meta_data_t *md, const char *key, _Bool *value);
 
+/* Returns the value as a string, regardless of the type. */
+int meta_data_as_string(meta_data_t *md, const char *key, char **value);
+
 #endif /* META_DATA_H */
 /* vim: set sw=2 sts=2 et : */
index 7a503c7..0f06737 100644 (file)
@@ -25,6 +25,9 @@
  *   Sebastian Harl <sh at tokkee.org>
  **/
 
+/* _GNU_SOURCE is needed in Linux to use pthread_setname_np */
+#define _GNU_SOURCE
+
 #include "collectd.h"
 
 #include "common.h"
 #include "utils_random.h"
 #include "utils_time.h"
 
+#if HAVE_PTHREAD_NP_H
+#include <pthread_np.h> /* for pthread_set_name_np(3) */
+#endif
+
 #include <ltdl.h>
 
 /*
@@ -105,7 +112,7 @@ static c_avl_tree_t *data_sets;
 static char *plugindir = NULL;
 
 #ifndef DEFAULT_MAX_READ_INTERVAL
-#define DEFAULT_MAX_READ_INTERVAL TIME_T_TO_CDTIME_T(86400)
+#define DEFAULT_MAX_READ_INTERVAL TIME_T_TO_CDTIME_T_STATIC(86400)
 #endif
 static c_heap_t *read_heap = NULL;
 static llist_t *read_list;
@@ -113,7 +120,7 @@ static int read_loop = 1;
 static pthread_mutex_t read_lock = PTHREAD_MUTEX_INITIALIZER;
 static pthread_cond_t read_cond = PTHREAD_COND_INITIALIZER;
 static pthread_t *read_threads = NULL;
-static int read_threads_num = 0;
+static size_t read_threads_num = 0;
 static cdtime_t max_read_interval = DEFAULT_MAX_READ_INTERVAL;
 
 static write_queue_t *write_queue_head;
@@ -146,34 +153,27 @@ static const char *plugin_get_dir(void) {
     return (plugindir);
 }
 
-static void plugin_update_internal_statistics(void) { /* {{{ */
-  derive_t copy_write_queue_length;
-  value_list_t vl = VALUE_LIST_INIT;
-  value_t values[2];
-
-  copy_write_queue_length = write_queue_length;
+static int plugin_update_internal_statistics(void) { /* {{{ */
+  gauge_t copy_write_queue_length = (gauge_t)write_queue_length;
 
   /* Initialize `vl' */
-  vl.values = values;
-  vl.values_len = 2;
-  vl.time = 0;
-  sstrncpy(vl.host, hostname_g, sizeof(vl.host));
+  value_list_t vl = VALUE_LIST_INIT;
   sstrncpy(vl.plugin, "collectd", sizeof(vl.plugin));
-
-  vl.type_instance[0] = 0;
-  vl.values_len = 1;
+  vl.interval = plugin_get_interval();
 
   /* Write queue */
   sstrncpy(vl.plugin_instance, "write_queue", sizeof(vl.plugin_instance));
 
   /* Write queue : queue length */
-  vl.values[0].gauge = (gauge_t)copy_write_queue_length;
+  vl.values = &(value_t){.gauge = copy_write_queue_length};
+  vl.values_len = 1;
   sstrncpy(vl.type, "queue_length", sizeof(vl.type));
   vl.type_instance[0] = 0;
   plugin_dispatch_values(&vl);
 
   /* Write queue : Values dropped (queue length > low limit) */
-  vl.values[0].derive = (derive_t)stats_values_dropped;
+  vl.values = &(value_t){.gauge = (gauge_t)stats_values_dropped};
+  vl.values_len = 1;
   sstrncpy(vl.type, "derive", sizeof(vl.type));
   sstrncpy(vl.type_instance, "dropped", sizeof(vl.type_instance));
   plugin_dispatch_values(&vl);
@@ -182,13 +182,14 @@ static void plugin_update_internal_statistics(void) { /* {{{ */
   sstrncpy(vl.plugin_instance, "cache", sizeof(vl.plugin_instance));
 
   /* Cache : Nb entry in cache tree */
-  vl.values[0].gauge = (gauge_t)uc_get_size();
+  vl.values = &(value_t){.gauge = (gauge_t)uc_get_size()};
+  vl.values_len = 1;
   sstrncpy(vl.type, "cache_size", sizeof(vl.type));
   vl.type_instance[0] = 0;
   plugin_dispatch_values(&vl);
 
-  return;
-} /* }}} void plugin_update_internal_statistics */
+  return 0;
+} /* }}} int plugin_update_internal_statistics */
 
 static void destroy_callback(callback_func_t *cf) /* {{{ */
 {
@@ -486,11 +487,8 @@ static void *plugin_read_thread(void __attribute__((unused)) * args) {
      * pthread_cond_timedwait returns. */
     rc = 0;
     while ((read_loop != 0) && (cdtime() < rf->rf_next_read) && rc == 0) {
-      struct timespec ts = {0};
-
-      CDTIME_T_TO_TIMESPEC(rf->rf_next_read, &ts);
-
-      rc = pthread_cond_timedwait(&read_cond, &read_lock, &ts);
+      rc = pthread_cond_timedwait(&read_cond, &read_lock,
+                                  &CDTIME_T_TO_TIMESPEC(rf->rf_next_read));
     }
 
     /* Must hold `read_lock' when accessing `rf->rf_type'. */
@@ -601,7 +599,38 @@ static void *plugin_read_thread(void __attribute__((unused)) * args) {
   return ((void *)0);
 } /* void *plugin_read_thread */
 
-static void start_read_threads(int num) {
+#ifdef PTHREAD_MAX_NAMELEN_NP
+#define THREAD_NAME_MAX PTHREAD_MAX_NAMELEN_NP
+#else
+#define THREAD_NAME_MAX 16
+#endif
+
+static void set_thread_name(pthread_t tid, char const *name) {
+#if defined(HAVE_PTHREAD_SETNAME_NP) || defined(HAVE_PTHREAD_SET_NAME_NP)
+
+  /* glibc limits the length of the name and fails if the passed string
+   * is too long, so we truncate it here. */
+  char n[THREAD_NAME_MAX];
+  if (strlen(name) >= THREAD_NAME_MAX)
+    WARNING("set_thread_name(\"%s\"): name too long", name);
+  sstrncpy(n, name, sizeof(n));
+
+#if defined(HAVE_PTHREAD_SETNAME_NP)
+  int status = pthread_setname_np(tid, n);
+  if (status != 0) {
+    char errbuf[1024];
+    ERROR("set_thread_name(\"%s\"): %s", n,
+          sstrerror(status, errbuf, sizeof(errbuf)));
+  }
+#else /* if defined(HAVE_PTHREAD_SET_NAME_NP) */
+  pthread_set_name_np(tid, n);
+#endif
+
+#endif
+}
+
+static void start_read_threads(size_t num) /* {{{ */
+{
   if (read_threads != NULL)
     return;
 
@@ -612,22 +641,31 @@ static void start_read_threads(int num) {
   }
 
   read_threads_num = 0;
-  for (int i = 0; i < num; i++) {
-    if (pthread_create(read_threads + read_threads_num, NULL,
-                       plugin_read_thread, NULL) == 0) {
-      read_threads_num++;
-    } else {
-      ERROR("plugin: start_read_threads: pthread_create failed.");
+  for (size_t i = 0; i < num; i++) {
+    int status = pthread_create(read_threads + read_threads_num,
+                                /* attr = */ NULL, plugin_read_thread,
+                                /* arg = */ NULL);
+    if (status != 0) {
+      char errbuf[1024];
+      ERROR("plugin: start_read_threads: pthread_create failed "
+            "with status %i (%s).",
+            status, sstrerror(status, errbuf, sizeof(errbuf)));
       return;
     }
+
+    char name[THREAD_NAME_MAX];
+    ssnprintf(name, sizeof(name), "reader#%zu", read_threads_num);
+    set_thread_name(read_threads[read_threads_num], name);
+
+    read_threads_num++;
   } /* for (i) */
-} /* void start_read_threads */
+} /* }}} void start_read_threads */
 
 static void stop_read_threads(void) {
   if (read_threads == NULL)
     return;
 
-  INFO("collectd: Stopping %i read threads.", read_threads_num);
+  INFO("collectd: Stopping %zu read threads.", read_threads_num);
 
   pthread_mutex_lock(&read_lock);
   read_loop = 0;
@@ -635,7 +673,7 @@ static void stop_read_threads(void) {
   pthread_cond_broadcast(&read_cond);
   pthread_mutex_unlock(&read_lock);
 
-  for (int i = 0; i < read_threads_num; i++) {
+  for (size_t i = 0; i < read_threads_num; i++) {
     if (pthread_join(read_threads[i], NULL) != 0) {
       ERROR("plugin: stop_read_threads: pthread_join failed.");
     }
@@ -668,6 +706,9 @@ plugin_value_list_clone(value_list_t const *vl_orig) /* {{{ */
     return (NULL);
   memcpy(vl, vl_orig, sizeof(*vl));
 
+  if (vl->host[0] == 0)
+    sstrncpy(vl->host, hostname_g, sizeof(vl->host));
+
   vl->values = calloc(vl_orig->values_len, sizeof(*vl->values));
   if (vl->values == NULL) {
     plugin_value_list_free(vl);
@@ -808,11 +849,9 @@ static void start_write_threads(size_t num) /* {{{ */
 
   write_threads_num = 0;
   for (size_t i = 0; i < num; i++) {
-    int status;
-
-    status = pthread_create(write_threads + write_threads_num,
-                            /* attr = */ NULL, plugin_write_thread,
-                            /* arg = */ NULL);
+    int status = pthread_create(write_threads + write_threads_num,
+                                /* attr = */ NULL, plugin_write_thread,
+                                /* arg = */ NULL);
     if (status != 0) {
       char errbuf[1024];
       ERROR("plugin: start_write_threads: pthread_create failed "
@@ -821,6 +860,10 @@ static void start_write_threads(size_t num) /* {{{ */
       return;
     }
 
+    char name[THREAD_NAME_MAX];
+    ssnprintf(name, sizeof(name), "writer#%zu", write_threads_num);
+    set_thread_name(write_threads[write_threads_num], name);
+
     write_threads_num++;
   } /* for (i) */
 } /* }}} void start_write_threads */
@@ -1528,8 +1571,10 @@ int plugin_init_all(void) {
   /* Init the value cache */
   uc_init();
 
-  if (IS_TRUE(global_option_get("CollectInternalStats")))
+  if (IS_TRUE(global_option_get("CollectInternalStats"))) {
     record_statistics = 1;
+    plugin_register_read("collectd", plugin_update_internal_statistics);
+  }
 
   chain_name = global_option_get("PreCacheChain");
   pre_cache_chain = fc_chain_get_by_name(chain_name);
@@ -1610,16 +1655,13 @@ int plugin_init_all(void) {
     rt = global_option_get("ReadThreads");
     num = atoi(rt);
     if (num != -1)
-      start_read_threads((num > 0) ? num : 5);
+      start_read_threads((num > 0) ? ((size_t)num) : 5);
   }
   return ret;
 } /* void plugin_init_all */
 
 /* TODO: Rename this function. */
 void plugin_read_all(void) {
-  if (record_statistics) {
-    plugin_update_internal_statistics();
-  }
   uc_check_timeout();
 
   return;
@@ -1882,15 +1924,16 @@ static int plugin_dispatch_values_internal(value_list_t *vl) {
   int status;
   static c_complain_t no_write_complaint = C_COMPLAIN_INIT_STATIC;
 
-  value_t *saved_values;
-  int saved_values_len;
-
   data_set_t *ds;
 
-  int free_meta_data = 0;
+  _Bool free_meta_data = 0;
+
+  assert(vl != NULL);
 
-  assert(vl);
-  assert(vl->plugin);
+  /* These fields are initialized by plugin_value_list_clone() if needed: */
+  assert(vl->host[0] != 0);
+  assert(vl->time != 0); /* The time is determined at _enqueue_ time. */
+  assert(vl->interval != 0);
 
   if (vl->type[0] == 0 || vl->values == NULL || vl->values_len < 1) {
     ERROR("plugin_dispatch_values: Invalid value list "
@@ -1928,11 +1971,6 @@ static int plugin_dispatch_values_internal(value_list_t *vl) {
     return (-1);
   }
 
-  /* Assured by plugin_value_list_clone(). The time is determined at
-   * _enqueue_ time. */
-  assert(vl->time != 0);
-  assert(vl->interval != 0);
-
   DEBUG("plugin_dispatch_values: time = %.3f; interval = %.3f; "
         "host = %s; "
         "plugin = %s; plugin_instance = %s; "
@@ -1966,26 +2004,6 @@ static int plugin_dispatch_values_internal(value_list_t *vl) {
   escape_slashes(vl->type, sizeof(vl->type));
   escape_slashes(vl->type_instance, sizeof(vl->type_instance));
 
-  /* Copy the values. This way, we can assure `targets' that they get
-   * dynamically allocated values, which they can free and replace if
-   * they like. */
-  if ((pre_cache_chain != NULL) || (post_cache_chain != NULL)) {
-    saved_values = vl->values;
-    saved_values_len = vl->values_len;
-
-    vl->values = (value_t *)calloc(vl->values_len, sizeof(*vl->values));
-    if (vl->values == NULL) {
-      ERROR("plugin_dispatch_values: calloc failed.");
-      vl->values = saved_values;
-      return (-1);
-    }
-    memcpy(vl->values, saved_values, vl->values_len * sizeof(*vl->values));
-  } else /* if ((pre == NULL) && (post == NULL)) */
-  {
-    saved_values = NULL;
-    saved_values_len = 0;
-  }
-
   if (pre_cache_chain != NULL) {
     status = fc_process_chain(ds, vl, pre_cache_chain);
     if (status < 0) {
@@ -1993,16 +2011,8 @@ static int plugin_dispatch_values_internal(value_list_t *vl) {
               "pre-cache chain failed with "
               "status %i (%#x).",
               status, status);
-    } else if (status == FC_TARGET_STOP) {
-      /* Restore the state of the value_list so that plugins
-       * don't get confused.. */
-      if (saved_values != NULL) {
-        sfree(vl->values);
-        vl->values = saved_values;
-        vl->values_len = saved_values_len;
-      }
+    } else if (status == FC_TARGET_STOP)
       return (0);
-    }
   }
 
   /* Update the value cache */
@@ -2019,14 +2029,6 @@ static int plugin_dispatch_values_internal(value_list_t *vl) {
   } else
     fc_default_action(ds, vl);
 
-  /* Restore the state of the value_list so that plugins don't get
-   * confused.. */
-  if (saved_values != NULL) {
-    sfree(vl->values);
-    vl->values = saved_values;
-    vl->values_len = saved_values_len;
-  }
-
   if ((free_meta_data != 0) && (vl->meta != NULL)) {
     meta_data_destroy(vl->meta);
     vl->meta = NULL;
@@ -2565,18 +2567,28 @@ static void *plugin_thread_start(void *arg) {
 } /* void *plugin_thread_start */
 
 int plugin_thread_create(pthread_t *thread, const pthread_attr_t *attr,
-                         void *(*start_routine)(void *), void *arg) {
+                         void *(*start_routine)(void *), void *arg,
+                         char const *name) {
   plugin_thread_t *plugin_thread;
 
   plugin_thread = malloc(sizeof(*plugin_thread));
   if (plugin_thread == NULL)
-    return -1;
+    return ENOMEM;
 
   plugin_thread->ctx = plugin_get_ctx();
   plugin_thread->start_routine = start_routine;
   plugin_thread->arg = arg;
 
-  return pthread_create(thread, attr, plugin_thread_start, plugin_thread);
+  int ret = pthread_create(thread, attr, plugin_thread_start, plugin_thread);
+  if (ret != 0) {
+    sfree(plugin_thread);
+    return ret;
+  }
+
+  if (name != NULL)
+    set_thread_name(*thread, name);
+
+  return 0;
 } /* int plugin_thread_create */
 
 /* vim: set sw=8 ts=8 noet fdm=marker : */
index 8bdb4c2..f6448a5 100644 (file)
 /*
  * Public data types
  */
+struct identifier_s {
+  char *host;
+  char *plugin;
+  char *plugin_instance;
+  char *type;
+  char *type_instance;
+};
+typedef struct identifier_s identifier_t;
+
 typedef unsigned long long counter_t;
 typedef double gauge_t;
 typedef int64_t derive_t;
@@ -111,9 +120,7 @@ struct value_list_s {
 typedef struct value_list_s value_list_t;
 
 #define VALUE_LIST_INIT                                                        \
-  { NULL, 0, 0, plugin_get_interval(), "localhost", "", "", "", "", NULL }
-#define VALUE_LIST_STATIC                                                      \
-  { NULL, 0, 0, 0, "localhost", "", "", "", "", NULL }
+  { .values = NULL, .meta = NULL }
 
 struct data_source_s {
   char name[DATA_MAX_NAME_LEN];
@@ -440,7 +447,8 @@ cdtime_t plugin_get_interval(void);
  */
 
 int plugin_thread_create(pthread_t *thread, const pthread_attr_t *attr,
-                         void *(*start_routine)(void *), void *arg);
+                         void *(*start_routine)(void *), void *arg,
+                         char const *name);
 
 /*
  * Plugins need to implement this
index b57658f..ddfc789 100644 (file)
@@ -51,6 +51,19 @@ int plugin_register_shutdown(const char *name, int (*callback)(void)) {
 
 int plugin_dispatch_values(value_list_t const *vl) { return ENOTSUP; }
 
+int plugin_flush(const char *plugin, cdtime_t timeout, const char *identifier) {
+  return ENOTSUP;
+}
+
+static data_source_t magic_ds[] = {{"value", DS_TYPE_DERIVE, 0.0, NAN}};
+static data_set_t magic = {"MAGIC", 1, magic_ds};
+const data_set_t *plugin_get_ds(const char *name) {
+  if (strcmp(name, "MAGIC"))
+    return NULL;
+
+  return &magic;
+}
+
 void plugin_log(int level, char const *format, ...) {
   char buffer[1024];
   va_list ap;
index 530bdbb..aa8ce9e 100644 (file)
@@ -222,74 +222,49 @@ int uc_init(void) {
 } /* int uc_init */
 
 int uc_check_timeout(void) {
-  cdtime_t now;
-  cache_entry_t *ce;
-
-  char **keys = NULL;
-  cdtime_t *keys_time = NULL;
-  cdtime_t *keys_interval = NULL;
-  int keys_len = 0;
-
-  char *key;
-  c_avl_iterator_t *iter;
-
-  int status;
+  struct {
+    char *key;
+    cdtime_t time;
+    cdtime_t interval;
+  } *expired = NULL;
+  size_t expired_num = 0;
 
   pthread_mutex_lock(&cache_lock);
-
-  now = cdtime();
+  cdtime_t now = cdtime();
 
   /* Build a list of entries to be flushed */
-  iter = c_avl_get_iterator(cache_tree);
+  c_avl_iterator_t *iter = c_avl_get_iterator(cache_tree);
+  char *key = NULL;
+  cache_entry_t *ce = NULL;
   while (c_avl_iterator_next(iter, (void *)&key, (void *)&ce) == 0) {
-    char **tmp;
-    cdtime_t *tmp_time;
-
     /* If the entry is fresh enough, continue. */
     if ((now - ce->last_update) < (ce->interval * timeout_g))
       continue;
 
-    /* If entry has not been updated, add to `keys' array */
-    tmp = realloc((void *)keys, (keys_len + 1) * sizeof(char *));
+    void *tmp = realloc(expired, (expired_num + 1) * sizeof(*expired));
     if (tmp == NULL) {
       ERROR("uc_check_timeout: realloc failed.");
       continue;
     }
-    keys = tmp;
-
-    tmp_time = realloc(keys_time, (keys_len + 1) * sizeof(*keys_time));
-    if (tmp_time == NULL) {
-      ERROR("uc_check_timeout: realloc failed.");
-      continue;
-    }
-    keys_time = tmp_time;
+    expired = tmp;
 
-    tmp_time = realloc(keys_interval, (keys_len + 1) * sizeof(*keys_interval));
-    if (tmp_time == NULL) {
-      ERROR("uc_check_timeout: realloc failed.");
-      continue;
-    }
-    keys_interval = tmp_time;
+    expired[expired_num].key = strdup(key);
+    expired[expired_num].time = ce->last_time;
+    expired[expired_num].interval = ce->interval;
 
-    keys[keys_len] = strdup(key);
-    if (keys[keys_len] == NULL) {
+    if (expired[expired_num].key == NULL) {
       ERROR("uc_check_timeout: strdup failed.");
       continue;
     }
-    keys_time[keys_len] = ce->last_time;
-    keys_interval[keys_len] = ce->interval;
 
-    keys_len++;
+    expired_num++;
   } /* while (c_avl_iterator_next) */
 
   c_avl_iterator_destroy(iter);
   pthread_mutex_unlock(&cache_lock);
 
-  if (keys_len == 0) {
-    /* realloc() may have been called for these. */
-    sfree(keys);
-    sfree(keys_time);
-    sfree(keys_interval);
+  if (expired_num == 0) {
+    sfree(expired);
     return (0);
   }
 
@@ -298,50 +273,42 @@ int uc_check_timeout(void) {
    * including plugin specific meta data, rates, history, …. This must be done
    * without holding the lock, otherwise we will run into a deadlock if a
    * plugin calls the cache interface. */
-  for (int i = 0; i < keys_len; i++) {
-    value_list_t vl = VALUE_LIST_INIT;
-
-    vl.values = NULL;
-    vl.values_len = 0;
-    vl.meta = NULL;
-
-    status = parse_identifier_vl(keys[i], &vl);
-    if (status != 0) {
-      ERROR("uc_check_timeout: parse_identifier_vl (\"%s\") failed.", keys[i]);
+  for (size_t i = 0; i < expired_num; i++) {
+    value_list_t vl = {
+        .time = expired[i].time, .interval = expired[i].interval,
+    };
+
+    if (parse_identifier_vl(expired[i].key, &vl) != 0) {
+      ERROR("uc_check_timeout: parse_identifier_vl (\"%s\") failed.",
+            expired[i].key);
       continue;
     }
 
-    vl.time = keys_time[i];
-    vl.interval = keys_interval[i];
-
     plugin_dispatch_missing(&vl);
-  } /* for (i = 0; i < keys_len; i++) */
+  } /* for (i = 0; i < expired_num; i++) */
 
   /* Now actually remove all the values from the cache. We don't re-evaluate
    * the timestamp again, so in theory it is possible we remove a value after
    * it is updated here. */
   pthread_mutex_lock(&cache_lock);
-  for (int i = 0; i < keys_len; i++) {
-    key = NULL;
-    ce = NULL;
-
-    status = c_avl_remove(cache_tree, keys[i], (void *)&key, (void *)&ce);
-    if (status != 0) {
-      ERROR("uc_check_timeout: c_avl_remove (\"%s\") failed.", keys[i]);
-      sfree(keys[i]);
+  for (size_t i = 0; i < expired_num; i++) {
+    char *key = NULL;
+    cache_entry_t *value = NULL;
+
+    if (c_avl_remove(cache_tree, expired[i].key, (void *)&key,
+                     (void *)&value) != 0) {
+      ERROR("uc_check_timeout: c_avl_remove (\"%s\") failed.", expired[i].key);
+      sfree(expired[i].key);
       continue;
     }
-
-    sfree(keys[i]);
     sfree(key);
-    cache_free(ce);
-  } /* for (i = 0; i < keys_len; i++) */
-  pthread_mutex_unlock(&cache_lock);
+    cache_free(value);
 
-  sfree(keys);
-  sfree(keys_time);
-  sfree(keys_interval);
+    sfree(expired[i].key);
+  } /* for (i = 0; i < expired_num; i++) */
+  pthread_mutex_unlock(&cache_lock);
 
+  sfree(expired);
   return (0);
 } /* int uc_check_timeout */
 
@@ -455,6 +422,9 @@ int uc_get_rate_by_name(const char *name, gauge_t **ret_values,
 
     /* remove missing values from getval */
     if (ce->state == STATE_MISSING) {
+      DEBUG("utils_cache: uc_get_rate_by_name: requested metric \"%s\" is in "
+            "state \"missing\".",
+            name);
       status = -1;
     } else {
       ret_num = ce->values_num;
@@ -498,7 +468,7 @@ gauge_t *uc_get_rate(const data_set_t *ds, const value_list_t *vl) {
 
   /* This is important - the caller has no other way of knowing how many
    * values are returned. */
-  if (ret_num != (size_t)ds->ds_num) {
+  if (ret_num != ds->ds_num) {
     ERROR("utils_cache: uc_get_rate: ds[%s] has %zu values, "
           "but uc_get_rate_by_name returned %zu.",
           ds->type, ds->ds_num, ret_num);
index 5c8127f..2f40873 100644 (file)
@@ -86,9 +86,9 @@ uc_iter_t *uc_get_iterator(void);
  *
  * PARAMETERS
  *   `iter'     The iterator object to advance.
- *   `ret_name' Pointer to a string where to store the name. The returned
- *              value is a copy of the value and has to be freed by the
- *              caller.
+ *   `ret_name' Optional pointer to a string where to store the name. If not
+ *              NULL, the returned value is a copy of the value and has to be
+ *              freed by the caller.
  *
  * RETURN VALUE
  *   Zero upon success or non-zero if the iterator ie NULL or no further
index 9031f4d..0f0077d 100644 (file)
  *   Florian octo Forster <octo at collectd.org>
  */
 
+#include <errno.h>
 #include "utils_cache.h"
 
 gauge_t *uc_get_rate(__attribute__((unused)) data_set_t const *ds,
                      __attribute__((unused)) value_list_t const *vl) {
+  errno = ENOTSUP;
   return (NULL);
 }
+
+int uc_get_rate_by_name(const char *name, gauge_t **ret_values,
+                        size_t *ret_values_num) {
+  return (ENOTSUP);
+}
+
+int uc_get_names(char ***ret_names, cdtime_t **ret_times, size_t *ret_number) {
+  return (ENOTSUP);
+}
diff --git a/src/daemon/utils_match.c b/src/daemon/utils_match.c
deleted file mode 100644 (file)
index be5dfd6..0000000
+++ /dev/null
@@ -1,348 +0,0 @@
-/**
- * collectd - src/utils_match.c
- * Copyright (C) 2008-2014  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 <octo at collectd.org>
- **/
-
-#include "collectd.h"
-
-#include "common.h"
-#include "plugin.h"
-
-#include "utils_match.h"
-
-#include <regex.h>
-
-#define UTILS_MATCH_FLAGS_FREE_USER_DATA 0x01
-#define UTILS_MATCH_FLAGS_EXCLUDE_REGEX 0x02
-#define UTILS_MATCH_FLAGS_REGEX 0x04
-
-struct cu_match_s {
-  regex_t regex;
-  regex_t excluderegex;
-  int flags;
-
-  int (*callback)(const char *str, char *const *matches, size_t matches_num,
-                  void *user_data);
-  void *user_data;
-};
-
-/*
- * Private functions
- */
-static char *match_substr(const char *str, int begin, int end) {
-  char *ret;
-  size_t ret_len;
-
-  if ((begin < 0) || (end < 0) || (begin >= end))
-    return (NULL);
-  if ((size_t)end > (strlen(str) + 1)) {
-    ERROR("utils_match: match_substr: `end' points after end of string.");
-    return (NULL);
-  }
-
-  ret_len = end - begin;
-  ret = malloc(ret_len + 1);
-  if (ret == NULL) {
-    ERROR("utils_match: match_substr: malloc failed.");
-    return (NULL);
-  }
-
-  sstrncpy(ret, str + begin, ret_len + 1);
-  return (ret);
-} /* char *match_substr */
-
-static int default_callback(const char __attribute__((unused)) * str,
-                            char *const *matches, size_t matches_num,
-                            void *user_data) {
-  cu_match_value_t *data = (cu_match_value_t *)user_data;
-
-  if (data->ds_type & UTILS_MATCH_DS_TYPE_GAUGE) {
-    gauge_t value;
-    char *endptr = NULL;
-
-    if (data->ds_type & UTILS_MATCH_CF_GAUGE_INC) {
-      data->value.gauge = isnan(data->value.gauge) ? 1 : data->value.gauge + 1;
-      data->values_num++;
-      return (0);
-    }
-
-    if (matches_num < 2)
-      return (-1);
-
-    value = (gauge_t)strtod(matches[1], &endptr);
-    if (matches[1] == endptr)
-      return (-1);
-
-    if ((data->values_num == 0) ||
-        (data->ds_type & UTILS_MATCH_CF_GAUGE_LAST)) {
-      data->value.gauge = value;
-    } else if (data->ds_type & UTILS_MATCH_CF_GAUGE_AVERAGE) {
-      double f = ((double)data->values_num) / ((double)(data->values_num + 1));
-      data->value.gauge = (data->value.gauge * f) + (value * (1.0 - f));
-    } else if (data->ds_type & UTILS_MATCH_CF_GAUGE_MIN) {
-      if (data->value.gauge > value)
-        data->value.gauge = value;
-    } else if (data->ds_type & UTILS_MATCH_CF_GAUGE_MAX) {
-      if (data->value.gauge < value)
-        data->value.gauge = value;
-    } else if (data->ds_type & UTILS_MATCH_CF_GAUGE_ADD) {
-      data->value.gauge += value;
-    } else {
-      ERROR("utils_match: default_callback: obj->ds_type is invalid!");
-      return (-1);
-    }
-
-    data->values_num++;
-  } else if (data->ds_type & UTILS_MATCH_DS_TYPE_COUNTER) {
-    counter_t value;
-    char *endptr = NULL;
-
-    if (data->ds_type & UTILS_MATCH_CF_COUNTER_INC) {
-      data->value.counter++;
-      data->values_num++;
-      return (0);
-    }
-
-    if (matches_num < 2)
-      return (-1);
-
-    value = (counter_t)strtoull(matches[1], &endptr, 0);
-    if (matches[1] == endptr)
-      return (-1);
-
-    if (data->ds_type & UTILS_MATCH_CF_COUNTER_SET)
-      data->value.counter = value;
-    else if (data->ds_type & UTILS_MATCH_CF_COUNTER_ADD)
-      data->value.counter += value;
-    else {
-      ERROR("utils_match: default_callback: obj->ds_type is invalid!");
-      return (-1);
-    }
-
-    data->values_num++;
-  } else if (data->ds_type & UTILS_MATCH_DS_TYPE_DERIVE) {
-    derive_t value;
-    char *endptr = NULL;
-
-    if (data->ds_type & UTILS_MATCH_CF_DERIVE_INC) {
-      data->value.derive++;
-      data->values_num++;
-      return (0);
-    }
-
-    if (matches_num < 2)
-      return (-1);
-
-    value = (derive_t)strtoll(matches[1], &endptr, 0);
-    if (matches[1] == endptr)
-      return (-1);
-
-    if (data->ds_type & UTILS_MATCH_CF_DERIVE_SET)
-      data->value.derive = value;
-    else if (data->ds_type & UTILS_MATCH_CF_DERIVE_ADD)
-      data->value.derive += value;
-    else {
-      ERROR("utils_match: default_callback: obj->ds_type is invalid!");
-      return (-1);
-    }
-
-    data->values_num++;
-  } else if (data->ds_type & UTILS_MATCH_DS_TYPE_ABSOLUTE) {
-    absolute_t value;
-    char *endptr = NULL;
-
-    if (matches_num < 2)
-      return (-1);
-
-    value = (absolute_t)strtoull(matches[1], &endptr, 0);
-    if (matches[1] == endptr)
-      return (-1);
-
-    if (data->ds_type & UTILS_MATCH_CF_ABSOLUTE_SET)
-      data->value.absolute = value;
-    else {
-      ERROR("utils_match: default_callback: obj->ds_type is invalid!");
-      return (-1);
-    }
-
-    data->values_num++;
-  } else {
-    ERROR("utils_match: default_callback: obj->ds_type is invalid!");
-    return (-1);
-  }
-
-  return (0);
-} /* int default_callback */
-
-/*
- * Public functions
- */
-cu_match_t *
-match_create_callback(const char *regex, const char *excluderegex,
-                      int (*callback)(const char *str, char *const *matches,
-                                      size_t matches_num, void *user_data),
-                      void *user_data) {
-  cu_match_t *obj;
-  int status;
-
-  DEBUG("utils_match: match_create_callback: regex = %s, excluderegex = %s",
-        regex, excluderegex);
-
-  obj = calloc(1, sizeof(*obj));
-  if (obj == NULL)
-    return (NULL);
-
-  status = regcomp(&obj->regex, regex, REG_EXTENDED | REG_NEWLINE);
-  if (status != 0) {
-    ERROR("Compiling the regular expression \"%s\" failed.", regex);
-    sfree(obj);
-    return (NULL);
-  }
-  obj->flags |= UTILS_MATCH_FLAGS_REGEX;
-
-  if (excluderegex && strcmp(excluderegex, "") != 0) {
-    status = regcomp(&obj->excluderegex, excluderegex, REG_EXTENDED);
-    if (status != 0) {
-      ERROR("Compiling the excluding regular expression \"%s\" failed.",
-            excluderegex);
-      sfree(obj);
-      return (NULL);
-    }
-    obj->flags |= UTILS_MATCH_FLAGS_EXCLUDE_REGEX;
-  }
-
-  obj->callback = callback;
-  obj->user_data = user_data;
-
-  return (obj);
-} /* cu_match_t *match_create_callback */
-
-cu_match_t *match_create_simple(const char *regex, const char *excluderegex,
-                                int match_ds_type) {
-  cu_match_value_t *user_data;
-  cu_match_t *obj;
-
-  user_data = calloc(1, sizeof(*user_data));
-  if (user_data == NULL)
-    return (NULL);
-  user_data->ds_type = match_ds_type;
-
-  obj = match_create_callback(regex, excluderegex, default_callback, user_data);
-  if (obj == NULL) {
-    sfree(user_data);
-    return (NULL);
-  }
-
-  obj->flags |= UTILS_MATCH_FLAGS_FREE_USER_DATA;
-
-  return (obj);
-} /* cu_match_t *match_create_simple */
-
-void match_value_reset(cu_match_value_t *mv) {
-  if (mv == NULL)
-    return;
-
-  if (mv->ds_type & UTILS_MATCH_DS_TYPE_GAUGE) {
-    mv->value.gauge = NAN;
-    mv->values_num = 0;
-  }
-} /* }}} void match_value_reset */
-
-void match_destroy(cu_match_t *obj) {
-  if (obj == NULL)
-    return;
-
-  if (obj->flags & UTILS_MATCH_FLAGS_FREE_USER_DATA)
-    sfree(obj->user_data);
-  if (obj->flags & UTILS_MATCH_FLAGS_REGEX)
-    regfree(&obj->regex);
-  if (obj->flags & UTILS_MATCH_FLAGS_EXCLUDE_REGEX)
-    regfree(&obj->excluderegex);
-
-  sfree(obj);
-} /* void match_destroy */
-
-int match_apply(cu_match_t *obj, const char *str) {
-  int status;
-  regmatch_t re_match[32];
-  char *matches[32] = {0};
-  size_t matches_num;
-
-  if ((obj == NULL) || (str == NULL))
-    return (-1);
-
-  if (obj->flags & UTILS_MATCH_FLAGS_EXCLUDE_REGEX) {
-    status =
-        regexec(&obj->excluderegex, str, STATIC_ARRAY_SIZE(re_match), re_match,
-                /* eflags = */ 0);
-    /* Regex did match, so exclude this line */
-    if (status == 0) {
-      DEBUG("ExludeRegex matched, don't count that line\n");
-      return (0);
-    }
-  }
-
-  status = regexec(&obj->regex, str, STATIC_ARRAY_SIZE(re_match), re_match,
-                   /* eflags = */ 0);
-
-  /* Regex did not match */
-  if (status != 0)
-    return (0);
-
-  for (matches_num = 0; matches_num < STATIC_ARRAY_SIZE(matches);
-       matches_num++) {
-    if ((re_match[matches_num].rm_so < 0) || (re_match[matches_num].rm_eo < 0))
-      break;
-
-    matches[matches_num] = match_substr(str, re_match[matches_num].rm_so,
-                                        re_match[matches_num].rm_eo);
-    if (matches[matches_num] == NULL) {
-      status = -1;
-      break;
-    }
-  }
-
-  if (status != 0) {
-    ERROR("utils_match: match_apply: match_substr failed.");
-  } else {
-    status = obj->callback(str, matches, matches_num, obj->user_data);
-    if (status != 0) {
-      ERROR("utils_match: match_apply: callback failed.");
-    }
-  }
-
-  for (size_t i = 0; i < matches_num; i++) {
-    sfree(matches[i]);
-  }
-
-  return (status);
-} /* int match_apply */
-
-void *match_get_user_data(cu_match_t *obj) {
-  if (obj == NULL)
-    return (NULL);
-  return (obj->user_data);
-} /* void *match_get_user_data */
-
-/* vim: set sw=2 sts=2 ts=8 : */
diff --git a/src/daemon/utils_match.h b/src/daemon/utils_match.h
deleted file mode 100644 (file)
index ad999cd..0000000
+++ /dev/null
@@ -1,175 +0,0 @@
-/**
- * collectd - src/utils_match.h
- * Copyright (C) 2008-2014  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 <octo at collectd.org>
- **/
-
-#ifndef UTILS_MATCH_H
-#define UTILS_MATCH_H 1
-
-#include "plugin.h"
-
-/*
- * Each type may have 12 sub-types
- * 0x1000 = 1000000000000
- *          ^             <- Type bit
- *           ^^^^^^^^^^^^ <- Subtype bits
- */
-#define UTILS_MATCH_DS_TYPE_GAUGE 0x1000
-#define UTILS_MATCH_DS_TYPE_COUNTER 0x2000
-#define UTILS_MATCH_DS_TYPE_DERIVE 0x4000
-#define UTILS_MATCH_DS_TYPE_ABSOLUTE 0x8000
-
-#define UTILS_MATCH_CF_GAUGE_AVERAGE 0x01
-#define UTILS_MATCH_CF_GAUGE_MIN 0x02
-#define UTILS_MATCH_CF_GAUGE_MAX 0x04
-#define UTILS_MATCH_CF_GAUGE_LAST 0x08
-#define UTILS_MATCH_CF_GAUGE_INC 0x10
-#define UTILS_MATCH_CF_GAUGE_ADD 0x20
-
-#define UTILS_MATCH_CF_COUNTER_SET 0x01
-#define UTILS_MATCH_CF_COUNTER_ADD 0x02
-#define UTILS_MATCH_CF_COUNTER_INC 0x04
-
-#define UTILS_MATCH_CF_DERIVE_SET 0x01
-#define UTILS_MATCH_CF_DERIVE_ADD 0x02
-#define UTILS_MATCH_CF_DERIVE_INC 0x04
-
-#define UTILS_MATCH_CF_ABSOLUTE_SET 0x01
-#define UTILS_MATCH_CF_ABSOLUTE_ADD 0x02
-#define UTILS_MATCH_CF_ABSOLUTE_INC 0x04
-
-/*
- * Data types
- */
-struct cu_match_s;
-typedef struct cu_match_s cu_match_t;
-
-struct cu_match_value_s {
-  int ds_type;
-  value_t value;
-  unsigned int values_num;
-};
-typedef struct cu_match_value_s cu_match_value_t;
-
-/*
- * Prototypes
- */
-/*
- * NAME
- *  match_create_callback
- *
- * DESCRIPTION
- *  Creates a new `cu_match_t' object which will use the regular expression
- *  `regex' to match lines, see the `match_apply' method below. If the line
- *  matches, the callback passed in `callback' will be called along with the
- *  pointer `user_pointer'.
- *  The string that's passed to the callback depends on the regular expression:
- *  If the regular expression includes a sub-match, i. e. something like
- *    "value=([0-9][0-9]*)"
- *  then only the submatch (the part in the parenthesis) will be passed to the
- *  callback. If there is no submatch, then the entire string is passed to the
- *  callback.
- *  The optional `excluderegex' allows to exclude the line from the match, if
- *  the excluderegex matches.
- */
-cu_match_t *
-match_create_callback(const char *regex, const char *excluderegex,
-                      int (*callback)(const char *str, char *const *matches,
-                                      size_t matches_num, void *user_data),
-                      void *user_data);
-
-/*
- * NAME
- *  match_create_simple
- *
- * DESCRIPTION
- *  Creates a new `cu_match_t' with a default callback. The user data for that
- *  default callback will be a `cu_match_value_t' structure, with
- *  `ds_type' copied to the structure. The default callback will handle the
- *  string as containing a number (see strtoll(3) and strtod(3)) and store that
- *  number in the `value' member. How that is done depends on `ds_type':
- *
- *  UTILS_MATCH_DS_TYPE_GAUGE
- *    The function will search for a floating point number in the string and
- *    store it in value.gauge.
- *  UTILS_MATCH_DS_TYPE_COUNTER_SET
- *    The function will search for an integer in the string and store it in
- *    value.counter.
- *  UTILS_MATCH_DS_TYPE_COUNTER_ADD
- *    The function will search for an integer in the string and add it to the
- *    value in value.counter.
- *  UTILS_MATCH_DS_TYPE_COUNTER_INC
- *    The function will not search for anything in the string and increase
- *    value.counter by one.
- */
-cu_match_t *match_create_simple(const char *regex, const char *excluderegex,
-                                int ds_type);
-
-/*
- * NAME
- *  match_value_reset
- *
- * DESCRIPTION
- *   Resets the internal state, if applicable. This function must be called
- *   after each iteration for "simple" matches, usually after dispatching the
- *   metrics.
- */
-void match_value_reset(cu_match_value_t *mv);
-
-/*
- * NAME
- *  match_destroy
- *
- * DESCRIPTION
- *  Destroys the object and frees all internal resources.
- */
-void match_destroy(cu_match_t *obj);
-
-/*
- * NAME
- *  match_apply
- *
- * DESCRIPTION
- *  Tries to match the string `str' with the regular expression of `obj'. If
- *  the string matches, calls the callback in `obj' with the (sub-)match.
- *
- *  The user_data pointer passed to `match_create_callback' is NOT freed
- *  automatically. The `cu_match_value_t' structure allocated by
- *  `match_create_callback' is freed automatically.
- */
-int match_apply(cu_match_t *obj, const char *str);
-
-/*
- * NAME
- *  match_get_user_data
- *
- * DESCRIPTION
- *  Returns the pointer passed to `match_create_callback' or a pointer to the
- *  `cu_match_value_t' structure allocated by `match_create_simple'.
- */
-void *match_get_user_data(cu_match_t *obj);
-
-#endif /* UTILS_MATCH_H */
-
-/* vim: set sw=2 sts=2 ts=8 : */
diff --git a/src/daemon/utils_tail.c b/src/daemon/utils_tail.c
deleted file mode 100644 (file)
index 565a224..0000000
+++ /dev/null
@@ -1,235 +0,0 @@
-/**
- * collectd - src/utils_tail.c
- * Copyright (C) 2007-2008  C-Ware, Inc.
- * Copyright (C) 2008       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.
- *
- * Author:
- *   Luke Heberling <lukeh at c-ware.com>
- *   Florian Forster <octo at collectd.org>
- *
- * Description:
- *   Encapsulates useful code for plugins which must watch for appends to
- *   the end of a file.
- **/
-
-#include "collectd.h"
-
-#include "common.h"
-#include "utils_tail.h"
-
-struct cu_tail_s {
-  char *file;
-  FILE *fh;
-  struct stat stat;
-};
-
-static int cu_tail_reopen(cu_tail_t *obj) {
-  int seek_end = 0;
-  FILE *fh;
-  struct stat stat_buf = {0};
-  int status;
-
-  status = stat(obj->file, &stat_buf);
-  if (status != 0) {
-    char errbuf[1024];
-    ERROR("utils_tail: stat (%s) failed: %s", obj->file,
-          sstrerror(errno, errbuf, sizeof(errbuf)));
-    return (-1);
-  }
-
-  /* The file is already open.. */
-  if ((obj->fh != NULL) && (stat_buf.st_ino == obj->stat.st_ino)) {
-    /* Seek to the beginning if file was truncated */
-    if (stat_buf.st_size < obj->stat.st_size) {
-      INFO("utils_tail: File `%s' was truncated.", obj->file);
-      status = fseek(obj->fh, 0, SEEK_SET);
-      if (status != 0) {
-        char errbuf[1024];
-        ERROR("utils_tail: fseek (%s) failed: %s", obj->file,
-              sstrerror(errno, errbuf, sizeof(errbuf)));
-        fclose(obj->fh);
-        obj->fh = NULL;
-        return (-1);
-      }
-    }
-    memcpy(&obj->stat, &stat_buf, sizeof(struct stat));
-    return (1);
-  }
-
-  /* Seek to the end if we re-open the same file again or the file opened
-   * is the first at all or the first after an error */
-  if ((obj->stat.st_ino == 0) || (obj->stat.st_ino == stat_buf.st_ino))
-    seek_end = 1;
-
-  fh = fopen(obj->file, "r");
-  if (fh == NULL) {
-    char errbuf[1024];
-    ERROR("utils_tail: fopen (%s) failed: %s", obj->file,
-          sstrerror(errno, errbuf, sizeof(errbuf)));
-    return (-1);
-  }
-
-  if (seek_end != 0) {
-    status = fseek(fh, 0, SEEK_END);
-    if (status != 0) {
-      char errbuf[1024];
-      ERROR("utils_tail: fseek (%s) failed: %s", obj->file,
-            sstrerror(errno, errbuf, sizeof(errbuf)));
-      fclose(fh);
-      return (-1);
-    }
-  }
-
-  if (obj->fh != NULL)
-    fclose(obj->fh);
-  obj->fh = fh;
-  memcpy(&obj->stat, &stat_buf, sizeof(struct stat));
-
-  return (0);
-} /* int cu_tail_reopen */
-
-cu_tail_t *cu_tail_create(const char *file) {
-  cu_tail_t *obj;
-
-  obj = calloc(1, sizeof(*obj));
-  if (obj == NULL)
-    return (NULL);
-
-  obj->file = strdup(file);
-  if (obj->file == NULL) {
-    free(obj);
-    return (NULL);
-  }
-
-  obj->fh = NULL;
-
-  return (obj);
-} /* cu_tail_t *cu_tail_create */
-
-int cu_tail_destroy(cu_tail_t *obj) {
-  if (obj->fh != NULL)
-    fclose(obj->fh);
-  free(obj->file);
-  free(obj);
-
-  return (0);
-} /* int cu_tail_destroy */
-
-int cu_tail_readline(cu_tail_t *obj, char *buf, int buflen) {
-  int status;
-
-  if (buflen < 1) {
-    ERROR("utils_tail: cu_tail_readline: buflen too small: %i bytes.", buflen);
-    return (-1);
-  }
-
-  if (obj->fh == NULL) {
-    status = cu_tail_reopen(obj);
-    if (status < 0)
-      return (status);
-  }
-  assert(obj->fh != NULL);
-
-  /* Try to read from the filehandle. If that succeeds, everything appears to
-   * be fine and we can return. */
-  clearerr(obj->fh);
-  if (fgets(buf, buflen, obj->fh) != NULL) {
-    buf[buflen - 1] = 0;
-    return (0);
-  }
-
-  /* Check if we encountered an error */
-  if (ferror(obj->fh) != 0) {
-    /* Jupp, error. Force `cu_tail_reopen' to reopen the file.. */
-    fclose(obj->fh);
-    obj->fh = NULL;
-  }
-  /* else: eof -> check if the file was moved away and reopen the new file if
-   * so.. */
-
-  status = cu_tail_reopen(obj);
-  /* error -> return with error */
-  if (status < 0)
-    return (status);
-  /* file end reached and file not reopened -> nothing more to read */
-  else if (status > 0) {
-    buf[0] = 0;
-    return (0);
-  }
-
-  /* If we get here: file was re-opened and there may be more to read.. Let's
-   * try again. */
-  if (fgets(buf, buflen, obj->fh) != NULL) {
-    buf[buflen - 1] = 0;
-    return (0);
-  }
-
-  if (ferror(obj->fh) != 0) {
-    char errbuf[1024];
-    WARNING("utils_tail: fgets (%s) returned an error: %s", obj->file,
-            sstrerror(errno, errbuf, sizeof(errbuf)));
-    fclose(obj->fh);
-    obj->fh = NULL;
-    return (-1);
-  }
-
-  /* EOf, well, apparently the new file is empty.. */
-  buf[0] = 0;
-  return (0);
-} /* int cu_tail_readline */
-
-int cu_tail_read(cu_tail_t *obj, char *buf, int buflen, tailfunc_t *callback,
-                 void *data) {
-  int status;
-
-  while (42) {
-    size_t len;
-
-    status = cu_tail_readline(obj, buf, buflen);
-    if (status != 0) {
-      ERROR("utils_tail: cu_tail_read: cu_tail_readline "
-            "failed.");
-      break;
-    }
-
-    /* check for EOF */
-    if (buf[0] == 0)
-      break;
-
-    len = strlen(buf);
-    while (len > 0) {
-      if (buf[len - 1] != '\n')
-        break;
-      buf[len - 1] = '\0';
-      len--;
-    }
-
-    status = callback(data, buf, buflen);
-    if (status != 0) {
-      ERROR("utils_tail: cu_tail_read: callback returned "
-            "status %i.",
-            status);
-      break;
-    }
-  }
-
-  return status;
-} /* int cu_tail_read */
diff --git a/src/daemon/utils_tail.h b/src/daemon/utils_tail.h
deleted file mode 100644 (file)
index 73a6de2..0000000
+++ /dev/null
@@ -1,88 +0,0 @@
-/**
- * collectd - src/utils_tail.h
- * Copyright (C) 2007-2008  C-Ware, Inc.
- *
- * 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.
- *
- * Author:
- *   Luke Heberling <lukeh at c-ware.com>
- *
- * DESCRIPTION
- *   Facilitates reading information that is appended to a file, taking into
- *   account that the file may be rotated and a new file created under the
- *   same name.
- **/
-
-#ifndef UTILS_TAIL_H
-#define UTILS_TAIL_H 1
-
-struct cu_tail_s;
-typedef struct cu_tail_s cu_tail_t;
-
-typedef int tailfunc_t(void *data, char *buf, int buflen);
-
-/*
- * NAME
- *   cu_tail_create
- *
- * DESCRIPTION
- *   Allocates a new tail object..
- *
- * PARAMETERS
- *   `file'       The name of the file to be tailed.
- */
-cu_tail_t *cu_tail_create(const char *file);
-
-/*
- * cu_tail_destroy
- *
- * Takes a tail object returned by `cu_tail_create' and destroys it, freeing
- * all internal memory.
- *
- * Returns 0 when successful and non-zero otherwise.
- */
-int cu_tail_destroy(cu_tail_t *obj);
-
-/*
- * cu_tail_readline
- *
- * Reads from the file until `buflen' characters are read, a newline
- * character is read, or an eof condition is encountered. `buf' is
- * always null-terminated on successful return and isn't touched when non-zero
- * is returned.
- *
- * You can check if the EOF condition is reached by looking at the buffer: If
- * the length of the string stored in the buffer is zero, EOF occurred.
- * Otherwise at least the newline character will be in the buffer.
- *
- * Returns 0 when successful and non-zero otherwise.
- */
-int cu_tail_readline(cu_tail_t *obj, char *buf, int buflen);
-
-/*
- * cu_tail_readline
- *
- * Reads from the file until eof condition or an error is encountered.
- *
- * Returns 0 when successful and non-zero otherwise.
- */
-int cu_tail_read(cu_tail_t *obj, char *buf, int buflen, tailfunc_t *callback,
-                 void *data);
-
-#endif /* UTILS_TAIL_H */
diff --git a/src/daemon/utils_tail_match.c b/src/daemon/utils_tail_match.c
deleted file mode 100644 (file)
index 6b878ed..0000000
+++ /dev/null
@@ -1,251 +0,0 @@
-/*
- * collectd - src/utils_tail_match.c
- * Copyright (C) 2007-2008  C-Ware, Inc.
- * Copyright (C) 2008       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.
- *
- * Author:
- *   Luke Heberling <lukeh at c-ware.com>
- *   Florian Forster <octo at collectd.org>
- *
- * Description:
- *   Encapsulates useful code to plugins which must parse a log file.
- */
-
-#include "collectd.h"
-
-#include "common.h"
-#include "plugin.h"
-#include "utils_match.h"
-#include "utils_tail.h"
-#include "utils_tail_match.h"
-
-struct cu_tail_match_simple_s {
-  char plugin[DATA_MAX_NAME_LEN];
-  char plugin_instance[DATA_MAX_NAME_LEN];
-  char type[DATA_MAX_NAME_LEN];
-  char type_instance[DATA_MAX_NAME_LEN];
-  cdtime_t interval;
-};
-typedef struct cu_tail_match_simple_s cu_tail_match_simple_t;
-
-struct cu_tail_match_match_s {
-  cu_match_t *match;
-  void *user_data;
-  int (*submit)(cu_match_t *match, void *user_data);
-  void (*free)(void *user_data);
-};
-typedef struct cu_tail_match_match_s cu_tail_match_match_t;
-
-struct cu_tail_match_s {
-  int flags;
-  cu_tail_t *tail;
-
-  cdtime_t interval;
-  cu_tail_match_match_t *matches;
-  size_t matches_num;
-};
-
-/*
- * Private functions
- */
-static int simple_submit_match(cu_match_t *match, void *user_data) {
-  cu_tail_match_simple_t *data = (cu_tail_match_simple_t *)user_data;
-  cu_match_value_t *match_value;
-  value_list_t vl = VALUE_LIST_INIT;
-  value_t values[1];
-
-  match_value = (cu_match_value_t *)match_get_user_data(match);
-  if (match_value == NULL)
-    return (-1);
-
-  if ((match_value->ds_type & UTILS_MATCH_DS_TYPE_GAUGE) &&
-      (match_value->values_num == 0))
-    values[0].gauge = NAN;
-  else
-    values[0] = match_value->value;
-
-  vl.values = values;
-  vl.values_len = 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));
-  sstrncpy(vl.type, data->type, sizeof(vl.type));
-  sstrncpy(vl.type_instance, data->type_instance, sizeof(vl.type_instance));
-
-  vl.interval = data->interval;
-  plugin_dispatch_values(&vl);
-
-  if (match_value->ds_type & UTILS_MATCH_DS_TYPE_GAUGE) {
-    match_value->value.gauge = NAN;
-    match_value->values_num = 0;
-  }
-
-  return (0);
-} /* int simple_submit_match */
-
-static int tail_callback(void *data, char *buf,
-                         int __attribute__((unused)) buflen) {
-  cu_tail_match_t *obj = (cu_tail_match_t *)data;
-
-  for (size_t i = 0; i < obj->matches_num; i++)
-    match_apply(obj->matches[i].match, buf);
-
-  return (0);
-} /* int tail_callback */
-
-/*
- * Public functions
- */
-cu_tail_match_t *tail_match_create(const char *filename) {
-  cu_tail_match_t *obj;
-
-  obj = calloc(1, sizeof(*obj));
-  if (obj == NULL)
-    return (NULL);
-
-  obj->tail = cu_tail_create(filename);
-  if (obj->tail == NULL) {
-    sfree(obj);
-    return (NULL);
-  }
-
-  return (obj);
-} /* cu_tail_match_t *tail_match_create */
-
-void tail_match_destroy(cu_tail_match_t *obj) {
-  if (obj == NULL)
-    return;
-
-  if (obj->tail != NULL) {
-    cu_tail_destroy(obj->tail);
-    obj->tail = NULL;
-  }
-
-  for (size_t i = 0; i < obj->matches_num; i++) {
-    cu_tail_match_match_t *match = obj->matches + i;
-    if (match->match != NULL) {
-      match_destroy(match->match);
-      match->match = NULL;
-    }
-
-    if ((match->user_data != NULL) && (match->free != NULL))
-      (*match->free)(match->user_data);
-    match->user_data = NULL;
-  }
-
-  sfree(obj->matches);
-  sfree(obj);
-} /* void tail_match_destroy */
-
-int tail_match_add_match(cu_tail_match_t *obj, cu_match_t *match,
-                         int (*submit_match)(cu_match_t *match,
-                                             void *user_data),
-                         void *user_data,
-                         void (*free_user_data)(void *user_data)) {
-  cu_tail_match_match_t *temp;
-
-  temp = realloc(obj->matches,
-                 sizeof(cu_tail_match_match_t) * (obj->matches_num + 1));
-  if (temp == NULL)
-    return (-1);
-
-  obj->matches = temp;
-  obj->matches_num++;
-
-  DEBUG("tail_match_add_match interval %lf",
-        CDTIME_T_TO_DOUBLE(((cu_tail_match_simple_t *)user_data)->interval));
-  temp = obj->matches + (obj->matches_num - 1);
-
-  temp->match = match;
-  temp->user_data = user_data;
-  temp->submit = submit_match;
-  temp->free = free_user_data;
-
-  return (0);
-} /* int tail_match_add_match */
-
-int tail_match_add_match_simple(cu_tail_match_t *obj, const char *regex,
-                                const char *excluderegex, int ds_type,
-                                const char *plugin, const char *plugin_instance,
-                                const char *type, const char *type_instance,
-                                const cdtime_t interval) {
-  cu_match_t *match;
-  cu_tail_match_simple_t *user_data;
-  int status;
-
-  match = match_create_simple(regex, excluderegex, ds_type);
-  if (match == NULL)
-    return (-1);
-
-  user_data = calloc(1, sizeof(*user_data));
-  if (user_data == NULL) {
-    match_destroy(match);
-    return (-1);
-  }
-
-  sstrncpy(user_data->plugin, plugin, sizeof(user_data->plugin));
-  if (plugin_instance != NULL)
-    sstrncpy(user_data->plugin_instance, plugin_instance,
-             sizeof(user_data->plugin_instance));
-
-  sstrncpy(user_data->type, type, sizeof(user_data->type));
-  if (type_instance != NULL)
-    sstrncpy(user_data->type_instance, type_instance,
-             sizeof(user_data->type_instance));
-
-  user_data->interval = interval;
-
-  status =
-      tail_match_add_match(obj, match, simple_submit_match, user_data, free);
-
-  if (status != 0) {
-    match_destroy(match);
-    sfree(user_data);
-  }
-
-  return (status);
-} /* int tail_match_add_match_simple */
-
-int tail_match_read(cu_tail_match_t *obj) {
-  char buffer[4096];
-  int status;
-
-  status = cu_tail_read(obj->tail, buffer, sizeof(buffer), tail_callback,
-                        (void *)obj);
-  if (status != 0) {
-    ERROR("tail_match: cu_tail_read failed.");
-    return (status);
-  }
-
-  for (size_t i = 0; i < obj->matches_num; i++) {
-    cu_tail_match_match_t *lt_match = obj->matches + i;
-
-    if (lt_match->submit == NULL)
-      continue;
-
-    (*lt_match->submit)(lt_match->match, lt_match->user_data);
-  }
-
-  return (0);
-} /* int tail_match_read */
-
-/* vim: set sw=2 sts=2 ts=8 : */
diff --git a/src/daemon/utils_tail_match.h b/src/daemon/utils_tail_match.h
deleted file mode 100644 (file)
index 6c6019a..0000000
+++ /dev/null
@@ -1,138 +0,0 @@
-/*
- * collectd - src/utils_tail_match.h
- * Copyright (C) 2007-2008  C-Ware, Inc.
- * Copyright (C) 2008       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:
- *   Luke Heberling <lukeh at c-ware.com>
- *   Florian Forster <octo at collectd.org>
- *
- * Description:
- *   `tail_match' uses `utils_tail' and `utils_match' to tail a file and try to
- *   match it using several regular expressions. Matches are then passed to
- *   user-provided callback functions or default handlers. This should keep all
- *   of the parsing logic out of the actual plugin, which only operate with
- *   regular expressions.
- */
-
-#include "utils_match.h"
-
-struct cu_tail_match_s;
-typedef struct cu_tail_match_s cu_tail_match_t;
-
-/*
- * NAME
- *   tail_match_create
- *
- * DESCRIPTION
- *   Allocates, initializes and returns a new `cu_tail_match_t' object.
- *
- * PARAMETERS
- *   `filename'  The name to read data from.
- *
- * RETURN VALUE
- *   Returns NULL upon failure, non-NULL otherwise.
- */
-cu_tail_match_t *tail_match_create(const char *filename);
-
-/*
- * NAME
- *   tail_match_destroy
- *
- * DESCRIPTION
- *   Releases resources used by the `cu_tail_match_t' object.
- *
- * PARAMETERS
- *   The object to destroy.
- */
-void tail_match_destroy(cu_tail_match_t *obj);
-
-/*
- * NAME
- *   tail_match_add_match
- *
- * DESCRIPTION
- *   Adds a match, in form of a `cu_match_t' object, to the object.
- *   After data has been read from the logfile (using utils_tail) the callback
- *   function `submit_match' is called with the match object and the user
- *   supplied data.
- *   Please note that his function is called regardless whether this match
- *   matched any lines recently or not.
- *   When `tail_match_destroy' is called the `user_data' pointer is freed using
- *   the `free_user_data' callback - if it is not NULL.
- *   When using this interface the `tail_match' module doesn't dispatch any
- * values
- *   itself - all that has to happen in either the match-callbacks or the
- *   submit_match callback.
- *
- * RETURN VALUE
- *   Zero upon success, non-zero otherwise.
- */
-int tail_match_add_match(cu_tail_match_t *obj, cu_match_t *match,
-                         int (*submit_match)(cu_match_t *match,
-                                             void *user_data),
-                         void *user_data,
-                         void (*free_user_data)(void *user_data));
-
-/*
- * NAME
- *  tail_match_add_match_simple
- *
- * DESCRIPTION
- *  A simplified version of `tail_match_add_match'. The regular expressen
- * `regex'
- *  must match a number, which is then dispatched according to `ds_type'. See
- *  the `match_create_simple' function in utils_match.h for a description how
- *  this flag effects calculation of a new value.
- *  The values gathered are dispatched by the tail_match module in this case.
- * The
- *  passed `plugin', `plugin_instance', `type', and `type_instance' are
- *  directly used when submitting these values.
- *  With excluderegex it is possible to exlude lines from the match.
- *
- * RETURN VALUE
- *   Zero upon success, non-zero otherwise.
- */
-int tail_match_add_match_simple(cu_tail_match_t *obj, const char *regex,
-                                const char *excluderegex, int ds_type,
-                                const char *plugin, const char *plugin_instance,
-                                const char *type, const char *type_instance,
-                                const cdtime_t interval);
-
-/*
- * NAME
- *   tail_match_read
- *
- * DESCRIPTION
- *   This function should be called periodically by plugins. It reads new lines
- *   from the logfile using `utils_tail' and tries to match them using all
- *   added `utils_match' objects.
- *   After all lines have been read and processed, the submit_match callback is
- *   called or, in case of tail_match_add_match_simple, the data is dispatched
- * to
- *   the daemon directly.
- *
- * RETURN VALUE
- *   Zero on success, nonzero on failure.
-*/
-int tail_match_read(cu_tail_match_t *obj);
-
-/* vim: set sw=2 sts=2 ts=8 : */
index 6bdf124..9ba2b5a 100644 (file)
@@ -50,10 +50,10 @@ cdtime_t cdtime(void) /* {{{ */
     char errbuf[1024];
     ERROR("cdtime: clock_gettime failed: %s",
           sstrerror(errno, errbuf, sizeof(errbuf)));
-    return (0);
+    return 0;
   }
 
-  return (TIMESPEC_TO_CDTIME_T(&ts));
+  return TIMESPEC_TO_CDTIME_T(&ts);
 } /* }}} cdtime_t cdtime */
 #else /* !HAVE_CLOCK_GETTIME */
 /* Work around for Mac OS X which doesn't have clock_gettime(2). *sigh* */
@@ -67,14 +67,58 @@ cdtime_t cdtime(void) /* {{{ */
     char errbuf[1024];
     ERROR("cdtime: gettimeofday failed: %s",
           sstrerror(errno, errbuf, sizeof(errbuf)));
-    return (0);
+    return 0;
   }
 
-  return (TIMEVAL_TO_CDTIME_T(&tv));
+  return TIMEVAL_TO_CDTIME_T(&tv);
 } /* }}} cdtime_t cdtime */
 #endif
 #endif
 
+/**********************************************************************
+ Time retrieval functions
+***********************************************************************/
+
+static int get_utc_time(cdtime_t t, struct tm *t_tm, long *nsec) /* {{{ */
+{
+  struct timespec t_spec = CDTIME_T_TO_TIMESPEC(t);
+  NORMALIZE_TIMESPEC(t_spec);
+
+  if (gmtime_r(&t_spec.tv_sec, t_tm) == NULL) {
+    char errbuf[1024];
+    int status = errno;
+    ERROR("get_utc_time: gmtime_r failed: %s",
+          sstrerror(status, errbuf, sizeof(errbuf)));
+    return status;
+  }
+
+  *nsec = t_spec.tv_nsec;
+  return 0;
+} /* }}} int get_utc_time */
+
+static int get_local_time(cdtime_t t, struct tm *t_tm, long *nsec) /* {{{ */
+{
+  struct timespec t_spec = CDTIME_T_TO_TIMESPEC(t);
+  NORMALIZE_TIMESPEC(t_spec);
+
+  if (localtime_r(&t_spec.tv_sec, t_tm) == NULL) {
+    char errbuf[1024];
+    int status = errno;
+    ERROR("get_local_time: localtime_r failed: %s",
+          sstrerror(status, errbuf, sizeof(errbuf)));
+    return status;
+  }
+
+  *nsec = t_spec.tv_nsec;
+  return 0;
+} /* }}} int get_local_time */
+
+/**********************************************************************
+ Formatting functions
+***********************************************************************/
+
+static const char zulu_zone[] = "Z";
+
 /* format_zone reads time zone information from "extern long timezone", exported
  * by <time.h>, and formats it according to RFC 3339. This differs from
  * strftime()'s "%z" format by including a colon between hour and minute. */
@@ -107,61 +151,94 @@ static int format_zone(char *buffer, size_t buffer_size,
   return 0;
 } /* }}} int format_zone */
 
-static int format_rfc3339(char *buffer, size_t buffer_size, cdtime_t t,
-                          _Bool print_nano) /* {{{ */
+int format_rfc3339(char *buffer, size_t buffer_size, struct tm const *t_tm,
+                   long nsec, _Bool print_nano, char const *zone) /* {{{ */
+{
+  int len;
+  char *pos = buffer;
+  size_t size_left = buffer_size;
+
+  if ((len = strftime(pos, size_left, "%Y-%m-%dT%H:%M:%S", t_tm)) == 0)
+    return ENOMEM;
+  pos += len;
+  size_left -= len;
+
+  if (print_nano) {
+    if ((len = ssnprintf(pos, size_left, ".%09ld", nsec)) == 0)
+      return ENOMEM;
+    pos += len;
+    size_left -= len;
+  }
+
+  sstrncpy(pos, zone, size_left);
+  return 0;
+} /* }}} int format_rfc3339 */
+
+int format_rfc3339_utc(char *buffer, size_t buffer_size, cdtime_t t,
+                       _Bool print_nano) /* {{{ */
 {
-  struct timespec t_spec;
   struct tm t_tm;
-  char base[20]; /* 2006-01-02T15:04:05 */
-  char nano[11]; /* .999999999 */
-  char zone[7];  /* +00:00 */
-  char *fields[] = {base, nano, zone};
-  size_t len;
+  long nsec = 0;
   int status;
 
-  CDTIME_T_TO_TIMESPEC(t, &t_spec);
-  NORMALIZE_TIMESPEC(t_spec);
+  if ((status = get_utc_time(t, &t_tm, &nsec)) != 0)
+    return status; /* The error should have already be reported. */
 
-  if (localtime_r(&t_spec.tv_sec, &t_tm) == NULL) {
-    char errbuf[1024];
-    status = errno;
-    ERROR("format_rfc3339: localtime_r failed: %s",
-          sstrerror(status, errbuf, sizeof(errbuf)));
-    return (status);
-  }
+  return format_rfc3339(buffer, buffer_size, &t_tm, nsec, print_nano,
+                        zulu_zone);
+} /* }}} int format_rfc3339_utc */
 
-  len = strftime(base, sizeof(base), "%Y-%m-%dT%H:%M:%S", &t_tm);
-  if (len == 0)
-    return ENOMEM;
+int format_rfc3339_local(char *buffer, size_t buffer_size, cdtime_t t,
+                         _Bool print_nano) /* {{{ */
+{
+  struct tm t_tm;
+  long nsec = 0;
+  int status;
+  char zone[7]; /* +00:00 */
 
-  if (print_nano)
-    ssnprintf(nano, sizeof(nano), ".%09ld", (long)t_spec.tv_nsec);
-  else
-    sstrncpy(nano, "", sizeof(nano));
+  if ((status = get_local_time(t, &t_tm, &nsec)) != 0)
+    return status; /* The error should have already be reported. */
 
-  status = format_zone(zone, sizeof(zone), &t_tm);
-  if (status != 0)
+  if ((status = format_zone(zone, sizeof(zone), &t_tm)) != 0)
     return status;
 
-  if (strjoin(buffer, buffer_size, fields, STATIC_ARRAY_SIZE(fields), "") < 0)
-    return ENOMEM;
-  return 0;
-} /* }}} int format_rfc3339 */
+  return format_rfc3339(buffer, buffer_size, &t_tm, nsec, print_nano, zone);
+} /* }}} int format_rfc3339_local */
+
+/**********************************************************************
+ Public functions
+***********************************************************************/
 
 int rfc3339(char *buffer, size_t buffer_size, cdtime_t t) /* {{{ */
 {
   if (buffer_size < RFC3339_SIZE)
     return ENOMEM;
 
-  return format_rfc3339(buffer, buffer_size, t, 0);
-} /* }}} size_t cdtime_to_rfc3339 */
+  return format_rfc3339_utc(buffer, buffer_size, t, 0);
+} /* }}} int rfc3339 */
 
 int rfc3339nano(char *buffer, size_t buffer_size, cdtime_t t) /* {{{ */
 {
   if (buffer_size < RFC3339NANO_SIZE)
     return ENOMEM;
 
-  return format_rfc3339(buffer, buffer_size, t, 1);
-} /* }}} size_t cdtime_to_rfc3339nano */
+  return format_rfc3339_utc(buffer, buffer_size, t, 1);
+} /* }}} int rfc3339nano */
+
+int rfc3339_local(char *buffer, size_t buffer_size, cdtime_t t) /* {{{ */
+{
+  if (buffer_size < RFC3339_SIZE)
+    return ENOMEM;
+
+  return format_rfc3339_local(buffer, buffer_size, t, 0);
+} /* }}} int rfc3339 */
+
+int rfc3339nano_local(char *buffer, size_t buffer_size, cdtime_t t) /* {{{ */
+{
+  if (buffer_size < RFC3339NANO_SIZE)
+    return ENOMEM;
+
+  return format_rfc3339_local(buffer, buffer_size, t, 1);
+} /* }}} int rfc3339nano */
 
 /* vim: set sw=2 sts=2 et fdm=marker : */
index f5f8a91..db510af 100644 (file)
@@ -48,61 +48,86 @@ extern cdtime_t cdtime_mock;
 /* typedef uint64_t cdtime_t; */
 
 /* 2^30 = 1073741824 */
-#define TIME_T_TO_CDTIME_T(t) (((cdtime_t)(t)) << 30)
+#define TIME_T_TO_CDTIME_T_STATIC(t) (((cdtime_t)(t)) << 30)
+#define TIME_T_TO_CDTIME_T(t)                                                  \
+  (cdtime_t) { TIME_T_TO_CDTIME_T_STATIC(t) }
 
 #define MS_TO_CDTIME_T(ms)                                                     \
-  (((((cdtime_t)(ms)) / 1000) << 30) |                                         \
-   ((((((cdtime_t)(ms)) % 1000) << 30) + 500) / 1000))
+  (cdtime_t) {                                                                 \
+    ((((cdtime_t)(ms)) / 1000) << 30) |                                        \
+        ((((((cdtime_t)(ms)) % 1000) << 30) + 500) / 1000)                     \
+  }
 #define US_TO_CDTIME_T(us)                                                     \
-  (((((cdtime_t)(us)) / 1000000) << 30) |                                      \
-   ((((((cdtime_t)(us)) % 1000000) << 30) + 500000) / 1000000))
+  (cdtime_t) {                                                                 \
+    ((((cdtime_t)(us)) / 1000000) << 30) |                                     \
+        ((((((cdtime_t)(us)) % 1000000) << 30) + 500000) / 1000000)            \
+  }
 #define NS_TO_CDTIME_T(ns)                                                     \
-  (((((cdtime_t)(ns)) / 1000000000) << 30) |                                   \
-   ((((((cdtime_t)(ns)) % 1000000000) << 30) + 500000000) / 1000000000))
+  (cdtime_t) {                                                                 \
+    ((((cdtime_t)(ns)) / 1000000000) << 30) |                                  \
+        ((((((cdtime_t)(ns)) % 1000000000) << 30) + 500000000) / 1000000000)   \
+  }
 
-#define CDTIME_T_TO_TIME_T(t) ((time_t)(((t) + (1 << 29)) >> 30))
+#define CDTIME_T_TO_TIME_T(t)                                                  \
+  (time_t) { (time_t)(((t) + (1 << 29)) >> 30) }
 #define CDTIME_T_TO_MS(t)                                                      \
-  ((uint64_t)((((t) >> 30) * 1000) +                                           \
-              ((((t)&0x3fffffff) * 1000 + (1 << 29)) >> 30)))
+  (uint64_t) {                                                                 \
+    (uint64_t)((((t) >> 30) * 1000) +                                          \
+               ((((t)&0x3fffffff) * 1000 + (1 << 29)) >> 30))                  \
+  }
 #define CDTIME_T_TO_US(t)                                                      \
-  ((uint64_t)((((t) >> 30) * 1000000) +                                        \
-              ((((t)&0x3fffffff) * 1000000 + (1 << 29)) >> 30)))
+  (uint64_t) {                                                                 \
+    (uint64_t)((((t) >> 30) * 1000000) +                                       \
+               ((((t)&0x3fffffff) * 1000000 + (1 << 29)) >> 30))               \
+  }
 #define CDTIME_T_TO_NS(t)                                                      \
-  ((uint64_t)((((t) >> 30) * 1000000000) +                                     \
-              ((((t)&0x3fffffff) * 1000000000 + (1 << 29)) >> 30)))
-
-#define CDTIME_T_TO_DOUBLE(t) (((double)(t)) / 1073741824.0)
-#define DOUBLE_TO_CDTIME_T(d) ((cdtime_t)((d)*1073741824.0))
-
-#define CDTIME_T_TO_TIMEVAL(cdt, tvp)                                          \
-  do {                                                                         \
-    (tvp)->tv_sec = (time_t)((cdt) >> 30);                                     \
-    (tvp)->tv_usec =                                                           \
-        (suseconds_t)((((cdt)&0x3fffffff) * 1000000 + (1 << 29)) >> 30);       \
-  } while (0)
+  (uint64_t) {                                                                 \
+    (uint64_t)((((t) >> 30) * 1000000000) +                                    \
+               ((((t)&0x3fffffff) * 1000000000 + (1 << 29)) >> 30))            \
+  }
+
+#define CDTIME_T_TO_DOUBLE(t)                                                  \
+  (double) { ((double)(t)) / 1073741824.0 }
+#define DOUBLE_TO_CDTIME_T_STATIC(d) ((cdtime_t)((d)*1073741824.0))
+#define DOUBLE_TO_CDTIME_T(d)                                                  \
+  (cdtime_t) { DOUBLE_TO_CDTIME_T_STATIC(d) }
+
+#define CDTIME_T_TO_TIMEVAL(t)                                                 \
+  (struct timeval) {                                                           \
+    .tv_sec = (time_t)((t) >> 30),                                             \
+    .tv_usec = (suseconds_t)((((t)&0x3fffffff) * 1000000 + (1 << 29)) >> 30),  \
+  }
 #define TIMEVAL_TO_CDTIME_T(tv)                                                \
   US_TO_CDTIME_T(1000000 * (tv)->tv_sec + (tv)->tv_usec)
 
-#define CDTIME_T_TO_TIMESPEC(cdt, tsp)                                         \
-  do {                                                                         \
-    (tsp)->tv_sec = (time_t)((cdt) >> 30);                                     \
-    (tsp)->tv_nsec =                                                           \
-        (long)((((cdt)&0x3fffffff) * 1000000000 + (1 << 29)) >> 30);           \
-  } while (0)
+#define CDTIME_T_TO_TIMESPEC(t)                                                \
+  (struct timespec) {                                                          \
+    .tv_sec = (time_t)((t) >> 30),                                             \
+    .tv_nsec = (long)((((t)&0x3fffffff) * 1000000000 + (1 << 29)) >> 30),      \
+  }
 #define TIMESPEC_TO_CDTIME_T(ts)                                               \
   NS_TO_CDTIME_T(1000000000ULL * (ts)->tv_sec + (ts)->tv_nsec)
 
 cdtime_t cdtime(void);
 
-#define RFC3339_SIZE 26
-#define RFC3339NANO_SIZE 36
+#define RFC3339_SIZE 26     /* 2006-01-02T15:04:05+00:00 */
+#define RFC3339NANO_SIZE 36 /* 2006-01-02T15:04:05.999999999+00:00 */
 
-/* rfc3339 formats a cdtime_t time in RFC 3339 format with second precision. */
+/* rfc3339 formats a cdtime_t time as UTC in RFC 3339 zulu format with second
+ * precision, e.g., "2006-01-02T15:04:05Z". */
 int rfc3339(char *buffer, size_t buffer_size, cdtime_t t);
 
-/* rfc3339nano formats a cdtime_t time in RFC 3339 format with nanosecond
- * precision. */
+/* rfc3339nano formats a cdtime_t as UTC time in RFC 3339 zulu format with
+ * nanosecond precision, e.g., "2006-01-02T15:04:05.999999999Z". */
 int rfc3339nano(char *buffer, size_t buffer_size, cdtime_t t);
 
+/* rfc3339 formats a cdtime_t time as local in RFC 3339 format with second
+ * precision, e.g., "2006-01-02T15:04:05+00:00". */
+int rfc3339_local(char *buffer, size_t buffer_size, cdtime_t t);
+
+/* rfc3339nano formats a cdtime_t time as local in RFC 3339 format with
+ * nanosecond precision, e.g., "2006-01-02T15:04:05.999999999+00:00". */
+int rfc3339nano_local(char *buffer, size_t buffer_size, cdtime_t t);
+
 #endif /* UTILS_TIME_H */
 /* vim: set sw=2 sts=2 et : */
index 6b8e63d..c1c60a2 100644 (file)
@@ -117,9 +117,6 @@ DEF_TEST(conversion) {
   };
 
   for (size_t i = 0; i < (sizeof(cases) / sizeof(cases[0])); i++) {
-    struct timeval tv;
-    struct timespec ts;
-
     // cdtime -> s
     EXPECT_EQ_UINT64(cases[i].tt, CDTIME_T_TO_TIME_T(cases[i].t));
 
@@ -127,12 +124,12 @@ DEF_TEST(conversion) {
     EXPECT_EQ_UINT64(cases[i].ms, CDTIME_T_TO_MS(cases[i].t));
 
     // cdtime -> us
-    CDTIME_T_TO_TIMEVAL(cases[i].t, &tv);
+    struct timeval tv = CDTIME_T_TO_TIMEVAL(cases[i].t);
     EXPECT_EQ_UINT64(cases[i].tv.tv_sec, tv.tv_sec);
     EXPECT_EQ_UINT64(cases[i].tv.tv_usec, tv.tv_usec);
 
     // cdtime -> ns
-    CDTIME_T_TO_TIMESPEC(cases[i].t, &ts);
+    struct timespec ts = CDTIME_T_TO_TIMESPEC(cases[i].t);
     EXPECT_EQ_UINT64(cases[i].ts.tv_sec, ts.tv_sec);
     EXPECT_EQ_UINT64(cases[i].ts.tv_nsec, ts.tv_nsec);
 
index d013e14..7cab1d5 100644 (file)
--- a/src/dbi.c
+++ b/src/dbi.c
@@ -352,23 +352,20 @@ static int cdbi_config_add_database(oconfig_item_t *ci) /* {{{ */
       ERROR("dbi plugin: realloc failed");
       status = -1;
     } else {
-      char *name = NULL;
-
       databases = temp;
       databases[databases_num] = db;
       databases_num++;
 
-      name = ssnprintf_alloc("dbi:%s", db->name);
-
-      user_data_t ud = {.data = db};
-
+      char *name = ssnprintf_alloc("dbi:%s", db->name);
       plugin_register_complex_read(
           /* group = */ NULL,
           /* name = */ name ? name : db->name,
           /* callback = */ cdbi_read_database,
           /* interval = */ (db->interval > 0) ? db->interval : 0,
-          /* user_data = */ &ud);
-      free(name);
+          &(user_data_t){
+              .data = db,
+          });
+      sfree(name);
     }
   }
 
@@ -409,13 +406,13 @@ static int cdbi_init(void) /* {{{ */
 
   if (queries_num == 0) {
     ERROR("dbi plugin: No <Query> blocks have been found. Without them, "
-          "this plugin can't do anything useful, so we will returns an error.");
+          "this plugin can't do anything useful, so we will return an error.");
     return (-1);
   }
 
   if (databases_num == 0) {
     ERROR("dbi plugin: No <Database> blocks have been found. Without them, "
-          "this plugin can't do anything useful, so we will returns an error.");
+          "this plugin can't do anything useful, so we will return an error.");
     return (-1);
   }
 
index 1581e0c..5416605 100644 (file)
--- a/src/df.c
+++ b/src/df.c
@@ -132,14 +132,10 @@ __attribute__((nonnull(2))) static void df_submit_one(char *plugin_instance,
                                                       const char *type,
                                                       const char *type_instance,
                                                       gauge_t value) {
-  value_t values[1];
   value_list_t vl = VALUE_LIST_INIT;
 
-  values[0].gauge = value;
-
-  vl.values = values;
+  vl.values = &(value_t){.gauge = value};
   vl.values_len = 1;
-  sstrncpy(vl.host, hostname_g, sizeof(vl.host));
   sstrncpy(vl.plugin, "df", sizeof(vl.plugin));
   if (plugin_instance != NULL)
     sstrncpy(vl.plugin_instance, plugin_instance, sizeof(vl.plugin_instance));
@@ -193,7 +189,8 @@ static int df_read(void) {
       }
 
       /* Duplicate found: leave non-NULL dup_ptr. */
-      if (by_device &&
+      if (by_device && (mnt_ptr->spec_device != NULL) &&
+          (dup_ptr->spec_device != NULL) &&
           (strcmp(mnt_ptr->spec_device, dup_ptr->spec_device) == 0))
         break;
       else if (!by_device && (strcmp(mnt_ptr->dir, dup_ptr->dir) == 0))
index cbb1e38..e01e150 100644 (file)
@@ -127,7 +127,7 @@ static int numdisk = 0;
 /* #endif HAVE_LIBKSTAT */
 
 #elif defined(HAVE_LIBSTATGRAB)
-/* #endif HAVE_LIBKSTATGRAB */
+/* #endif HAVE_LIBSTATGRAB */
 
 #elif HAVE_PERFSTAT
 static perfstat_disk_t *stat_disk;
@@ -270,15 +270,13 @@ static int disk_shutdown(void) {
 
 static void disk_submit(const char *plugin_instance, const char *type,
                         derive_t read, derive_t write) {
-  value_t values[2];
   value_list_t vl = VALUE_LIST_INIT;
-
-  values[0].derive = read;
-  values[1].derive = write;
+  value_t values[] = {
+      {.derive = read}, {.derive = write},
+  };
 
   vl.values = values;
-  vl.values_len = 2;
-  sstrncpy(vl.host, hostname_g, sizeof(vl.host));
+  vl.values_len = STATIC_ARRAY_SIZE(values);
   sstrncpy(vl.plugin, "disk", sizeof(vl.plugin));
   sstrncpy(vl.plugin_instance, plugin_instance, sizeof(vl.plugin_instance));
   sstrncpy(vl.type, type, sizeof(vl.type));
@@ -289,15 +287,13 @@ static void disk_submit(const char *plugin_instance, const char *type,
 #if KERNEL_FREEBSD || KERNEL_LINUX
 static void submit_io_time(char const *plugin_instance, derive_t io_time,
                            derive_t weighted_time) {
-  value_t values[2];
   value_list_t vl = VALUE_LIST_INIT;
-
-  values[0].derive = io_time;
-  values[1].derive = weighted_time;
+  value_t values[] = {
+      {.derive = io_time}, {.derive = weighted_time},
+  };
 
   vl.values = values;
-  vl.values_len = 2;
-  sstrncpy(vl.host, hostname_g, sizeof(vl.host));
+  vl.values_len = STATIC_ARRAY_SIZE(values);
   sstrncpy(vl.plugin, "disk", sizeof(vl.plugin));
   sstrncpy(vl.plugin_instance, plugin_instance, sizeof(vl.plugin_instance));
   sstrncpy(vl.type, "disk_io_time", sizeof(vl.type));
@@ -308,14 +304,10 @@ static void submit_io_time(char const *plugin_instance, derive_t io_time,
 
 #if KERNEL_LINUX
 static void submit_in_progress(char const *disk_name, gauge_t in_progress) {
-  value_t v;
   value_list_t vl = VALUE_LIST_INIT;
 
-  v.gauge = in_progress;
-
-  vl.values = &v;
+  vl.values = &(value_t){.gauge = in_progress};
   vl.values_len = 1;
-  sstrncpy(vl.host, hostname_g, sizeof(vl.host));
   sstrncpy(vl.plugin, "disk", sizeof(vl.plugin));
   sstrncpy(vl.plugin_instance, disk_name, sizeof(vl.plugin_instance));
   sstrncpy(vl.type, "pending_operations", sizeof(vl.type));
index 1541c31..e3208a6 100644 (file)
--- a/src/dns.c
+++ b/src/dns.c
@@ -242,27 +242,15 @@ static int dns_run_pcap_loop(void) {
 
 static int dns_sleep_one_interval(void) /* {{{ */
 {
-  cdtime_t interval;
-  struct timespec ts = {0, 0};
-  int status = 0;
-
-  interval = plugin_get_interval();
-  CDTIME_T_TO_TIMESPEC(interval, &ts);
-
-  while (42) {
-    struct timespec rem = {0, 0};
-
-    status = nanosleep(&ts, &rem);
-    if (status == 0)
-      break;
-    else if ((errno == EINTR) || (errno == EAGAIN)) {
-      ts = rem;
+  struct timespec ts = CDTIME_T_TO_TIMESPEC(plugin_get_interval());
+  while (nanosleep(&ts, &ts) != 0) {
+    if ((errno == EINTR) || (errno == EAGAIN))
       continue;
-    } else
-      break;
+
+    return (errno);
   }
 
-  return (status);
+  return (0);
 } /* }}} int dns_sleep_one_interval */
 
 static void *dns_child_loop(__attribute__((unused)) void *dummy) /* {{{ */
@@ -296,8 +284,8 @@ static int dns_init(void) {
   if (listen_thread_init != 0)
     return (-1);
 
-  status =
-      plugin_thread_create(&listen_thread, NULL, dns_child_loop, (void *)0);
+  status = plugin_thread_create(&listen_thread, NULL, dns_child_loop, (void *)0,
+                                "dns listen");
   if (status != 0) {
     char errbuf[1024];
     ERROR("dns plugin: pthread_create failed: %s",
@@ -325,14 +313,10 @@ static int dns_init(void) {
 
 static void submit_derive(const char *type, const char *type_instance,
                           derive_t value) {
-  value_t values[1];
   value_list_t vl = VALUE_LIST_INIT;
 
-  values[0].derive = value;
-
-  vl.values = values;
+  vl.values = &(value_t){.derive = value};
   vl.values_len = 1;
-  sstrncpy(vl.host, hostname_g, sizeof(vl.host));
   sstrncpy(vl.plugin, "dns", sizeof(vl.plugin));
   sstrncpy(vl.type, type, sizeof(vl.type));
   sstrncpy(vl.type_instance, type_instance, sizeof(vl.type_instance));
@@ -341,15 +325,13 @@ static void submit_derive(const char *type, const char *type_instance,
 } /* void submit_derive */
 
 static void submit_octets(derive_t queries, derive_t responses) {
-  value_t values[2];
+  value_t values[] = {
+      {.derive = queries}, {.derive = responses},
+  };
   value_list_t vl = VALUE_LIST_INIT;
 
-  values[0].derive = queries;
-  values[1].derive = responses;
-
   vl.values = values;
-  vl.values_len = 2;
-  sstrncpy(vl.host, hostname_g, sizeof(vl.host));
+  vl.values_len = STATIC_ARRAY_SIZE(values);
   sstrncpy(vl.plugin, "dns", sizeof(vl.plugin));
   sstrncpy(vl.type, "dns_octets", sizeof(vl.type));
 
diff --git a/src/dpdkstat.c b/src/dpdkstat.c
new file mode 100644 (file)
index 0000000..6d0aabf
--- /dev/null
@@ -0,0 +1,827 @@
+/*-
+ * collectd - src/dpdkstat.c
+ * MIT License
+ *
+ * Copyright(c) 2016 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:
+ *   Maryam Tahhan <maryam.tahhan@intel.com>
+ *   Harry van Haaren <harry.van.haaren@intel.com>
+ *   Taras Chornyi <tarasx.chornyi@intel.com>
+ */
+
+#include "collectd.h"
+
+#include "common.h" /* auxiliary functions */
+#include "plugin.h" /* plugin_register_*, plugin_dispatch_values */
+#include "utils_time.h"
+
+#include <getopt.h>
+#include <poll.h>
+#include <semaphore.h>
+#include <sys/mman.h>
+#include <sys/queue.h>
+
+#include <rte_atomic.h>
+#include <rte_branch_prediction.h>
+#include <rte_common.h>
+#include <rte_config.h>
+#include <rte_debug.h>
+#include <rte_debug.h>
+#include <rte_eal.h>
+#include <rte_ethdev.h>
+#include <rte_launch.h>
+#include <rte_lcore.h>
+#include <rte_log.h>
+#include <rte_malloc.h>
+#include <rte_memory.h>
+#include <rte_memzone.h>
+#include <rte_per_lcore.h>
+#include <rte_string_fns.h>
+#include <rte_tailq.h>
+#include <rte_version.h>
+
+#define DPDK_DEFAULT_RTE_CONFIG "/var/run/.rte_config"
+#define DPDK_MAX_ARGC 8
+#define DPDKSTAT_MAX_BUFFER_SIZE (4096 * 4)
+#define DPDK_SHM_NAME "dpdk_collectd_stats_shm"
+#define ERR_BUF_SIZE 1024
+#define REINIT_SHM 1
+#define RESET 1
+#define NO_RESET 0
+
+#define RTE_VERSION_16_07 RTE_VERSION_NUM(16, 7, 0, 16)
+
+#if RTE_VERSION < RTE_VERSION_16_07
+#define DPDK_STATS_XSTAT_GET_VALUE(ctx, index) ctx->xstats[index].value
+#define DPDK_STATS_XSTAT_GET_NAME(ctx, index) ctx->xstats[index].name
+#define DPDK_STATS_CTX_GET_XSTAT_SIZE sizeof(struct rte_eth_xstats)
+#define DPDK_STATS_CTX_INIT(ctx)                                               \
+  do {                                                                         \
+    ctx->xstats = (struct rte_eth_xstats *)&ctx->raw_data[0];                  \
+  } while (0)
+#else
+#define DPDK_STATS_XSTAT_GET_VALUE(ctx, index) ctx->xstats[index].value
+#define DPDK_STATS_XSTAT_GET_NAME(ctx, index) ctx->xnames[index].name
+#define DPDK_STATS_CTX_GET_XSTAT_SIZE                                          \
+  (sizeof(struct rte_eth_xstat) + sizeof(struct rte_eth_xstat_name))
+#define DPDK_STATS_CTX_INIT(ctx)                                               \
+  do {                                                                         \
+    ctx->xstats = (struct rte_eth_xstat *)&ctx->raw_data[0];                   \
+    ctx->xnames =                                                              \
+        (struct rte_eth_xstat_name *)&ctx                                      \
+            ->raw_data[ctx->num_xstats * sizeof(struct rte_eth_xstat)];        \
+  } while (0)
+#endif
+
+enum DPDK_HELPER_ACTION {
+  DPDK_HELPER_ACTION_COUNT_STATS,
+  DPDK_HELPER_ACTION_SEND_STATS,
+};
+
+enum DPDK_HELPER_STATUS {
+  DPDK_HELPER_NOT_INITIALIZED = 0,
+  DPDK_HELPER_WAITING_ON_PRIMARY,
+  DPDK_HELPER_INITIALIZING_EAL,
+  DPDK_HELPER_ALIVE_SENDING_STATS,
+  DPDK_HELPER_GRACEFUL_QUIT,
+};
+
+struct dpdk_config_s {
+  /* General DPDK params */
+  char coremask[DATA_MAX_NAME_LEN];
+  char memory_channels[DATA_MAX_NAME_LEN];
+  char socket_memory[DATA_MAX_NAME_LEN];
+  char process_type[DATA_MAX_NAME_LEN];
+  char file_prefix[DATA_MAX_NAME_LEN];
+  cdtime_t interval;
+  uint32_t eal_initialized;
+  uint32_t enabled_port_mask;
+  char port_name[RTE_MAX_ETHPORTS][DATA_MAX_NAME_LEN];
+  uint32_t eal_argc;
+  /* Helper info */
+  int collectd_reinit_shm;
+  pid_t helper_pid;
+  sem_t sema_helper_get_stats;
+  sem_t sema_stats_in_shm;
+  int helper_pipes[2];
+  enum DPDK_HELPER_STATUS helper_status;
+  enum DPDK_HELPER_ACTION helper_action;
+  /* xstats info */
+  uint32_t num_ports;
+  uint32_t num_xstats;
+  cdtime_t port_read_time[RTE_MAX_ETHPORTS];
+  uint32_t num_stats_in_port[RTE_MAX_ETHPORTS];
+  struct rte_eth_link link_status[RTE_MAX_ETHPORTS];
+#if RTE_VERSION < RTE_VERSION_16_07
+  struct rte_eth_xstats *xstats;
+#else
+  struct rte_eth_xstat *xstats;
+  struct rte_eth_xstat_name *xnames;
+#endif
+  char *raw_data;
+  /* rte_eth_xstats from here on until the end of the SHM */
+};
+typedef struct dpdk_config_s dpdk_config_t;
+
+static int g_configured;
+static dpdk_config_t *g_configuration;
+
+static void dpdk_config_init_default(void);
+static int dpdk_config(oconfig_item_t *ci);
+static int dpdk_helper_init_eal(void);
+static int dpdk_helper_run(void);
+static int dpdk_helper_spawn(enum DPDK_HELPER_ACTION action);
+static int dpdk_init(void);
+static int dpdk_read(user_data_t *ud);
+static int dpdk_shm_cleanup(void);
+static int dpdk_shm_init(size_t size);
+
+/* Write the default configuration to the g_configuration instances */
+static void dpdk_config_init_default(void) {
+  g_configuration->interval = plugin_get_interval();
+  if (g_configuration->interval == cf_get_default_interval())
+    WARNING("dpdkstat: No time interval was configured, default value %" PRIu64
+            " ms is set",
+            CDTIME_T_TO_MS(g_configuration->interval));
+  /* Default is all ports enabled */
+  g_configuration->enabled_port_mask = ~0;
+  g_configuration->eal_argc = DPDK_MAX_ARGC;
+  g_configuration->eal_initialized = 0;
+  ssnprintf(g_configuration->coremask, DATA_MAX_NAME_LEN, "%s", "0xf");
+  ssnprintf(g_configuration->memory_channels, DATA_MAX_NAME_LEN, "%s", "1");
+  ssnprintf(g_configuration->process_type, DATA_MAX_NAME_LEN, "%s",
+            "secondary");
+  ssnprintf(g_configuration->file_prefix, DATA_MAX_NAME_LEN, "%s",
+            DPDK_DEFAULT_RTE_CONFIG);
+
+  for (int i = 0; i < RTE_MAX_ETHPORTS; i++)
+    g_configuration->port_name[i][0] = 0;
+}
+
+static int dpdk_config(oconfig_item_t *ci) {
+  int port_counter = 0;
+  /* Allocate g_configuration and
+   * initialize a POSIX SHared Memory (SHM) object.
+   */
+  int err = dpdk_shm_init(sizeof(dpdk_config_t));
+  if (err) {
+    char errbuf[ERR_BUF_SIZE];
+    ERROR("dpdkstat: error in shm_init, %s",
+          sstrerror(errno, errbuf, sizeof(errbuf)));
+    return -1;
+  }
+
+  /* Set defaults for config, overwritten by loop if config item exists */
+  dpdk_config_init_default();
+
+  for (int i = 0; i < ci->children_num; i++) {
+    oconfig_item_t *child = ci->children + i;
+
+    if (strcasecmp("Coremask", child->key) == 0) {
+      cf_util_get_string_buffer(child, g_configuration->coremask,
+                                sizeof(g_configuration->coremask));
+      DEBUG("dpdkstat:COREMASK %s ", g_configuration->coremask);
+    } else if (strcasecmp("MemoryChannels", child->key) == 0) {
+      cf_util_get_string_buffer(child, g_configuration->memory_channels,
+                                sizeof(g_configuration->memory_channels));
+      DEBUG("dpdkstat:Memory Channels %s ", g_configuration->memory_channels);
+    } else if (strcasecmp("SocketMemory", child->key) == 0) {
+      cf_util_get_string_buffer(child, g_configuration->socket_memory,
+                                sizeof(g_configuration->memory_channels));
+      DEBUG("dpdkstat: socket mem %s ", g_configuration->socket_memory);
+    } else if (strcasecmp("ProcessType", child->key) == 0) {
+      cf_util_get_string_buffer(child, g_configuration->process_type,
+                                sizeof(g_configuration->process_type));
+      DEBUG("dpdkstat: proc type %s ", g_configuration->process_type);
+    } else if ((strcasecmp("FilePrefix", child->key) == 0) &&
+               (child->values[0].type == OCONFIG_TYPE_STRING)) {
+      ssnprintf(g_configuration->file_prefix, DATA_MAX_NAME_LEN,
+                "/var/run/.%s_config", child->values[0].value.string);
+      DEBUG("dpdkstat: file prefix %s ", g_configuration->file_prefix);
+    } else if ((strcasecmp("EnabledPortMask", child->key) == 0) &&
+               (child->values[0].type == OCONFIG_TYPE_NUMBER)) {
+      g_configuration->enabled_port_mask =
+          (uint32_t)child->values[0].value.number;
+      DEBUG("dpdkstat: Enabled Port Mask %u",
+            g_configuration->enabled_port_mask);
+    } else if (strcasecmp("PortName", child->key) == 0) {
+      cf_util_get_string_buffer(
+          child, g_configuration->port_name[port_counter],
+          sizeof(g_configuration->port_name[port_counter]));
+      DEBUG("dpdkstat: Port %d Name: %s ", port_counter,
+            g_configuration->port_name[port_counter]);
+      port_counter++;
+    } else {
+      WARNING("dpdkstat: The config option \"%s\" is unknown.", child->key);
+    }
+  }                 /* End for (int i = 0; i < ci->children_num; i++)*/
+  g_configured = 1; /* Bypass configuration in dpdk_shm_init(). */
+
+  return 0;
+}
+
+/*
+ * Allocate g_configuration and initialize SHared Memory (SHM)
+ * for config and helper process
+ */
+static int dpdk_shm_init(size_t size) {
+  /*
+   * Check if SHM is already configured: when config items are provided, the
+   * config function initializes SHM. If there is no config, then init() will
+   * just return.
+   */
+  if (g_configuration)
+    return 0;
+
+  char errbuf[ERR_BUF_SIZE];
+
+  /* Create and open a new object, or open an existing object. */
+  int fd = shm_open(DPDK_SHM_NAME, O_CREAT | O_TRUNC | O_RDWR, 0666);
+  if (fd < 0) {
+    WARNING("dpdkstat:Failed to open %s as SHM:%s", DPDK_SHM_NAME,
+            sstrerror(errno, errbuf, sizeof(errbuf)));
+    goto fail;
+  }
+  /* Set the size of the shared memory object. */
+  int ret = ftruncate(fd, size);
+  if (ret != 0) {
+    WARNING("dpdkstat:Failed to resize SHM:%s",
+            sstrerror(errno, errbuf, sizeof(errbuf)));
+    goto fail_close;
+  }
+  /* Map the shared memory object into this process' virtual address space. */
+  g_configuration = mmap(0, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
+  if (g_configuration == MAP_FAILED) {
+    WARNING("dpdkstat:Failed to mmap SHM:%s",
+            sstrerror(errno, errbuf, sizeof(errbuf)));
+    goto fail_close;
+  }
+  /*
+   * Close the file descriptor, the shared memory object still exists
+   * and can only be removed by calling shm_unlink().
+   */
+  close(fd);
+
+  /* Initialize g_configuration. */
+  memset(g_configuration, 0, size);
+
+  /* Initialize the semaphores for SHM use */
+  int err = sem_init(&g_configuration->sema_helper_get_stats, 1, 0);
+  if (err) {
+    ERROR("dpdkstat semaphore init failed: %s",
+          sstrerror(errno, errbuf, sizeof(errbuf)));
+    goto fail;
+  }
+  err = sem_init(&g_configuration->sema_stats_in_shm, 1, 0);
+  if (err) {
+    ERROR("dpdkstat semaphore init failed: %s",
+          sstrerror(errno, errbuf, sizeof(errbuf)));
+    goto fail;
+  }
+
+  g_configuration->xstats = NULL;
+
+  return 0;
+
+fail_close:
+  close(fd);
+fail:
+  /* Reset to zero, as it was set to MAP_FAILED aka: (void *)-1. Avoid
+   * an issue if collectd attempts to run this plugin failure.
+   */
+  g_configuration = 0;
+  return -1;
+}
+
+static int dpdk_re_init_shm() {
+  dpdk_config_t temp_config;
+  memcpy(&temp_config, g_configuration, sizeof(dpdk_config_t));
+  DEBUG("dpdkstat: %s: ports %" PRIu32 ", xstats %" PRIu32, __func__,
+        temp_config.num_ports, temp_config.num_xstats);
+
+  size_t shm_xstats_size =
+      sizeof(dpdk_config_t) +
+      (DPDK_STATS_CTX_GET_XSTAT_SIZE * g_configuration->num_xstats);
+  DEBUG("=== SHM new size for %" PRIu32 " xstats", g_configuration->num_xstats);
+
+  int err = dpdk_shm_cleanup();
+  if (err) {
+    ERROR("dpdkstat: Error in shm_cleanup in %s", __func__);
+    return err;
+  }
+  err = dpdk_shm_init(shm_xstats_size);
+  if (err) {
+    WARNING("dpdkstat: Error in shm_init in %s", __func__);
+    return err;
+  }
+  /* If the XML config() function has been run, don't re-initialize defaults */
+  if (!g_configured)
+    dpdk_config_init_default();
+
+  memcpy(g_configuration, &temp_config, sizeof(dpdk_config_t));
+  g_configuration->collectd_reinit_shm = 0;
+  g_configuration->raw_data = (char *)(g_configuration + 1);
+  DPDK_STATS_CTX_INIT(g_configuration);
+  return 0;
+}
+
+static int dpdk_init(void) {
+  int err = dpdk_shm_init(sizeof(dpdk_config_t));
+  if (err) {
+    ERROR("dpdkstat: %s : error %d in shm_init()", __func__, err);
+    return err;
+  }
+
+  /* If the XML config() function has been run, dont re-initialize defaults */
+  if (!g_configured) {
+    dpdk_config_init_default();
+  }
+
+  return 0;
+}
+
+static int dpdk_helper_stop(int reset) {
+  g_configuration->helper_status = DPDK_HELPER_GRACEFUL_QUIT;
+  if (reset) {
+    g_configuration->eal_initialized = 0;
+    g_configuration->num_ports = 0;
+    g_configuration->xstats = NULL;
+    g_configuration->num_xstats = 0;
+    for (int i = 0; i < RTE_MAX_ETHPORTS; i++)
+      g_configuration->num_stats_in_port[i] = 0;
+  }
+  close(g_configuration->helper_pipes[1]);
+  int err = kill(g_configuration->helper_pid, SIGKILL);
+  if (err) {
+    char errbuf[ERR_BUF_SIZE];
+    WARNING("dpdkstat: error sending kill to helper: %s",
+            sstrerror(errno, errbuf, sizeof(errbuf)));
+  }
+
+  return 0;
+}
+
+static int dpdk_helper_spawn(enum DPDK_HELPER_ACTION action) {
+  char errbuf[ERR_BUF_SIZE];
+  g_configuration->eal_initialized = 0;
+  g_configuration->helper_action = action;
+  /*
+   * Create a pipe for helper stdout back to collectd. This is necessary for
+   * logging EAL failures, as rte_eal_init() calls rte_panic().
+   */
+  if (pipe(g_configuration->helper_pipes) != 0) {
+    DEBUG("dpdkstat: Could not create helper pipe: %s",
+          sstrerror(errno, errbuf, sizeof(errbuf)));
+    return -1;
+  }
+
+  int pipe0_flags = fcntl(g_configuration->helper_pipes[0], F_GETFL, 0);
+  int pipe1_flags = fcntl(g_configuration->helper_pipes[1], F_GETFL, 0);
+  if (pipe0_flags == -1 || pipe1_flags == -1) {
+    WARNING("dpdkstat: Failed setting up pipe flags: %s",
+            sstrerror(errno, errbuf, sizeof(errbuf)));
+  }
+  int pipe0_err = fcntl(g_configuration->helper_pipes[0], F_SETFL,
+                        pipe1_flags | O_NONBLOCK);
+  int pipe1_err = fcntl(g_configuration->helper_pipes[1], F_SETFL,
+                        pipe0_flags | O_NONBLOCK);
+  if (pipe0_err == -1 || pipe1_err == -1) {
+    WARNING("dpdkstat: Failed setting up pipes: %s",
+            sstrerror(errno, errbuf, sizeof(errbuf)));
+  }
+
+  pid_t pid = fork();
+  if (pid > 0) {
+    close(g_configuration->helper_pipes[1]);
+    g_configuration->helper_pid = pid;
+    DEBUG("dpdkstat: helper pid %li", (long)g_configuration->helper_pid);
+    /* Kick helper once its alive to have it start processing */
+    sem_post(&g_configuration->sema_helper_get_stats);
+  } else if (pid == 0) {
+    /* Replace stdout with a pipe to collectd. */
+    close(g_configuration->helper_pipes[0]);
+    close(STDOUT_FILENO);
+    dup2(g_configuration->helper_pipes[1], STDOUT_FILENO);
+    dpdk_helper_run();
+    exit(0);
+  } else {
+    ERROR("dpdkstat: Failed to fork helper process: %s",
+          sstrerror(errno, errbuf, sizeof(errbuf)));
+    return -1;
+  }
+  return 0;
+}
+
+/*
+ * Initialize the DPDK EAL, if this returns, EAL is successfully initialized.
+ * On failure, the EAL prints an error message, and the helper process exits.
+ */
+static int dpdk_helper_init_eal(void) {
+  g_configuration->helper_status = DPDK_HELPER_INITIALIZING_EAL;
+  char *argp[(g_configuration->eal_argc) + 1];
+  int i = 0;
+
+  argp[i++] = "collectd-dpdk";
+  if (strcasecmp(g_configuration->coremask, "") != 0) {
+    argp[i++] = "-c";
+    argp[i++] = g_configuration->coremask;
+  }
+  if (strcasecmp(g_configuration->memory_channels, "") != 0) {
+    argp[i++] = "-n";
+    argp[i++] = g_configuration->memory_channels;
+  }
+  if (strcasecmp(g_configuration->socket_memory, "") != 0) {
+    argp[i++] = "--socket-mem";
+    argp[i++] = g_configuration->socket_memory;
+  }
+  if (strcasecmp(g_configuration->file_prefix, "") != 0 &&
+      strcasecmp(g_configuration->file_prefix, DPDK_DEFAULT_RTE_CONFIG) != 0) {
+    argp[i++] = "--file-prefix";
+    argp[i++] = g_configuration->file_prefix;
+  }
+  if (strcasecmp(g_configuration->process_type, "") != 0) {
+    argp[i++] = "--proc-type";
+    argp[i++] = g_configuration->process_type;
+  }
+  g_configuration->eal_argc = i;
+
+  g_configuration->eal_initialized = 1;
+  int ret = rte_eal_init(g_configuration->eal_argc, argp);
+  if (ret < 0) {
+    g_configuration->eal_initialized = 0;
+    return ret;
+  }
+  return 0;
+}
+
+static int dpdk_helper_run(void) {
+  char errbuf[ERR_BUF_SIZE];
+  pid_t ppid = getppid();
+  g_configuration->helper_status = DPDK_HELPER_WAITING_ON_PRIMARY;
+
+  while (1) {
+    /* sem_timedwait() to avoid blocking forever */
+    cdtime_t now = cdtime();
+    cdtime_t safety_period = MS_TO_CDTIME_T(1500);
+    int ret =
+        sem_timedwait(&g_configuration->sema_helper_get_stats,
+                      &CDTIME_T_TO_TIMESPEC(now + safety_period +
+                                            g_configuration->interval * 2));
+
+    if (ret == -1 && errno == ETIMEDOUT) {
+      ERROR("dpdkstat-helper: sem timedwait()"
+            " timeout, did collectd terminate?");
+      dpdk_helper_stop(RESET);
+    }
+    /* Parent PID change means collectd died so quit the helper process. */
+    if (ppid != getppid()) {
+      WARNING("dpdkstat-helper: parent PID changed, quitting.");
+      dpdk_helper_stop(RESET);
+    }
+
+    /* Checking for DPDK primary process. */
+    if (!rte_eal_primary_proc_alive(g_configuration->file_prefix)) {
+      if (g_configuration->eal_initialized) {
+        WARNING("dpdkstat-helper: no primary alive but EAL initialized:"
+                " quitting.");
+        dpdk_helper_stop(RESET);
+      }
+      g_configuration->helper_status = DPDK_HELPER_WAITING_ON_PRIMARY;
+      /* Back to start of while() - waiting for primary process */
+      continue;
+    }
+
+    if (!g_configuration->eal_initialized) {
+      /* Initialize EAL. */
+      int ret = dpdk_helper_init_eal();
+      if (ret != 0) {
+        WARNING("ERROR INITIALIZING EAL");
+        dpdk_helper_stop(RESET);
+      }
+    }
+
+    g_configuration->helper_status = DPDK_HELPER_ALIVE_SENDING_STATS;
+
+    uint8_t nb_ports = rte_eth_dev_count();
+    if (nb_ports == 0) {
+      DEBUG("dpdkstat-helper: No DPDK ports available. "
+            "Check bound devices to DPDK driver.");
+      dpdk_helper_stop(RESET);
+    }
+
+    if (nb_ports > RTE_MAX_ETHPORTS)
+      nb_ports = RTE_MAX_ETHPORTS;
+
+    int len = 0, enabled_port_count = 0, num_xstats = 0;
+    for (uint8_t i = 0; i < nb_ports; i++) {
+      if (!(g_configuration->enabled_port_mask & (1 << i)))
+        continue;
+
+      if (g_configuration->helper_action == DPDK_HELPER_ACTION_COUNT_STATS) {
+#if RTE_VERSION >= RTE_VERSION_16_07
+        len = rte_eth_xstats_get_names(i, NULL, 0);
+#else
+        len = rte_eth_xstats_get(i, NULL, 0);
+#endif
+        if (len < 0) {
+          ERROR("dpdkstat-helper: Cannot get xstats count on port %" PRIu8, i);
+          break;
+        }
+        num_xstats += len;
+        g_configuration->num_stats_in_port[enabled_port_count] = len;
+        enabled_port_count++;
+        continue;
+      } else {
+        len = g_configuration->num_stats_in_port[enabled_port_count];
+        g_configuration->port_read_time[enabled_port_count] = cdtime();
+        ret = rte_eth_xstats_get(
+            i, g_configuration->xstats + num_xstats,
+            g_configuration->num_stats_in_port[enabled_port_count]);
+        if (ret < 0 || ret != len) {
+          DEBUG("dpdkstat-helper: Error reading xstats on port %" PRIu8
+                " len = %d",
+                i, len);
+          break;
+        }
+#if RTE_VERSION >= RTE_VERSION_16_07
+        ret = rte_eth_xstats_get_names(i, g_configuration->xnames + num_xstats,
+                                       len);
+        if (ret < 0 || ret != len) {
+          ERROR("dpdkstat-helper: Error reading xstat names (port=%d; len=%d)",
+                i, len);
+          break;
+        }
+#endif
+        num_xstats += g_configuration->num_stats_in_port[enabled_port_count];
+        enabled_port_count++;
+      }
+    } /* for (nb_ports) */
+
+    if (g_configuration->helper_action == DPDK_HELPER_ACTION_COUNT_STATS) {
+      g_configuration->num_ports = enabled_port_count;
+      g_configuration->num_xstats = num_xstats;
+      DEBUG("dpdkstat-helper ports: %" PRIu32 ", num stats: %" PRIu32,
+            g_configuration->num_ports, g_configuration->num_xstats);
+      /* Exit, allowing collectd to re-init SHM to the right size */
+      g_configuration->collectd_reinit_shm = REINIT_SHM;
+      dpdk_helper_stop(NO_RESET);
+    }
+    /* Now kick collectd send thread to send the stats */
+    int err = sem_post(&g_configuration->sema_stats_in_shm);
+    if (err) {
+      WARNING("dpdkstat: error posting semaphore to helper %s",
+              sstrerror(errno, errbuf, sizeof(errbuf)));
+      dpdk_helper_stop(RESET);
+    }
+  } /* while(1) */
+
+  return 0;
+}
+
+static void dpdk_submit_xstats(const char *dev_name, int count,
+                               uint32_t counters, cdtime_t port_read_time) {
+  for (uint32_t j = 0; j < counters; j++) {
+    value_list_t vl = VALUE_LIST_INIT;
+    char *counter_name;
+    char *type_end;
+
+    vl.values = &(value_t){.derive = (derive_t)DPDK_STATS_XSTAT_GET_VALUE(
+                               g_configuration, count + j)};
+    vl.values_len = 1; /* Submit stats one at a time */
+    vl.time = port_read_time;
+    sstrncpy(vl.plugin, "dpdkstat", sizeof(vl.plugin));
+    sstrncpy(vl.plugin_instance, dev_name, sizeof(vl.plugin_instance));
+    counter_name = DPDK_STATS_XSTAT_GET_NAME(g_configuration, count + j);
+    if (counter_name == NULL) {
+      WARNING("dpdkstat: Failed to get counter name.");
+      return;
+    }
+
+    type_end = strrchr(counter_name, '_');
+
+    if ((type_end != NULL) &&
+        (strncmp(counter_name, "rx_", strlen("rx_")) == 0)) {
+      if (strstr(type_end, "bytes") != NULL) {
+        sstrncpy(vl.type, "if_rx_octets", sizeof(vl.type));
+      } else if (strstr(type_end, "error") != NULL) {
+        sstrncpy(vl.type, "if_rx_errors", sizeof(vl.type));
+      } else if (strstr(type_end, "dropped") != NULL) {
+        sstrncpy(vl.type, "if_rx_dropped", sizeof(vl.type));
+      } else if (strstr(type_end, "packets") != NULL) {
+        sstrncpy(vl.type, "if_rx_packets", sizeof(vl.type));
+      } else if (strstr(type_end, "_placement") != NULL) {
+        sstrncpy(vl.type, "if_rx_errors", sizeof(vl.type));
+      } else if (strstr(type_end, "_buff") != NULL) {
+        sstrncpy(vl.type, "if_rx_errors", sizeof(vl.type));
+      } else {
+        /* Does not fit obvious type: use a more generic one */
+        sstrncpy(vl.type, "derive", sizeof(vl.type));
+      }
+
+    } else if ((type_end != NULL) &&
+               (strncmp(counter_name, "tx_", strlen("tx_"))) == 0) {
+      if (strstr(type_end, "bytes") != NULL) {
+        sstrncpy(vl.type, "if_tx_octets", sizeof(vl.type));
+      } else if (strstr(type_end, "error") != NULL) {
+        sstrncpy(vl.type, "if_tx_errors", sizeof(vl.type));
+      } else if (strstr(type_end, "dropped") != NULL) {
+        sstrncpy(vl.type, "if_tx_dropped", sizeof(vl.type));
+      } else if (strstr(type_end, "packets") != NULL) {
+        sstrncpy(vl.type, "if_tx_packets", sizeof(vl.type));
+      } else {
+        /* Does not fit obvious type: use a more generic one */
+        sstrncpy(vl.type, "derive", sizeof(vl.type));
+      }
+    } else if ((type_end != NULL) &&
+               (strncmp(counter_name, "flow_", strlen("flow_"))) == 0) {
+
+      if (strstr(type_end, "_filters") != NULL) {
+        sstrncpy(vl.type, "operations", sizeof(vl.type));
+      } else if (strstr(type_end, "error") != NULL)
+        sstrncpy(vl.type, "errors", sizeof(vl.type));
+
+    } else if ((type_end != NULL) &&
+               (strncmp(counter_name, "mac_", strlen("mac_"))) == 0) {
+      if (strstr(type_end, "error") != NULL) {
+        sstrncpy(vl.type, "errors", sizeof(vl.type));
+      }
+    } else {
+      /* Does not fit obvious type, or strrchr error:
+       *   use a more generic type */
+      sstrncpy(vl.type, "derive", sizeof(vl.type));
+    }
+
+    sstrncpy(vl.type_instance, counter_name, sizeof(vl.type_instance));
+    plugin_dispatch_values(&vl);
+  }
+}
+
+static int dpdk_read(user_data_t *ud) {
+  int ret = 0;
+
+  /*
+   * Check if SHM flag is set to be re-initialized. AKA DPDK ports have been
+   * counted, so re-init SHM to be large enough to fit all the statistics.
+   */
+  if (g_configuration->collectd_reinit_shm) {
+    DEBUG("dpdkstat: read() now reinit SHM then launching send-thread");
+    dpdk_re_init_shm();
+  }
+
+  /*
+   * Check if DPDK proc is alive, and has already counted port / stats. This
+   * must be done in dpdk_read(), because the DPDK primary process may not be
+   * alive at dpdk_init() time.
+   */
+  if (g_configuration->helper_status == DPDK_HELPER_NOT_INITIALIZED ||
+      g_configuration->helper_status == DPDK_HELPER_GRACEFUL_QUIT) {
+    int action = DPDK_HELPER_ACTION_SEND_STATS;
+    if (g_configuration->num_xstats == 0)
+      action = DPDK_HELPER_ACTION_COUNT_STATS;
+    /* Spawn the helper thread to count stats or to read stats. */
+    int err = dpdk_helper_spawn(action);
+    if (err) {
+      char errbuf[ERR_BUF_SIZE];
+      ERROR("dpdkstat: error spawning helper %s",
+            sstrerror(errno, errbuf, sizeof(errbuf)));
+      return -1;
+    }
+  }
+
+  pid_t ws = waitpid(g_configuration->helper_pid, NULL, WNOHANG);
+  /*
+   * Conditions under which to respawn helper:
+   *  waitpid() fails, helper process died (or quit), so respawn
+   */
+  _Bool respawn_helper = 0;
+  if (ws != 0) {
+    respawn_helper = 1;
+  }
+
+  char buf[DPDKSTAT_MAX_BUFFER_SIZE];
+  char out[DPDKSTAT_MAX_BUFFER_SIZE];
+
+  /* non blocking check on helper logging pipe */
+  struct pollfd fds = {
+      .fd = g_configuration->helper_pipes[0], .events = POLLIN,
+  };
+  int data_avail = poll(&fds, 1, 0);
+  if (data_avail < 0) {
+    char errbuf[ERR_BUF_SIZE];
+    if (errno != EINTR || errno != EAGAIN)
+      ERROR("dpdkstats: poll(2) failed: %s",
+            sstrerror(errno, errbuf, sizeof(errbuf)));
+  }
+  while (data_avail) {
+    int nbytes = read(g_configuration->helper_pipes[0], buf, sizeof(buf));
+    if (nbytes <= 0)
+      break;
+    ssnprintf(out, nbytes, "%s", buf);
+    DEBUG("dpdkstat: helper-proc: %s", out);
+  }
+
+  if (respawn_helper) {
+    if (g_configuration->helper_pid)
+      dpdk_helper_stop(RESET);
+    dpdk_helper_spawn(DPDK_HELPER_ACTION_COUNT_STATS);
+  }
+
+  /* Kick helper process through SHM */
+  sem_post(&g_configuration->sema_helper_get_stats);
+
+  cdtime_t now = cdtime();
+  ret = sem_timedwait(&g_configuration->sema_stats_in_shm,
+                      &CDTIME_T_TO_TIMESPEC(now + g_configuration->interval));
+  if (ret == -1) {
+    if (errno == ETIMEDOUT)
+      DEBUG(
+          "dpdkstat: timeout in collectd thread: is a DPDK Primary running? ");
+    return 0;
+  }
+
+  /* Dispatch the stats.*/
+  uint32_t count = 0, port_num = 0;
+
+  for (uint32_t i = 0; i < g_configuration->num_ports; i++) {
+    char dev_name[64];
+    cdtime_t port_read_time = g_configuration->port_read_time[i];
+    uint32_t counters_num = g_configuration->num_stats_in_port[i];
+    size_t ports_max = CHAR_BIT * sizeof(g_configuration->enabled_port_mask);
+    for (size_t j = port_num; j < ports_max; j++) {
+      if ((g_configuration->enabled_port_mask & (1 << j)) != 0)
+        break;
+      port_num++;
+    }
+
+    if (g_configuration->port_name[i][0] != 0)
+      ssnprintf(dev_name, sizeof(dev_name), "%s",
+                g_configuration->port_name[i]);
+    else
+      ssnprintf(dev_name, sizeof(dev_name), "port.%" PRIu32, port_num);
+    dpdk_submit_xstats(dev_name, count, counters_num, port_read_time);
+    count += counters_num;
+    port_num++;
+  } /* for each port */
+  return 0;
+}
+
+static int dpdk_shm_cleanup(void) {
+  int ret = munmap(g_configuration, sizeof(dpdk_config_t));
+  g_configuration = 0;
+  if (ret) {
+    ERROR("dpdkstat: munmap returned %d", ret);
+    return ret;
+  }
+  ret = shm_unlink(DPDK_SHM_NAME);
+  if (ret) {
+    ERROR("dpdkstat: shm_unlink returned %d", ret);
+    return ret;
+  }
+  return 0;
+}
+
+static int dpdk_shutdown(void) {
+  int ret = 0;
+  char errbuf[ERR_BUF_SIZE];
+  close(g_configuration->helper_pipes[1]);
+  int err = kill(g_configuration->helper_pid, SIGKILL);
+  if (err) {
+    ERROR("dpdkstat: error sending sigkill to helper %s",
+          sstrerror(errno, errbuf, sizeof(errbuf)));
+    ret = -1;
+  }
+  err = dpdk_shm_cleanup();
+  if (err) {
+    ERROR("dpdkstat: error cleaning up SHM: %s",
+          sstrerror(errno, errbuf, sizeof(errbuf)));
+    ret = -1;
+  }
+
+  return ret;
+}
+
+void module_register(void) {
+  plugin_register_complex_config("dpdkstat", dpdk_config);
+  plugin_register_init("dpdkstat", dpdk_init);
+  plugin_register_complex_read(NULL, "dpdkstat", dpdk_read, 0, NULL);
+  plugin_register_shutdown("dpdkstat", dpdk_shutdown);
+}
index 1daa25f..2ee1656 100644 (file)
@@ -91,7 +91,6 @@ static int drbd_submit_fields(long int resource, char **fields,
   }
 
   vl.values_len = 1;
-  sstrncpy(vl.host, hostname_g, sizeof(vl.host));
   sstrncpy(vl.plugin, "drbd", sizeof(vl.plugin));
   sstrncpy(vl.plugin_instance, plugin_instance, sizeof(vl.plugin_instance));
   sstrncpy(vl.type, "drbd_resource", sizeof(vl.type));
index b7a5bca..50f7662 100644 (file)
@@ -361,8 +361,6 @@ static void *collect(void *arg) {
 } /* static void *collect (void *) */
 
 static void *open_connection(void __attribute__((unused)) * arg) {
-  struct sockaddr_un addr;
-
   const char *path = (NULL == sock_file) ? SOCK_PATH : sock_file;
   const char *group = (NULL == sock_group) ? COLLECTD_GRP_NAME : sock_group;
 
@@ -375,7 +373,9 @@ static void *open_connection(void __attribute__((unused)) * arg) {
     pthread_exit((void *)1);
   }
 
-  addr.sun_family = AF_UNIX;
+  struct sockaddr_un addr = {
+    .sun_family = AF_UNIX
+  };
   sstrncpy(addr.sun_path, path, (size_t)(UNIX_PATH_MAX - 1));
 
   errno = 0;
@@ -448,7 +448,7 @@ static void *open_connection(void __attribute__((unused)) * arg) {
       collectors[i]->socket = NULL;
 
       if (plugin_thread_create(&collectors[i]->thread, &ptattr, collect,
-                               collectors[i]) != 0) {
+                               collectors[i], "email collector") != 0) {
         char errbuf[1024];
         log_err("plugin_thread_create() failed: %s",
                 sstrerror(errno, errbuf, sizeof(errbuf)));
@@ -531,7 +531,8 @@ static void *open_connection(void __attribute__((unused)) * arg) {
 } /* static void *open_connection (void *) */
 
 static int email_init(void) {
-  if (plugin_thread_create(&connector, NULL, open_connection, NULL) != 0) {
+  if (plugin_thread_create(&connector, NULL, open_connection, NULL,
+                           "email listener") != 0) {
     char errbuf[1024];
     disabled = 1;
     log_err("plugin_thread_create() failed: %s",
@@ -613,14 +614,10 @@ static int email_shutdown(void) {
 
 static void email_submit(const char *type, const char *type_instance,
                          gauge_t value) {
-  value_t values[1];
   value_list_t vl = VALUE_LIST_INIT;
 
-  values[0].gauge = value;
-
-  vl.values = values;
+  vl.values = &(value_t){.gauge = value};
   vl.values_len = 1;
-  sstrncpy(vl.host, hostname_g, sizeof(vl.host));
   sstrncpy(vl.plugin, "email", sizeof(vl.plugin));
   sstrncpy(vl.type, type, sizeof(vl.type));
   sstrncpy(vl.type_instance, type_instance, sizeof(vl.type_instance));
index e312a0b..de35291 100644 (file)
 
 #define ENTROPY_FILE "/proc/sys/kernel/random/entropy_avail"
 
-static void entropy_submit(double entropy) {
-  value_t values[1];
+static void entropy_submit(value_t value) {
   value_list_t vl = VALUE_LIST_INIT;
 
-  values[0].gauge = entropy;
-
-  vl.values = values;
+  vl.values = &value;
   vl.values_len = 1;
-  sstrncpy(vl.host, hostname_g, sizeof(vl.host));
   sstrncpy(vl.plugin, "entropy", sizeof(vl.plugin));
   sstrncpy(vl.type, "entropy", sizeof(vl.type));
 
@@ -51,25 +47,13 @@ static void entropy_submit(double entropy) {
 }
 
 static int entropy_read(void) {
-  double entropy;
-  FILE *fh;
-  char buffer[64];
-
-  fh = fopen(ENTROPY_FILE, "r");
-  if (fh == NULL)
-    return (-1);
-
-  if (fgets(buffer, sizeof(buffer), fh) == NULL) {
-    fclose(fh);
+  value_t v;
+  if (parse_value_file(ENTROPY_FILE, &v, DS_TYPE_GAUGE) != 0) {
+    ERROR("entropy plugin: Reading \"" ENTROPY_FILE "\" failed.");
     return (-1);
   }
-  fclose(fh);
-
-  entropy = atof(buffer);
-
-  if (entropy > 0.0)
-    entropy_submit(entropy);
 
+  entropy_submit(v);
   return (0);
 }
 
index a6635c5..bd55252 100644 (file)
@@ -161,7 +161,6 @@ static void ethstat_submit_value(const char *device, const char *type_instance,
                                  derive_t value) {
   static c_complain_t complain_no_map = C_COMPLAIN_INIT_STATIC;
 
-  value_t values[1];
   value_list_t vl = VALUE_LIST_INIT;
   value_map_t *map = NULL;
 
@@ -178,11 +177,9 @@ static void ethstat_submit_value(const char *device, const char *type_instance,
     return;
   }
 
-  values[0].derive = value;
-  vl.values = values;
+  vl.values = &(value_t){.derive = value};
   vl.values_len = 1;
 
-  sstrncpy(vl.host, hostname_g, sizeof(vl.host));
   sstrncpy(vl.plugin, "ethstat", sizeof(vl.plugin));
   sstrncpy(vl.plugin_instance, device, sizeof(vl.plugin_instance));
   if (map != NULL) {
index 3e48028..28380ac 100644 (file)
@@ -250,7 +250,7 @@ static void set_environment(void) /* {{{ */
             CDTIME_T_TO_DOUBLE(plugin_get_interval()));
   setenv("COLLECTD_INTERVAL", buffer, /* overwrite = */ 1);
 
-  ssnprintf(buffer, sizeof(buffer), "%s", hostname_g);
+  sstrncpy(buffer, hostname_g, sizeof(buffer));
   setenv("COLLECTD_HOSTNAME", buffer, /* overwrite = */ 1);
 #else
   ssnprintf(buffer, sizeof(buffer), "COLLECTD_INTERVAL=%.3f",
@@ -500,7 +500,7 @@ failed:
 static int parse_line(char *buffer) /* {{{ */
 {
   if (strncasecmp("PUTVAL", buffer, strlen("PUTVAL")) == 0)
-    return (handle_putval(stdout, buffer));
+    return (cmd_handle_putval(stdout, buffer));
   else if (strncasecmp("PUTNOTIF", buffer, strlen("PUTNOTIF")) == 0)
     return (handle_putnotif(stdout, buffer));
   else {
@@ -787,7 +787,7 @@ static int exec_read(void) /* {{{ */
 
     pthread_attr_init(&attr);
     pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
-    plugin_thread_create(&t, &attr, exec_read_one, (void *)pl);
+    plugin_thread_create(&t, &attr, exec_read_one, (void *)pl, "exec read");
     pthread_attr_destroy(&attr);
   } /* for (pl) */
 
@@ -826,7 +826,8 @@ static int exec_notification(const notification_t *n, /* {{{ */
 
     pthread_attr_init(&attr);
     pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
-    plugin_thread_create(&t, &attr, exec_notification_one, (void *)pln);
+    plugin_thread_create(&t, &attr, exec_notification_one, (void *)pln,
+                         "exec notify");
     pthread_attr_destroy(&attr);
   } /* for (pl) */
 
index 3fbabf0..9210783 100644 (file)
@@ -54,17 +54,12 @@ static int fhcount_config(const char *key, const char *value) {
 
 static void fhcount_submit(const char *type, const char *type_instance,
                            gauge_t value) {
-
-  value_t values[1];
   value_list_t vl = VALUE_LIST_INIT;
 
-  values[0].gauge = value;
-
-  vl.values = values;
+  vl.values = &(value_t){.gauge = value};
   vl.values_len = 1;
 
   // Compose the metric
-  sstrncpy(vl.host, hostname_g, sizeof(vl.host));
   sstrncpy(vl.plugin, "fhcount", sizeof(vl.plugin));
   sstrncpy(vl.type, type, sizeof(vl.type));
   sstrncpy(vl.type_instance, type_instance, sizeof(vl.type_instance));
index 25c3e65..a78b880 100644 (file)
@@ -59,21 +59,17 @@ static fc_directory_conf_t **directories = NULL;
 static size_t directories_num = 0;
 
 static void fc_submit_dir(const fc_directory_conf_t *dir) {
-  value_t values[1];
   value_list_t vl = VALUE_LIST_INIT;
 
-  values[0].gauge = (gauge_t)dir->files_num;
-
-  vl.values = values;
-  vl.values_len = STATIC_ARRAY_SIZE(values);
-  sstrncpy(vl.host, hostname_g, sizeof(vl.host));
+  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));
 
   plugin_dispatch_values(&vl);
 
-  values[0].gauge = (gauge_t)dir->files_size;
+  vl.values = &(value_t){.gauge = (gauge_t)dir->files_size};
   sstrncpy(vl.type, "bytes", sizeof(vl.type));
 
   plugin_dispatch_values(&vl);
index 6dc6e72..c410ea3 100644 (file)
@@ -113,7 +113,6 @@ static void fscache_submit(const char *section, const char *name,
   vl.values = &value;
   vl.values_len = 1;
 
-  sstrncpy(vl.host, hostname_g, sizeof(vl.host));
   sstrncpy(vl.plugin, "fscache", sizeof(vl.plugin));
   sstrncpy(vl.plugin_instance, section, sizeof(vl.plugin_instance));
   sstrncpy(vl.type, "fscache_stat", sizeof(vl.type));
index 984d5f5..fc9a232 100644 (file)
@@ -812,8 +812,9 @@ static int mc_receive_thread_start(void) /* {{{ */
 
   mc_receive_thread_loop = 1;
 
-  status = plugin_thread_create(&mc_receive_thread_id, /* attr = */ NULL,
-                                mc_receive_thread, /* args = */ NULL);
+  status =
+      plugin_thread_create(&mc_receive_thread_id, /* attr = */ NULL,
+                           mc_receive_thread, /* args = */ NULL, "gmond recv");
   if (status != 0) {
     ERROR("gmond plugin: Starting receive thread failed.");
     mc_receive_thread_loop = 0;
index 8ebbf47..a4b3e2e 100644 (file)
--- a/src/gps.c
+++ b/src/gps.c
@@ -64,6 +64,7 @@ static cgps_data_t cgps_data = {NAN, NAN, NAN, NAN};
 static pthread_t cgps_thread_id;
 static pthread_mutex_t cgps_data_lock = PTHREAD_MUTEX_INITIALIZER;
 static pthread_mutex_t cgps_thread_lock = PTHREAD_MUTEX_INITIALIZER;
+static pthread_cond_t cgps_thread_cond = PTHREAD_COND_INITIALIZER;
 static int cgps_thread_shutdown = CGPS_FALSE;
 static int cgps_thread_running = CGPS_FALSE;
 
@@ -71,24 +72,16 @@ static int cgps_thread_running = CGPS_FALSE;
  * Non blocking pause for the thread.
  */
 static int cgps_thread_pause(cdtime_t pTime) {
-  cdtime_t now;
-  now = cdtime();
-  struct timespec pause_th;
-  CDTIME_T_TO_TIMESPEC(MS_TO_CDTIME_T(10), &pause_th);
-  while (CGPS_TRUE) {
-    if ((cdtime() - now) > pTime) {
-      break;
-    }
+  cdtime_t until = cdtime() + pTime;
 
-    pthread_mutex_lock(&cgps_thread_lock);
-    if (cgps_thread_shutdown == CGPS_TRUE) {
-      return CGPS_FALSE;
-    }
-    pthread_mutex_unlock(&cgps_thread_lock);
-    nanosleep(&pause_th, NULL);
-  }
+  pthread_mutex_lock(&cgps_thread_lock);
+  pthread_cond_timedwait(&cgps_thread_cond, &cgps_thread_lock,
+                         &CDTIME_T_TO_TIMESPEC(until));
+
+  int ret = !cgps_thread_shutdown;
 
-  return CGPS_TRUE;
+  pthread_mutex_lock(&cgps_thread_lock);
+  return ret;
 }
 
 /**
@@ -207,14 +200,10 @@ quit:
  */
 static void cgps_submit(const char *type, gauge_t value,
                         const char *type_instance) {
-  value_t values[1];
   value_list_t vl = VALUE_LIST_INIT;
 
-  values[0].gauge = value;
-
-  vl.values = values;
+  vl.values = &(value_t){.gauge = value};
   vl.values_len = 1;
-  sstrncpy(vl.host, hostname_g, sizeof(vl.host));
   sstrncpy(vl.plugin, "gps", sizeof(vl.plugin));
   sstrncpy(vl.type, type, sizeof(vl.type));
   sstrncpy(vl.type_instance, type_instance, sizeof(vl.type_instance));
@@ -293,7 +282,8 @@ static int cgps_init(void) {
         CDTIME_T_TO_DOUBLE(cgps_config_data.timeout),
         CDTIME_T_TO_DOUBLE(cgps_config_data.pause_connect));
 
-  status = plugin_thread_create(&cgps_thread_id, NULL, cgps_thread, NULL);
+  status =
+      plugin_thread_create(&cgps_thread_id, NULL, cgps_thread, NULL, "gps");
   if (status != 0) {
     ERROR("gps plugin: pthread_create() failed.");
     return (-1);
@@ -310,6 +300,7 @@ static int cgps_shutdown(void) {
 
   pthread_mutex_lock(&cgps_thread_lock);
   cgps_thread_shutdown = CGPS_TRUE;
+  pthread_cond_broadcast(&cgps_thread_cond);
   pthread_mutex_unlock(&cgps_thread_lock);
 
   pthread_join(cgps_thread_id, &res);
index 8b76954..0ae80bb 100644 (file)
@@ -1,6 +1,7 @@
 /**
  * collectd - src/grpc.cc
  * Copyright (C) 2015-2016 Sebastian Harl
+ * Copyright (C) 2016      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"),
@@ -22,6 +23,7 @@
  *
  * Authors:
  *   Sebastian Harl <sh at tokkee.org>
+ *   Florian octo Forster <octo at collectd.org>
  **/
 
 #include <grpc++/grpc++.h>
@@ -47,8 +49,8 @@ extern "C" {
 
 using collectd::Collectd;
 
-using collectd::DispatchValuesRequest;
-using collectd::DispatchValuesResponse;
+using collectd::PutValuesRequest;
+using collectd::PutValuesResponse;
 using collectd::QueryValuesRequest;
 using collectd::QueryValuesResponse;
 
@@ -283,13 +285,13 @@ public:
                return status;
        }
 
-       grpc::Status DispatchValues(grpc::ServerContext *ctx,
-                                                               grpc::ServerReader<DispatchValuesRequest> *reader,
-                                                               DispatchValuesResponse *res) override {
-               DispatchValuesRequest req;
+       grpc::Status PutValues(grpc::ServerContext *ctx,
+                                                  grpc::ServerReader<PutValuesRequest> *reader,
+                                                  PutValuesResponse *res) override {
+               PutValuesRequest req;
 
                while (reader->Read(&req)) {
-                       value_list_t vl = VALUE_LIST_INIT;
+                       value_list_t vl = {0};
                        auto status = unmarshal_value_list(req.value_list(), &vl);
                        if (!status.ok())
                                return status;
@@ -426,18 +428,18 @@ public:
        CollectdClient(std::shared_ptr<grpc::ChannelInterface> channel) : stub_(Collectd::NewStub(channel)) {
        }
 
-       int DispatchValues(value_list_t const *vl) {
+       int PutValues(value_list_t const *vl) {
                grpc::ClientContext ctx;
 
-               DispatchValuesRequest req;
+               PutValuesRequest req;
                auto status = marshal_value_list(vl, req.mutable_value_list());
                if (!status.ok()) {
                        ERROR("grpc: Marshalling value_list_t failed.");
                        return -1;
                }
 
-               DispatchValuesResponse res;
-               auto stream = stub_->DispatchValues(&ctx, &res);
+               PutValuesResponse res;
+               auto stream = stub_->PutValues(&ctx, &res);
                if (!stream->Write(req)) {
                        NOTICE("grpc: Broken stream.");
                        /* intentionally not returning. */
@@ -451,7 +453,7 @@ public:
                }
 
                return 0;
-       } /* int DispatchValues */
+       } /* int PutValues */
 
 private:
        std::unique_ptr<Collectd::Stub> stub_;
@@ -471,7 +473,7 @@ extern "C" {
                        value_list_t const *vl,
                        user_data_t *ud) {
                CollectdClient *c = (CollectdClient *) ud->data;
-               return c->DispatchValues(vl);
+               return c->PutValues(vl);
        }
 
        static int c_grpc_config_listen(oconfig_item_t *ci)
index fbaff3a..321c165 100644 (file)
@@ -3,6 +3,7 @@
  * Copyright (C) 2005,2006  Vincent Stehlé
  * Copyright (C) 2006-2010  Florian octo Forster
  * Copyright (C) 2008       Sebastian Harl
+ * Copyright (C) 2014       Carnegie Mellon University
  *
  * 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
@@ -22,6 +23,7 @@
  *   Vincent Stehlé <vincent.stehle at free.fr>
  *   Florian octo Forster <octo at collectd.org>
  *   Sebastian Harl <sh at tokkee.org>
+ *   Benjamin Gilbert <bgilbert at backtick.net>
  *
  * TODO:
  *   Do a pass, some day, and spare some memory. We consume too much for now
@@ -34,6 +36,7 @@
 #include "common.h"
 #include "plugin.h"
 
+#include <assert.h>
 #include <libgen.h> /* for basename */
 #include <netdb.h>
 #include <netinet/in.h>
@@ -45,6 +48,7 @@
 
 #define HDDTEMP_DEF_HOST "127.0.0.1"
 #define HDDTEMP_DEF_PORT "7634"
+#define HDDTEMP_MAX_RECV_BUF (1 << 20)
 
 static const char *config_keys[] = {"Host", "Port"};
 static int config_keys_num = STATIC_ARRAY_SIZE(config_keys);
@@ -75,10 +79,14 @@ static char hddtemp_port[16];
  *  we need to create a new socket each time. Is there another way?
  *  Hm, maybe we can re-use the `sockaddr' structure? -octo
  */
-static int hddtemp_query_daemon(char *buffer, int buffer_size) {
+static char *hddtemp_query_daemon(void) {
   int fd;
   ssize_t status;
+
+  char *buffer;
+  int buffer_size;
   int buffer_fill;
+  char *new_buffer;
 
   const char *host;
   const char *port;
@@ -104,7 +112,7 @@ static int hddtemp_query_daemon(char *buffer, int buffer_size) {
     ERROR("hddtemp plugin: getaddrinfo (%s, %s): %s", host, port,
           (ai_return == EAI_SYSTEM) ? sstrerror(errno, errbuf, sizeof(errbuf))
                                     : gai_strerror(ai_return));
-    return (-1);
+    return (NULL);
   }
 
   fd = -1;
@@ -138,16 +146,37 @@ static int hddtemp_query_daemon(char *buffer, int buffer_size) {
 
   if (fd < 0) {
     ERROR("hddtemp plugin: Could not connect to daemon.");
-    return (-1);
+    return (NULL);
   }
 
   /* receive data from the hddtemp daemon */
-  memset(buffer, '\0', buffer_size);
-
+  buffer = NULL;
+  buffer_size = 0;
   buffer_fill = 0;
-  while ((status = read(fd, buffer + buffer_fill, buffer_size - buffer_fill)) !=
-         0) {
-    if (status == -1) {
+  while (1) {
+    if ((buffer_size == 0) || (buffer_fill >= buffer_size - 1)) {
+      if (buffer_size == 0)
+        buffer_size = 1024;
+      else
+        buffer_size *= 2;
+      if (buffer_size > HDDTEMP_MAX_RECV_BUF) {
+        WARNING("hddtemp plugin: Message from hddtemp has been "
+                "truncated.");
+        break;
+      }
+      new_buffer = realloc(buffer, buffer_size);
+      if (new_buffer == NULL) {
+        close(fd);
+        free(buffer);
+        ERROR("hddtemp plugin: Allocation failed.");
+        return (NULL);
+      }
+      buffer = new_buffer;
+    }
+    status = read(fd, buffer + buffer_fill, buffer_size - buffer_fill - 1);
+    if (status == 0) {
+      break;
+    } else if (status == -1) {
       char errbuf[1024];
 
       if ((errno == EAGAIN) || (errno == EINTR))
@@ -156,28 +185,25 @@ static int hddtemp_query_daemon(char *buffer, int buffer_size) {
       ERROR("hddtemp plugin: Error reading from socket: %s",
             sstrerror(errno, errbuf, sizeof(errbuf)));
       close(fd);
-      return (-1);
+      free(buffer);
+      return (NULL);
     }
     buffer_fill += status;
-
-    if (buffer_fill >= buffer_size)
-      break;
   }
 
-  if (buffer_fill >= buffer_size) {
-    buffer[buffer_size - 1] = '\0';
-    WARNING("hddtemp plugin: Message from hddtemp has been "
-            "truncated.");
-  } else if (buffer_fill == 0) {
+  if (buffer_fill == 0) {
     WARNING("hddtemp plugin: Peer has unexpectedly shut down "
             "the socket. Buffer: `%s'",
             buffer);
     close(fd);
-    return (-1);
+    free(buffer);
+    return (NULL);
   }
 
+  assert(buffer_fill < buffer_size);
+  buffer[buffer_fill] = '\0';
   close(fd);
-  return (0);
+  return (buffer);
 }
 
 static int hddtemp_config(const char *key, const char *value) {
@@ -199,14 +225,10 @@ static int hddtemp_config(const char *key, const char *value) {
 }
 
 static void hddtemp_submit(char *type_instance, double value) {
-  value_t values[1];
   value_list_t vl = VALUE_LIST_INIT;
 
-  values[0].gauge = value;
-
-  vl.values = values;
+  vl.values = &(value_t){.gauge = value};
   vl.values_len = 1;
-  sstrncpy(vl.host, hostname_g, sizeof(vl.host));
   sstrncpy(vl.plugin, "hddtemp", sizeof(vl.plugin));
   sstrncpy(vl.type, "temperature", sizeof(vl.type));
   sstrncpy(vl.type_instance, type_instance, sizeof(vl.type_instance));
@@ -215,52 +237,45 @@ static void hddtemp_submit(char *type_instance, double value) {
 }
 
 static int hddtemp_read(void) {
-  char buf[1024];
-  char *fields[128];
+  char *buf;
   char *ptr;
   char *saveptr;
-  int num_fields;
-  int num_disks;
+  char *name;
+  char *model;
+  char *temperature;
+  char *mode;
 
   /* get data from daemon */
-  if (hddtemp_query_daemon(buf, sizeof(buf)) < 0)
+  buf = hddtemp_query_daemon();
+  if (buf == NULL)
     return (-1);
 
   /* NB: strtok_r will eat up "||" and leading "|"'s */
-  num_fields = 0;
   ptr = buf;
   saveptr = NULL;
-  while ((fields[num_fields] = strtok_r(ptr, "|", &saveptr)) != NULL) {
-    ptr = NULL;
-    num_fields++;
+  while ((name = strtok_r(ptr, "|", &saveptr)) != NULL &&
+         (model = strtok_r(NULL, "|", &saveptr)) != NULL &&
+         (temperature = strtok_r(NULL, "|", &saveptr)) != NULL &&
+         (mode = strtok_r(NULL, "|", &saveptr)) != NULL) {
+    double temperature_value;
 
-    if (num_fields >= 128)
-      break;
-  }
-
-  num_disks = num_fields / 4;
-
-  for (int i = 0; i < num_disks; i++) {
-    char *name;
-    double temperature;
-    char *mode;
-
-    mode = fields[4 * i + 3];
-    name = basename(fields[4 * i + 0]);
+    ptr = NULL;
 
     /* Skip non-temperature information */
     if (mode[0] != 'C' && mode[0] != 'F')
       continue;
 
-    temperature = atof(fields[4 * i + 2]);
+    name = basename(name);
+    temperature_value = atof(temperature);
 
     /* Convert farenheit to celsius */
     if (mode[0] == 'F')
-      temperature = (temperature - 32.0) * 5.0 / 9.0;
+      temperature_value = (temperature_value - 32.0) * 5.0 / 9.0;
 
-    hddtemp_submit(name, temperature);
+    hddtemp_submit(name, temperature_value);
   }
 
+  free(buf);
   return (0);
 } /* int hddtemp_read */
 
diff --git a/src/hugepages.c b/src/hugepages.c
new file mode 100644 (file)
index 0000000..f23a6e2
--- /dev/null
@@ -0,0 +1,277 @@
+/*-
+ * collectd - src/hugepages.c
+ * MIT License
+ *
+ * Copyright(c) 2016 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:
+ *   Jaroslav Safka <jaroslavx.safka@intel.com>
+ *   Kim-Marie Jones <kim-marie.jones@intel.com>
+ *   Florian Forster <octo at collectd.org>
+ */
+
+#include "collectd.h"
+
+#include "common.h" /* auxiliary functions */
+#include "plugin.h" /* plugin_register_*, plugin_dispatch_values */
+
+static const char g_plugin_name[] = "hugepages";
+
+static _Bool g_flag_rpt_numa = 1;
+static _Bool g_flag_rpt_mm = 1;
+
+static _Bool g_values_pages = 1;
+static _Bool g_values_bytes = 0;
+static _Bool g_values_percent = 0;
+
+#define HP_HAVE_NR 0x01
+#define HP_HAVE_SURPLUS 0x02
+#define HP_HAVE_FREE 0x04
+#define HP_HAVE_ALL 0x07
+
+struct entry_info {
+  char *d_name;
+  const char *node;
+  size_t page_size_kb;
+
+  gauge_t nr;
+  gauge_t surplus;
+  gauge_t free;
+  uint8_t flags;
+};
+
+static int hp_config(oconfig_item_t *ci) {
+  for (int i = 0; i < ci->children_num; i++) {
+    oconfig_item_t *child = ci->children + i;
+    if (strcasecmp("ReportPerNodeHP", child->key) == 0)
+      cf_util_get_boolean(child, &g_flag_rpt_numa);
+    else if (strcasecmp("ReportRootHP", child->key) == 0)
+      cf_util_get_boolean(child, &g_flag_rpt_mm);
+    else if (strcasecmp("ValuesPages", child->key) == 0)
+      cf_util_get_boolean(child, &g_values_pages);
+    else if (strcasecmp("ValuesBytes", child->key) == 0)
+      cf_util_get_boolean(child, &g_values_bytes);
+    else if (strcasecmp("ValuesPercentage", child->key) == 0)
+      cf_util_get_boolean(child, &g_values_percent);
+    else
+      ERROR("%s: Invalid configuration option: \"%s\".", g_plugin_name,
+            child->key);
+  }
+
+  return (0);
+}
+
+static void submit_hp(const struct entry_info *info) {
+  value_list_t vl = VALUE_LIST_INIT;
+
+  vl.values = &(value_t){.gauge = NAN};
+  vl.values_len = 1;
+
+  sstrncpy(vl.plugin, g_plugin_name, sizeof(vl.plugin));
+  if (info->node) {
+    ssnprintf(vl.plugin_instance, sizeof(vl.plugin_instance), "%s-%zuKb",
+              info->node, info->page_size_kb);
+  } else {
+    ssnprintf(vl.plugin_instance, sizeof(vl.plugin_instance), "%zuKb",
+              info->page_size_kb);
+  }
+
+  /* ensure all metrics have the same timestamp */
+  vl.time = cdtime();
+
+  gauge_t free = info->free;
+  gauge_t used = (info->nr + info->surplus) - info->free;
+
+  if (g_values_pages) {
+    sstrncpy(vl.type, "vmpage_number", sizeof(vl.type));
+    plugin_dispatch_multivalue(&vl, /* store_percentage = */ 0, DS_TYPE_GAUGE,
+                               "free", free, "used", used, NULL);
+  }
+  if (g_values_bytes) {
+    gauge_t page_size = (gauge_t)(1024 * info->page_size_kb);
+    sstrncpy(vl.type, "memory", sizeof(vl.type));
+    plugin_dispatch_multivalue(&vl, /* store_percentage = */ 0, DS_TYPE_GAUGE,
+                               "free", free * page_size, "used",
+                               used * page_size, NULL);
+  }
+  if (g_values_percent) {
+    sstrncpy(vl.type, "percent", sizeof(vl.type));
+    plugin_dispatch_multivalue(&vl, /* store_percentage = */ 1, DS_TYPE_GAUGE,
+                               "free", free, "used", used, NULL);
+  }
+}
+
+static int read_hugepage_entry(const char *path, const char *entry,
+                               void *e_info) {
+  char path2[PATH_MAX];
+  struct entry_info *info = e_info;
+  double value;
+
+  ssnprintf(path2, sizeof(path2), "%s/%s", path, entry);
+
+  FILE *fh = fopen(path2, "rt");
+  if (fh == NULL) {
+    ERROR("%s: cannot open %s", g_plugin_name, path2);
+    return -1;
+  }
+
+  if (fscanf(fh, "%lf", &value) != 1) {
+    ERROR("%s: cannot parse file %s", g_plugin_name, path2);
+    fclose(fh);
+    return -1;
+  }
+  fclose(fh);
+
+  if (strcmp(entry, "nr_hugepages") == 0) {
+    info->nr = value;
+    info->flags |= HP_HAVE_NR;
+  } else if (strcmp(entry, "surplus_hugepages") == 0) {
+    info->surplus = value;
+    info->flags |= HP_HAVE_SURPLUS;
+  } else if (strcmp(entry, "free_hugepages") == 0) {
+    info->free = value;
+    info->flags |= HP_HAVE_FREE;
+  }
+
+  if (info->flags != HP_HAVE_ALL) {
+    return 0;
+  }
+
+  submit_hp(info);
+
+  /* Reset flags so subsequent calls don't submit again. */
+  info->flags = 0;
+  return 0;
+}
+
+static int read_syshugepages(const char *path, const char *node) {
+  static const char hugepages_dir[] = "hugepages-";
+  DIR *dir;
+  struct dirent *result;
+  char path2[PATH_MAX];
+
+  dir = opendir(path);
+  if (dir == NULL) {
+    ERROR("%s: cannot open directory %s", g_plugin_name, path);
+    return -1;
+  }
+
+  /* read "hugepages-XXXXXkB" entries */
+  while ((result = readdir(dir)) != NULL) {
+    if (strncmp(result->d_name, hugepages_dir, sizeof(hugepages_dir) - 1)) {
+      /* not node dir */
+      errno = 0;
+      continue;
+    }
+
+    long page_size = strtol(result->d_name + strlen(hugepages_dir),
+                            /* endptr = */ NULL, /* base = */ 10);
+    if (errno != 0) {
+      char errbuf[1024];
+      ERROR("%s: failed to determine page size from directory name \"%s\": %s",
+            g_plugin_name, result->d_name,
+            sstrerror(errno, errbuf, sizeof(errbuf)));
+      continue;
+    }
+
+    /* /sys/devices/system/node/node?/hugepages/ */
+    ssnprintf(path2, sizeof(path2), "%s/%s", path, result->d_name);
+
+    walk_directory(path2, read_hugepage_entry,
+                   &(struct entry_info){
+                       .d_name = result->d_name,
+                       .node = node,
+                       .page_size_kb = (size_t)page_size,
+                   },
+                   /* hidden = */ 0);
+    errno = 0;
+  }
+
+  /* Check if NULL return from readdir() was an error */
+  if (errno != 0) {
+    ERROR("%s: readdir failed", g_plugin_name);
+    closedir(dir);
+    return -1;
+  }
+
+  closedir(dir);
+  return 0;
+}
+
+static int read_nodes(void) {
+  static const char sys_node[] = "/sys/devices/system/node";
+  static const char node_string[] = "node";
+  static const char sys_node_hugepages[] =
+      "/sys/devices/system/node/%s/hugepages";
+  DIR *dir;
+  struct dirent *result;
+  char path[PATH_MAX];
+
+  dir = opendir(sys_node);
+  if (dir == NULL) {
+    ERROR("%s: cannot open directory %s", g_plugin_name, sys_node);
+    return -1;
+  }
+
+  while ((result = readdir(dir)) != NULL) {
+    if (strncmp(result->d_name, node_string, sizeof(node_string) - 1)) {
+      /* not node dir */
+      errno = 0;
+      continue;
+    }
+
+    ssnprintf(path, sizeof(path), sys_node_hugepages, result->d_name);
+    read_syshugepages(path, result->d_name);
+    errno = 0;
+  }
+
+  /* Check if NULL return from readdir() was an error */
+  if (errno != 0) {
+    ERROR("%s: readdir failed", g_plugin_name);
+    closedir(dir);
+    return -1;
+  }
+
+  closedir(dir);
+  return 0;
+}
+
+static int huge_read(void) {
+  static const char sys_mm_hugepages[] = "/sys/kernel/mm/hugepages";
+
+  if (g_flag_rpt_mm) {
+    if (read_syshugepages(sys_mm_hugepages, "mm") != 0) {
+      return -1;
+    }
+  }
+  if (g_flag_rpt_numa) {
+    if (read_nodes() != 0) {
+      return -1;
+    }
+  }
+
+  return 0;
+}
+
+void module_register(void) {
+  plugin_register_complex_config(g_plugin_name, hp_config);
+  plugin_register_read(g_plugin_name, huge_read);
+}
diff --git a/src/intel_rdt.c b/src/intel_rdt.c
new file mode 100644 (file)
index 0000000..6f157b8
--- /dev/null
@@ -0,0 +1,692 @@
+/**
+ * collectd - src/intel_rdt.c
+ *
+ * Copyright(c) 2016 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 <serhiyx.pshyk@intel.com>
+ **/
+
+#include "common.h"
+#include "collectd.h"
+
+#include <pqos.h>
+
+#define RDT_PLUGIN "intel_rdt"
+
+#define RDT_MAX_SOCKETS 8
+#define RDT_MAX_SOCKET_CORES 64
+#define RDT_MAX_CORES (RDT_MAX_SOCKET_CORES * RDT_MAX_SOCKETS)
+
+typedef enum {
+  UNKNOWN = 0,
+  CONFIGURATION_ERROR,
+} rdt_config_status;
+
+struct rdt_core_group_s {
+  char *desc;
+  size_t num_cores;
+  unsigned *cores;
+  enum pqos_mon_event events;
+};
+typedef struct rdt_core_group_s rdt_core_group_t;
+
+struct rdt_ctx_s {
+  rdt_core_group_t cgroups[RDT_MAX_CORES];
+  struct pqos_mon_data *pgroups[RDT_MAX_CORES];
+  size_t num_groups;
+  const struct pqos_cpuinfo *pqos_cpu;
+  const struct pqos_cap *pqos_cap;
+  const struct pqos_capability *cap_mon;
+};
+typedef struct rdt_ctx_s rdt_ctx_t;
+
+static rdt_ctx_t *g_rdt = NULL;
+
+static rdt_config_status g_state = UNKNOWN;
+
+static int isdup(const uint64_t *nums, size_t size, uint64_t val) {
+  for (size_t i = 0; i < size; i++)
+    if (nums[i] == val)
+      return 1;
+  return 0;
+}
+
+static int strtouint64(const char *s, uint64_t *n) {
+  char *endptr = NULL;
+
+  assert(s != NULL);
+  assert(n != NULL);
+
+  *n = strtoull(s, &endptr, 0);
+
+  if (!(*s != '\0' && *endptr == '\0')) {
+    DEBUG(RDT_PLUGIN ": Error converting '%s' to unsigned number.", s);
+    return (-EINVAL);
+  }
+
+  return (0);
+}
+
+/*
+ * NAME
+ *   strlisttonums
+ *
+ * DESCRIPTION
+ *   Converts string of characters representing list of numbers into array of
+ *   numbers. Allowed formats are:
+ *     0,1,2,3
+ *     0-10,20-18
+ *     1,3,5-8,10,0x10-12
+ *
+ *   Numbers can be in decimal or hexadecimal format.
+ *
+ * PARAMETERS
+ *   `s'         String representing list of unsigned numbers.
+ *   `nums'      Array to put converted numeric values into.
+ *   `max'       Maximum number of elements that nums can accommodate.
+ *
+ * RETURN VALUE
+ *    Number of elements placed into nums.
+ */
+static size_t strlisttonums(char *s, uint64_t *nums, size_t max) {
+  int ret;
+  size_t index = 0;
+  char *saveptr = NULL;
+
+  if (s == NULL || nums == NULL || max == 0)
+    return index;
+
+  for (;;) {
+    char *p = NULL;
+    char *token = NULL;
+
+    token = strtok_r(s, ",", &saveptr);
+    if (token == NULL)
+      break;
+
+    s = NULL;
+
+    while (isspace(*token))
+      token++;
+    if (*token == '\0')
+      continue;
+
+    p = strchr(token, '-');
+    if (p != NULL) {
+      uint64_t n, start, end;
+      *p = '\0';
+      ret = strtouint64(token, &start);
+      if (ret < 0)
+        return (0);
+      ret = strtouint64(p + 1, &end);
+      if (ret < 0)
+        return (0);
+      if (start > end) {
+        return (0);
+      }
+      for (n = start; n <= end; n++) {
+        if (!(isdup(nums, index, n))) {
+          nums[index] = n;
+          index++;
+        }
+        if (index >= max)
+          return index;
+      }
+    } else {
+      uint64_t val;
+
+      ret = strtouint64(token, &val);
+      if (ret < 0)
+        return (0);
+
+      if (!(isdup(nums, index, val))) {
+        nums[index] = val;
+        index++;
+      }
+      if (index >= max)
+        return index;
+    }
+  }
+
+  return index;
+}
+
+/*
+ * NAME
+ *   cgroup_cmp
+ *
+ * DESCRIPTION
+ *   Function to compare cores in 2 core groups.
+ *
+ * PARAMETERS
+ *   `cg_a'      Pointer to core group a.
+ *   `cg_b'      Pointer to core group b.
+ *
+ * RETURN VALUE
+ *    1 if both groups contain the same cores
+ *    0 if none of their cores match
+ *    -1 if some but not all cores match
+ */
+static int cgroup_cmp(const rdt_core_group_t *cg_a,
+                      const rdt_core_group_t *cg_b) {
+  int found = 0;
+
+  assert(cg_a != NULL);
+  assert(cg_b != NULL);
+
+  const int sz_a = cg_a->num_cores;
+  const int sz_b = cg_b->num_cores;
+  const unsigned *tab_a = cg_a->cores;
+  const unsigned *tab_b = cg_b->cores;
+
+  for (int i = 0; i < sz_a; i++) {
+    for (int j = 0; j < sz_b; j++)
+      if (tab_a[i] == tab_b[j])
+        found++;
+  }
+  /* if no cores are the same */
+  if (!found)
+    return 0;
+  /* if group contains same cores */
+  if (sz_a == sz_b && sz_b == found)
+    return 1;
+  /* if not all cores are the same */
+  return -1;
+}
+
+static int cgroup_set(rdt_core_group_t *cg, char *desc, uint64_t *cores,
+                      size_t num_cores) {
+  assert(cg != NULL);
+  assert(desc != NULL);
+  assert(cores != NULL);
+  assert(num_cores > 0);
+
+  cg->cores = calloc(num_cores, sizeof(unsigned));
+  if (cg->cores == NULL) {
+    ERROR(RDT_PLUGIN ": Error allocating core group table");
+    return (-ENOMEM);
+  }
+  cg->num_cores = num_cores;
+  cg->desc = strdup(desc);
+  if (cg->desc == NULL) {
+    ERROR(RDT_PLUGIN ": Error allocating core group description");
+    sfree(cg->cores);
+    return (-ENOMEM);
+  }
+
+  for (size_t i = 0; i < num_cores; i++)
+    cg->cores[i] = (unsigned)cores[i];
+
+  return 0;
+}
+
+/*
+ * NAME
+ *   oconfig_to_cgroups
+ *
+ * DESCRIPTION
+ *   Function to set the descriptions and cores for each core group.
+ *   Takes a config option containing list of strings that are used to set
+ *   core group values.
+ *
+ * PARAMETERS
+ *   `item'        Config option containing core groups.
+ *   `groups'      Table of core groups to set values in.
+ *   `max_groups'  Maximum number of core groups allowed.
+ *   `max_core'    Maximum allowed core value.
+ *
+ * RETURN VALUE
+ *   On success, the number of core groups set up. On error, appropriate
+ *   negative error value.
+ */
+static int oconfig_to_cgroups(oconfig_item_t *item, rdt_core_group_t *groups,
+                              size_t max_groups, uint64_t max_core) {
+  int index = 0;
+
+  assert(groups != NULL);
+  assert(max_groups > 0);
+  assert(item != NULL);
+
+  for (int j = 0; j < item->values_num; j++) {
+    int ret;
+    size_t n;
+    uint64_t cores[RDT_MAX_CORES] = {0};
+    char value[DATA_MAX_NAME_LEN];
+
+    if ((item->values[j].value.string == NULL) ||
+        (strlen(item->values[j].value.string) == 0))
+      continue;
+
+    sstrncpy(value, item->values[j].value.string, sizeof(value));
+
+    n = strlisttonums(value, cores, STATIC_ARRAY_SIZE(cores));
+    if (n == 0) {
+      ERROR(RDT_PLUGIN ": Error parsing core group (%s)",
+            item->values[j].value.string);
+      return (-EINVAL);
+    }
+
+    for (int i = 0; i < n; i++) {
+      if (cores[i] > max_core) {
+        ERROR(RDT_PLUGIN ": Core group (%s) contains invalid core id (%d)",
+              item->values[j].value.string, (int)cores[i]);
+        return (-EINVAL);
+      }
+    }
+
+    /* set core group info */
+    ret = cgroup_set(&groups[index], item->values[j].value.string, cores, n);
+    if (ret < 0)
+      return ret;
+
+    index++;
+
+    if (index >= max_groups) {
+      WARNING(RDT_PLUGIN ": Too many core groups configured");
+      return index;
+    }
+  }
+
+  return index;
+}
+
+#if COLLECT_DEBUG
+static void rdt_dump_cgroups(void) {
+  char cores[RDT_MAX_CORES * 4];
+
+  if (g_rdt == NULL)
+    return;
+
+  DEBUG(RDT_PLUGIN ": Core Groups Dump");
+  DEBUG(RDT_PLUGIN ":  groups count: %zu", g_rdt->num_groups);
+
+  for (int i = 0; i < g_rdt->num_groups; i++) {
+
+    memset(cores, 0, sizeof(cores));
+    for (int j = 0; j < g_rdt->cgroups[i].num_cores; j++) {
+      snprintf(cores + strlen(cores), sizeof(cores) - strlen(cores) - 1, " %d",
+               g_rdt->cgroups[i].cores[j]);
+    }
+
+    DEBUG(RDT_PLUGIN ":  group[%d]:", i);
+    DEBUG(RDT_PLUGIN ":    description: %s", g_rdt->cgroups[i].desc);
+    DEBUG(RDT_PLUGIN ":    cores: %s", cores);
+    DEBUG(RDT_PLUGIN ":    events: 0x%X", g_rdt->cgroups[i].events);
+  }
+
+  return;
+}
+
+static inline double bytes_to_kb(const double bytes) { return bytes / 1024.0; }
+
+static inline double bytes_to_mb(const double bytes) {
+  return bytes / (1024.0 * 1024.0);
+}
+
+static void rdt_dump_data(void) {
+  /*
+   * CORE - monitored group of cores
+   * RMID - Resource Monitoring ID associated with the monitored group
+   * LLC - last level cache occupancy
+   * MBL - local memory bandwidth
+   * MBR - remote memory bandwidth
+   */
+  DEBUG("  CORE     RMID    LLC[KB]   MBL[MB]    MBR[MB]");
+  for (int i = 0; i < g_rdt->num_groups; i++) {
+
+    const struct pqos_event_values *pv = &g_rdt->pgroups[i]->values;
+
+    double llc = bytes_to_kb(pv->llc);
+    double mbr = bytes_to_mb(pv->mbm_remote_delta);
+    double mbl = bytes_to_mb(pv->mbm_local_delta);
+
+    DEBUG(" [%s] %8u %10.1f %10.1f %10.1f", g_rdt->cgroups[i].desc,
+          g_rdt->pgroups[i]->poll_ctx[0].rmid, llc, mbl, mbr);
+  }
+}
+#endif /* COLLECT_DEBUG */
+
+static void rdt_free_cgroups(void) {
+  for (int i = 0; i < RDT_MAX_CORES; i++) {
+    sfree(g_rdt->cgroups[i].desc);
+
+    sfree(g_rdt->cgroups[i].cores);
+    g_rdt->cgroups[i].num_cores = 0;
+
+    sfree(g_rdt->pgroups[i]);
+  }
+}
+
+static int rdt_default_cgroups(void) {
+  int ret;
+
+  /* configure each core in separate group */
+  for (unsigned i = 0; i < g_rdt->pqos_cpu->num_cores; i++) {
+    char desc[DATA_MAX_NAME_LEN];
+    uint64_t core = i;
+
+    ssnprintf(desc, sizeof(desc), "%d", g_rdt->pqos_cpu->cores[i].lcore);
+
+    /* set core group info */
+    ret = cgroup_set(&g_rdt->cgroups[i], desc, &core, 1);
+    if (ret < 0)
+      return ret;
+  }
+
+  return g_rdt->pqos_cpu->num_cores;
+}
+
+static int rdt_config_cgroups(oconfig_item_t *item) {
+  int n = 0;
+  enum pqos_mon_event events = 0;
+
+  if (item == NULL) {
+    DEBUG(RDT_PLUGIN ": cgroups_config: Invalid argument.");
+    return (-EINVAL);
+  }
+
+  DEBUG(RDT_PLUGIN ": Core groups [%d]:", item->values_num);
+  for (int j = 0; j < item->values_num; j++) {
+    if (item->values[j].type != OCONFIG_TYPE_STRING) {
+      ERROR(RDT_PLUGIN ": given core group value is not a string [idx=%d]", j);
+      return (-EINVAL);
+    }
+    DEBUG(RDT_PLUGIN ":  [%d]: %s", j, item->values[j].value.string);
+  }
+
+  n = oconfig_to_cgroups(item, g_rdt->cgroups, RDT_MAX_CORES,
+                         g_rdt->pqos_cpu->num_cores - 1);
+  if (n < 0) {
+    rdt_free_cgroups();
+    ERROR(RDT_PLUGIN ": Error parsing core groups configuration.");
+    return (-EINVAL);
+  }
+
+  if (n == 0) {
+    /* create default core groups if "Cores" config option is empty */
+    n = rdt_default_cgroups();
+    if (n < 0) {
+      rdt_free_cgroups();
+      ERROR(RDT_PLUGIN ": Error creating default core groups configuration.");
+      return n;
+    }
+    INFO(RDT_PLUGIN
+         ": No core groups configured. Default core groups created.");
+  }
+
+  /* Get all available events on this platform */
+  for (int i = 0; i < g_rdt->cap_mon->u.mon->num_events; i++)
+    events |= g_rdt->cap_mon->u.mon->events[i].type;
+
+  events &= ~(PQOS_PERF_EVENT_LLC_MISS);
+
+  DEBUG(RDT_PLUGIN ": Number of cores in the system: %u",
+        g_rdt->pqos_cpu->num_cores);
+  DEBUG(RDT_PLUGIN ": Available events to monitor: %#x", events);
+
+  g_rdt->num_groups = n;
+  for (int i = 0; i < n; i++) {
+    for (int j = 0; j < i; j++) {
+      int found = 0;
+      found = cgroup_cmp(&g_rdt->cgroups[j], &g_rdt->cgroups[i]);
+      if (found != 0) {
+        rdt_free_cgroups();
+        ERROR(RDT_PLUGIN ": Cannot monitor same cores in different groups.");
+        return (-EINVAL);
+      }
+    }
+
+    g_rdt->cgroups[i].events = events;
+    g_rdt->pgroups[i] = calloc(1, sizeof(*g_rdt->pgroups[i]));
+    if (g_rdt->pgroups[i] == NULL) {
+      rdt_free_cgroups();
+      ERROR(RDT_PLUGIN ": Failed to allocate memory for monitoring data.");
+      return (-ENOMEM);
+    }
+  }
+
+  return (0);
+}
+
+static int rdt_preinit(void) {
+  int ret;
+
+  if (g_rdt != NULL) {
+    /* already initialized if config callback was called before init callback */
+    return (0);
+  }
+
+  g_rdt = calloc(1, sizeof(*g_rdt));
+  if (g_rdt == NULL) {
+    ERROR(RDT_PLUGIN ": Failed to allocate memory for rdt context.");
+    return (-ENOMEM);
+  }
+
+  /* In case previous instance of the application was not closed properly
+   * call fini and ignore return code. */
+  pqos_fini();
+
+  /* TODO:
+   * stdout should not be used here. Will be reworked when support of log
+   * callback is added to PQoS library.
+  */
+  ret = pqos_init(&(struct pqos_config){.fd_log = STDOUT_FILENO});
+  if (ret != PQOS_RETVAL_OK) {
+    ERROR(RDT_PLUGIN ": Error initializing PQoS library!");
+    goto rdt_preinit_error1;
+  }
+
+  ret = pqos_cap_get(&g_rdt->pqos_cap, &g_rdt->pqos_cpu);
+  if (ret != PQOS_RETVAL_OK) {
+    ERROR(RDT_PLUGIN ": Error retrieving PQoS capabilities.");
+    goto rdt_preinit_error2;
+  }
+
+  ret = pqos_cap_get_type(g_rdt->pqos_cap, PQOS_CAP_TYPE_MON, &g_rdt->cap_mon);
+  if (ret == PQOS_RETVAL_PARAM) {
+    ERROR(RDT_PLUGIN ": Error retrieving monitoring capabilities.");
+    goto rdt_preinit_error2;
+  }
+
+  if (g_rdt->cap_mon == NULL) {
+    ERROR(
+        RDT_PLUGIN
+        ": Monitoring capability not detected. Nothing to do for the plugin.");
+    goto rdt_preinit_error2;
+  }
+
+  return (0);
+
+rdt_preinit_error2:
+  pqos_fini();
+
+rdt_preinit_error1:
+
+  sfree(g_rdt);
+
+  return (-1);
+}
+
+static int rdt_config(oconfig_item_t *ci) {
+  if (rdt_preinit() != 0) {
+    g_state = CONFIGURATION_ERROR;
+    /* if we return -1 at this point collectd
+      reports a failure in configuration and
+      aborts
+    */
+    return (0);
+  }
+
+  for (int i = 0; i < ci->children_num; i++) {
+    oconfig_item_t *child = ci->children + i;
+
+    if (strcasecmp("Cores", child->key) == 0) {
+      if (rdt_config_cgroups(child) != 0) {
+        g_state = CONFIGURATION_ERROR;
+        /* if we return -1 at this point collectd
+           reports a failure in configuration and
+           aborts
+         */
+       return (0);
+      }
+
+#if COLLECT_DEBUG
+      rdt_dump_cgroups();
+#endif /* COLLECT_DEBUG */
+    } else {
+      ERROR(RDT_PLUGIN ": Unknown configuration parameter \"%s\".", child->key);
+    }
+  }
+
+  return (0);
+}
+
+static void rdt_submit_derive(char *cgroup, char *type, char *type_instance,
+                              derive_t value) {
+  value_list_t vl = VALUE_LIST_INIT;
+
+  vl.values = &(value_t){.derive = value};
+  vl.values_len = 1;
+
+  sstrncpy(vl.plugin, RDT_PLUGIN, sizeof(vl.plugin));
+  snprintf(vl.plugin_instance, sizeof(vl.plugin_instance), "%s", cgroup);
+  sstrncpy(vl.type, type, sizeof(vl.type));
+  if (type_instance)
+    sstrncpy(vl.type_instance, type_instance, sizeof(vl.type_instance));
+
+  plugin_dispatch_values(&vl);
+}
+
+static void rdt_submit_gauge(char *cgroup, char *type, char *type_instance,
+                             gauge_t value) {
+  value_list_t vl = VALUE_LIST_INIT;
+
+  vl.values = &(value_t){.gauge = value};
+  vl.values_len = 1;
+
+  sstrncpy(vl.plugin, RDT_PLUGIN, sizeof(vl.plugin));
+  snprintf(vl.plugin_instance, sizeof(vl.plugin_instance), "%s", cgroup);
+  sstrncpy(vl.type, type, sizeof(vl.type));
+  if (type_instance)
+    sstrncpy(vl.type_instance, type_instance, sizeof(vl.type_instance));
+
+  plugin_dispatch_values(&vl);
+}
+
+static int rdt_read(__attribute__((unused)) user_data_t *ud) {
+  int ret;
+
+  if (g_rdt == NULL) {
+    ERROR(RDT_PLUGIN ": rdt_read: plugin not initialized.");
+    return (-EINVAL);
+  }
+
+  ret = pqos_mon_poll(&g_rdt->pgroups[0], (unsigned)g_rdt->num_groups);
+  if (ret != PQOS_RETVAL_OK) {
+    ERROR(RDT_PLUGIN ": Failed to poll monitoring data.");
+    return (-1);
+  }
+
+#if COLLECT_DEBUG
+  rdt_dump_data();
+#endif /* COLLECT_DEBUG */
+
+  for (int i = 0; i < g_rdt->num_groups; i++) {
+    enum pqos_mon_event mbm_events =
+        (PQOS_MON_EVENT_LMEM_BW | PQOS_MON_EVENT_TMEM_BW |
+         PQOS_MON_EVENT_RMEM_BW);
+
+    const struct pqos_event_values *pv = &g_rdt->pgroups[i]->values;
+
+    /* Submit only monitored events data */
+
+    if (g_rdt->cgroups[i].events & PQOS_MON_EVENT_L3_OCCUP)
+      rdt_submit_gauge(g_rdt->cgroups[i].desc, "bytes", "llc", pv->llc);
+
+    if (g_rdt->cgroups[i].events & PQOS_PERF_EVENT_IPC)
+      rdt_submit_gauge(g_rdt->cgroups[i].desc, "ipc", NULL, pv->ipc);
+
+    if (g_rdt->cgroups[i].events & mbm_events) {
+      rdt_submit_derive(g_rdt->cgroups[i].desc, "memory_bandwidth", "local",
+                        pv->mbm_local_delta);
+      rdt_submit_derive(g_rdt->cgroups[i].desc, "memory_bandwidth", "remote",
+                        pv->mbm_remote_delta);
+    }
+  }
+
+  return (0);
+}
+
+static int rdt_init(void) {
+  int ret;
+
+  if(g_state == CONFIGURATION_ERROR)
+    return (-1);
+
+  ret = rdt_preinit();
+  if (ret != 0)
+    return ret;
+
+  /* Start monitoring */
+  for (int i = 0; i < g_rdt->num_groups; i++) {
+    rdt_core_group_t *cg = &g_rdt->cgroups[i];
+
+    ret = pqos_mon_start(cg->num_cores, cg->cores, cg->events, (void *)cg->desc,
+                         g_rdt->pgroups[i]);
+
+    if (ret != PQOS_RETVAL_OK)
+      ERROR(RDT_PLUGIN ": Error starting monitoring group %s (pqos status=%d)",
+            cg->desc, ret);
+  }
+
+  return (0);
+}
+
+static int rdt_shutdown(void) {
+  int ret;
+
+  DEBUG(RDT_PLUGIN ": rdt_shutdown.");
+
+  if (g_rdt == NULL)
+    return (0);
+
+  /* Stop monitoring */
+  for (int i = 0; i < g_rdt->num_groups; i++) {
+    pqos_mon_stop(g_rdt->pgroups[i]);
+  }
+
+  ret = pqos_fini();
+  if (ret != PQOS_RETVAL_OK)
+    ERROR(RDT_PLUGIN ": Error shutting down PQoS library.");
+
+  rdt_free_cgroups();
+  sfree(g_rdt);
+
+  return (0);
+}
+
+void module_register(void) {
+  plugin_register_init(RDT_PLUGIN, rdt_init);
+  plugin_register_complex_config(RDT_PLUGIN, rdt_config);
+  plugin_register_complex_read(NULL, RDT_PLUGIN, rdt_read, 0, NULL);
+  plugin_register_shutdown(RDT_PLUGIN, rdt_shutdown);
+}
index b8cc48d..b7c6f90 100644 (file)
@@ -155,18 +155,16 @@ static int interface_init(void) {
 
 static void if_submit(const char *dev, const char *type, derive_t rx,
                       derive_t tx) {
-  value_t values[2];
   value_list_t vl = VALUE_LIST_INIT;
+  value_t values[] = {
+      {.derive = rx}, {.derive = tx},
+  };
 
   if (ignorelist_match(ignorelist, dev) != 0)
     return;
 
-  values[0].derive = rx;
-  values[1].derive = tx;
-
   vl.values = values;
-  vl.values_len = 2;
-  sstrncpy(vl.host, hostname_g, sizeof(vl.host));
+  vl.values_len = STATIC_ARRAY_SIZE(values);
   sstrncpy(vl.plugin, "interface", sizeof(vl.plugin));
   sstrncpy(vl.plugin_instance, dev, sizeof(vl.plugin_instance));
   sstrncpy(vl.type, type, sizeof(vl.type));
index d06b0e3..c9fb003 100644 (file)
--- a/src/ipc.c
+++ b/src/ipc.c
@@ -95,14 +95,10 @@ __attribute__((nonnull(1))) static void
 ipc_submit_g(const char *plugin_instance, const char *type,
              const char *type_instance, gauge_t value) /* {{{ */
 {
-  value_t values[1];
   value_list_t vl = VALUE_LIST_INIT;
 
-  values[0].gauge = value;
-
-  vl.values = values;
+  vl.values = &(value_t){.gauge = value};
   vl.values_len = 1;
-  sstrncpy(vl.host, hostname_g, sizeof(vl.host));
   sstrncpy(vl.plugin, "ipc", sizeof(vl.plugin));
   sstrncpy(vl.plugin_instance, plugin_instance, sizeof(vl.plugin_instance));
   sstrncpy(vl.type, type, sizeof(vl.type));
index 1d431b7..1bbaf41 100644 (file)
@@ -102,7 +102,6 @@ static void sensor_read_handler(ipmi_sensor_t *sensor, int err,
                                 double value,
                                 ipmi_states_t __attribute__((unused)) * states,
                                 void *user_data) {
-  value_t values[1];
   value_list_t vl = VALUE_LIST_INIT;
 
   c_ipmi_sensor_list_t *list_item = (c_ipmi_sensor_list_t *)user_data;
@@ -191,12 +190,9 @@ static void sensor_read_handler(ipmi_sensor_t *sensor, int err,
     return;
   }
 
-  values[0].gauge = value;
-
-  vl.values = values;
+  vl.values = &(value_t){.gauge = value};
   vl.values_len = 1;
 
-  sstrncpy(vl.host, hostname_g, 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));
@@ -579,7 +575,7 @@ static int c_ipmi_init(void) {
   c_ipmi_active = 1;
 
   status = plugin_thread_create(&thread_id, /* attr = */ NULL, thread_main,
-                                /* user data = */ NULL);
+                                /* user data = */ NULL, "ipmi");
   if (status != 0) {
     c_ipmi_active = 0;
     thread_id = (pthread_t)0;
index f35b5d6..bf17cfb 100644 (file)
@@ -210,7 +210,6 @@ static int submit6_match(const struct ip6t_entry_match *match,
                          const struct ip6t_entry *entry,
                          const ip_chain_t *chain, int rule_num) {
   int status;
-  value_t values[1];
   value_list_t vl = VALUE_LIST_INIT;
 
   /* Select the rules to collect */
@@ -225,9 +224,6 @@ static int submit6_match(const struct ip6t_entry_match *match,
       return (0);
   }
 
-  vl.values = values;
-  vl.values_len = 1;
-  sstrncpy(vl.host, hostname_g, sizeof(vl.host));
   sstrncpy(vl.plugin, "ip6tables", sizeof(vl.plugin));
 
   status = ssnprintf(vl.plugin_instance, sizeof(vl.plugin_instance), "%s-%s",
@@ -246,22 +242,22 @@ static int submit6_match(const struct ip6t_entry_match *match,
   }
 
   sstrncpy(vl.type, "ipt_bytes", sizeof(vl.type));
-  values[0].derive = (derive_t)entry->counters.bcnt;
+  vl.values = &(value_t){.derive = (derive_t)entry->counters.bcnt};
+  vl.values_len = 1;
   plugin_dispatch_values(&vl);
 
   sstrncpy(vl.type, "ipt_packets", sizeof(vl.type));
-  values[0].derive = (derive_t)entry->counters.pcnt;
+  vl.values = &(value_t){.derive = (derive_t)entry->counters.pcnt};
   plugin_dispatch_values(&vl);
 
   return (0);
-} /* int submit_match */
+} /* int submit6_match */
 
 /* This needs to return `int' for IPT_MATCH_ITERATE to work. */
 static int submit_match(const struct ipt_entry_match *match,
                         const struct ipt_entry *entry, const ip_chain_t *chain,
                         int rule_num) {
   int status;
-  value_t values[1];
   value_list_t vl = VALUE_LIST_INIT;
 
   /* Select the rules to collect */
@@ -276,9 +272,6 @@ static int submit_match(const struct ipt_entry_match *match,
       return (0);
   }
 
-  vl.values = values;
-  vl.values_len = 1;
-  sstrncpy(vl.host, hostname_g, sizeof(vl.host));
   sstrncpy(vl.plugin, "iptables", sizeof(vl.plugin));
 
   status = ssnprintf(vl.plugin_instance, sizeof(vl.plugin_instance), "%s-%s",
@@ -297,11 +290,12 @@ static int submit_match(const struct ipt_entry_match *match,
   }
 
   sstrncpy(vl.type, "ipt_bytes", sizeof(vl.type));
-  values[0].derive = (derive_t)entry->counters.bcnt;
+  vl.values = &(value_t){.derive = (derive_t)entry->counters.bcnt};
+  vl.values_len = 1;
   plugin_dispatch_values(&vl);
 
   sstrncpy(vl.type, "ipt_packets", sizeof(vl.type));
-  values[0].derive = (derive_t)entry->counters.pcnt;
+  vl.values = &(value_t){.derive = (derive_t)entry->counters.pcnt};
   plugin_dispatch_values(&vl);
 
   return (0);
index 3b96332..2446bd1 100644 (file)
@@ -220,15 +220,11 @@ static int get_ti(struct ip_vs_dest_entry *de, char *ti, size_t size) {
 
 static void cipvs_submit_connections(const char *pi, const char *ti,
                                      derive_t value) {
-  value_t values[1];
   value_list_t vl = VALUE_LIST_INIT;
 
-  values[0].derive = value;
-
-  vl.values = values;
+  vl.values = &(value_t){.derive = value};
   vl.values_len = 1;
 
-  sstrncpy(vl.host, hostname_g, sizeof(vl.host));
   sstrncpy(vl.plugin, "ipvs", sizeof(vl.plugin));
   sstrncpy(vl.plugin_instance, pi, sizeof(vl.plugin_instance));
   sstrncpy(vl.type, "connections", sizeof(vl.type));
@@ -241,16 +237,14 @@ static void cipvs_submit_connections(const char *pi, const char *ti,
 
 static void cipvs_submit_if(const char *pi, const char *t, const char *ti,
                             derive_t rx, derive_t tx) {
-  value_t values[2];
+  value_t values[] = {
+      {.derive = rx}, {.derive = tx},
+  };
   value_list_t vl = VALUE_LIST_INIT;
 
-  values[0].derive = rx;
-  values[1].derive = tx;
-
   vl.values = values;
-  vl.values_len = 2;
+  vl.values_len = STATIC_ARRAY_SIZE(values);
 
-  sstrncpy(vl.host, hostname_g, sizeof(vl.host));
   sstrncpy(vl.plugin, "ipvs", sizeof(vl.plugin));
   sstrncpy(vl.plugin_instance, pi, sizeof(vl.plugin_instance));
   sstrncpy(vl.type, t, sizeof(vl.type));
index ea29261..371ba64 100644 (file)
--- a/src/irq.c
+++ b/src/irq.c
@@ -61,17 +61,13 @@ static int irq_config(const char *key, const char *value) {
 }
 
 static void irq_submit(const char *irq_name, derive_t value) {
-  value_t values[1];
   value_list_t vl = VALUE_LIST_INIT;
 
   if (ignorelist_match(ignorelist, irq_name) != 0)
     return;
 
-  values[0].derive = value;
-
-  vl.values = values;
+  vl.values = &(value_t){.derive = value};
   vl.values_len = 1;
-  sstrncpy(vl.host, hostname_g, sizeof(vl.host));
   sstrncpy(vl.plugin, "irq", sizeof(vl.plugin));
   sstrncpy(vl.type, "irq", sizeof(vl.type));
   sstrncpy(vl.type_instance, irq_name, sizeof(vl.type_instance));
index 0a9bb9e..63b5e31 100644 (file)
@@ -1304,10 +1304,12 @@ static jint JNICALL cjni_api_register_read(JNIEnv *jvm_env, /* {{{ */
 
   DEBUG("java plugin: Registering new read callback: %s", cbi->name);
 
-  user_data_t ud = {.data = cbi, .free_func = cjni_callback_info_destroy};
-
-  plugin_register_complex_read(/* group = */ NULL, cbi->name, cjni_read,
-                               /* interval = */ 0, &ud);
+  plugin_register_complex_read(
+      /* group = */ NULL, cbi->name, cjni_read,
+      /* interval = */ 0,
+      &(user_data_t){
+          .data = cbi, .free_func = cjni_callback_info_destroy,
+      });
 
   (*jvm_env)->DeleteLocalRef(jvm_env, o_read);
 
@@ -1325,9 +1327,11 @@ static jint JNICALL cjni_api_register_write(JNIEnv *jvm_env, /* {{{ */
 
   DEBUG("java plugin: Registering new write callback: %s", cbi->name);
 
-  user_data_t ud = {.data = cbi, .free_func = cjni_callback_info_destroy};
-
-  plugin_register_write(cbi->name, cjni_write, &ud);
+  plugin_register_write(
+      cbi->name, cjni_write,
+      &(user_data_t){
+          .data = cbi, .free_func = cjni_callback_info_destroy,
+      });
 
   (*jvm_env)->DeleteLocalRef(jvm_env, o_write);
 
@@ -1345,9 +1349,11 @@ static jint JNICALL cjni_api_register_flush(JNIEnv *jvm_env, /* {{{ */
 
   DEBUG("java plugin: Registering new flush callback: %s", cbi->name);
 
-  user_data_t ud = {.data = cbi, .free_func = cjni_callback_info_destroy};
-
-  plugin_register_flush(cbi->name, cjni_flush, &ud);
+  plugin_register_flush(
+      cbi->name, cjni_flush,
+      &(user_data_t){
+          .data = cbi, .free_func = cjni_callback_info_destroy,
+      });
 
   (*jvm_env)->DeleteLocalRef(jvm_env, o_flush);
 
@@ -1372,9 +1378,10 @@ static jint JNICALL cjni_api_register_log(JNIEnv *jvm_env, /* {{{ */
 
   DEBUG("java plugin: Registering new log callback: %s", cbi->name);
 
-  user_data_t ud = {.data = cbi, .free_func = cjni_callback_info_destroy};
-
-  plugin_register_log(cbi->name, cjni_log, &ud);
+  plugin_register_log(cbi->name, cjni_log,
+                      &(user_data_t){
+                          .data = cbi, .free_func = cjni_callback_info_destroy,
+                      });
 
   (*jvm_env)->DeleteLocalRef(jvm_env, o_log);
 
@@ -1393,9 +1400,11 @@ static jint JNICALL cjni_api_register_notification(JNIEnv *jvm_env, /* {{{ */
 
   DEBUG("java plugin: Registering new notification callback: %s", cbi->name);
 
-  user_data_t ud = {.data = cbi, .free_func = cjni_callback_info_destroy};
-
-  plugin_register_notification(cbi->name, cjni_notification, &ud);
+  plugin_register_notification(
+      cbi->name, cjni_notification,
+      &(user_data_t){
+          .data = cbi, .free_func = cjni_callback_info_destroy,
+      });
 
   (*jvm_env)->DeleteLocalRef(jvm_env, o_notification);
 
index 585b1f9..c738860 100644 (file)
@@ -72,8 +72,6 @@ static int load_config(const char *key, const char *value) {
   return (-1);
 }
 static void load_submit(gauge_t snum, gauge_t mnum, gauge_t lnum) {
-  value_t values[3];
-  value_list_t vl = VALUE_LIST_INIT;
   int cores = 0;
   char errbuf[1024];
 
@@ -91,14 +89,14 @@ static void load_submit(gauge_t snum, gauge_t mnum, gauge_t lnum) {
     lnum /= cores;
   }
 
-  values[0].gauge = snum;
-  values[1].gauge = mnum;
-  values[2].gauge = lnum;
+  value_list_t vl = VALUE_LIST_INIT;
+  value_t values[] = {
+      {.gauge = snum}, {.gauge = mnum}, {.gauge = lnum},
+  };
 
   vl.values = values;
   vl.values_len = STATIC_ARRAY_SIZE(values);
 
-  sstrncpy(vl.host, hostname_g, sizeof(vl.host));
   sstrncpy(vl.plugin, "load", sizeof(vl.plugin));
   sstrncpy(vl.type, "load", sizeof(vl.type));
 
index 3ef7139..8df4313 100644 (file)
@@ -79,7 +79,6 @@ static void log_logstash_print(yajl_gen g, int severity,
   struct tm timestamp_tm;
   char timestamp_str[64];
   const unsigned char *buf;
-  time_t tt;
 #if HAVE_YAJL_V2
   size_t len;
 #else
@@ -127,8 +126,7 @@ static void log_logstash_print(yajl_gen g, int severity,
       yajl_gen_status_ok)
     goto err;
 
-  tt = CDTIME_T_TO_TIME_T(timestamp_time);
-  gmtime_r(&tt, &timestamp_tm);
+  gmtime_r(&CDTIME_T_TO_TIME_T(timestamp_time), &timestamp_tm);
 
   /*
    * format time as a UTC ISO 8601 compliant string
index ef3aa97..6629c0b 100644 (file)
@@ -78,7 +78,6 @@ static void logfile_print(const char *msg, int severity,
                           cdtime_t timestamp_time) {
   FILE *fh;
   _Bool do_close = 0;
-  struct tm timestamp_tm;
   char timestamp_str[64];
   char level_str[16] = "";
 
@@ -105,8 +104,8 @@ static void logfile_print(const char *msg, int severity,
   }
 
   if (print_timestamp) {
-    time_t tt = CDTIME_T_TO_TIME_T(timestamp_time);
-    localtime_r(&tt, &timestamp_tm);
+    struct tm timestamp_tm;
+    localtime_r(&CDTIME_T_TO_TIME_T(timestamp_time), &timestamp_tm);
 
     strftime(timestamp_str, sizeof(timestamp_str), "%Y-%m-%d %H:%M:%S",
              &timestamp_tm);
index 412a721..df4424c 100644 (file)
@@ -99,18 +99,13 @@ static int lpar_init(void) {
 } /* int lpar_init */
 
 static void lpar_submit(const char *type_instance, double value) {
-  value_t values[1];
   value_list_t vl = VALUE_LIST_INIT;
 
-  values[0].gauge = (gauge_t)value;
-
-  vl.values = values;
+  vl.values = &(value_t){.gauge = value};
   vl.values_len = 1;
   if (report_by_serial) {
     sstrncpy(vl.host, serial, sizeof(vl.host));
     sstrncpy(vl.plugin_instance, hostname_g, sizeof(vl.plugin));
-  } else {
-    sstrncpy(vl.host, hostname_g, sizeof(vl.host));
   }
   sstrncpy(vl.plugin, "lpar", sizeof(vl.plugin));
   sstrncpy(vl.type, "vcpu", sizeof(vl.type));
index 1bcb2e1..2bd56a1 100644 (file)
--- a/src/lua.c
+++ b/src/lua.c
@@ -303,13 +303,12 @@ static int lua_cb_register_read(lua_State *L) /* {{{ */
   cb->lua_function_name = strdup(function_name);
   pthread_mutex_init(&cb->lock, NULL);
 
-  user_data_t ud = {.data = cb};
-
   int status = plugin_register_complex_read(/* group = */ "lua",
                                             /* name      = */ function_name,
                                             /* callback  = */ clua_read,
-                                            /* interval  = */ 0,
-                                            /* user_data = */ &ud);
+                                            /* interval  = */ 0, &(user_data_t){
+                                                                     .data = cb,
+                                                                 });
 
   if (status != 0)
     return luaL_error(L, "%s", "plugin_register_complex_read failed");
@@ -347,11 +346,11 @@ static int lua_cb_register_write(lua_State *L) /* {{{ */
   cb->lua_function_name = strdup(function_name);
   pthread_mutex_init(&cb->lock, NULL);
 
-  user_data_t ud = {.data = cb};
-
-  int status = plugin_register_write(/* name = */ function_name,
-                                     /* callback  = */ clua_write,
-                                     /* user_data = */ &ud);
+  int status =
+      plugin_register_write(/* name = */ function_name,
+                            /* callback  = */ clua_write, &(user_data_t){
+                                                              .data = cb,
+                                                          });
 
   if (status != 0)
     return luaL_error(L, "%s", "plugin_register_write failed");
index f6ca577..3a84a6d 100644 (file)
--- a/src/lvm.c
+++ b/src/lvm.c
@@ -18,7 +18,7 @@
  *
  * Authors:
  *   Chad Malfait <malfaitc at yahoo.com>
- *   Benjamin Gilbert <bgilbert at cs.cmu.edu>
+ *   Benjamin Gilbert <bgilbert at backtick.net>
  **/
 
 #include <lvm2app.h>
@@ -52,15 +52,11 @@ static char const *get_lv_property_string(lv_t lv, char const *property) {
 
 static void lvm_submit(char const *plugin_instance, char const *type_instance,
                        uint64_t ivalue) {
-  value_t v;
   value_list_t vl = VALUE_LIST_INIT;
 
-  v.gauge = (gauge_t)ivalue;
-
-  vl.values = &v;
+  vl.values = &(value_t){.gauge = (gauge_t)ivalue};
   vl.values_len = 1;
 
-  sstrncpy(vl.host, hostname_g, sizeof(vl.host));
   sstrncpy(vl.plugin, "lvm", sizeof(vl.plugin));
   sstrncpy(vl.plugin_instance, plugin_instance, sizeof(vl.plugin_instance));
   sstrncpy(vl.type, "df_complex", sizeof(vl.type));
index 406c1c7..99e4224 100644 (file)
@@ -509,12 +509,11 @@ static int madwifi_config(const char *key, const char *value) {
 }
 
 static void submit(const char *dev, const char *type, const char *ti1,
-                   const char *ti2, value_t *val, int len) {
+                   const char *ti2, value_t *val, size_t len) {
   value_list_t vl = VALUE_LIST_INIT;
 
   vl.values = val;
   vl.values_len = len;
-  sstrncpy(vl.host, hostname_g, sizeof(vl.host));
   sstrncpy(vl.plugin, "madwifi", sizeof(vl.plugin));
   sstrncpy(vl.plugin_instance, dev, sizeof(vl.plugin_instance));
   sstrncpy(vl.type, type, sizeof(vl.type));
@@ -528,25 +527,22 @@ static void submit(const char *dev, const char *type, const char *ti1,
 }
 
 static void submit_derive(const char *dev, const char *type, const char *ti1,
-                          const char *ti2, derive_t val) {
-  value_t item;
-  item.derive = val;
-  submit(dev, type, ti1, ti2, &item, 1);
+                          const char *ti2, derive_t value) {
+  submit(dev, type, ti1, ti2, &(value_t){.derive = value}, 1);
 }
 
 static void submit_derive2(const char *dev, const char *type, const char *ti1,
                            const char *ti2, derive_t val1, derive_t val2) {
-  value_t items[2];
-  items[0].derive = val1;
-  items[1].derive = val2;
-  submit(dev, type, ti1, ti2, items, 2);
+  value_t values[] = {
+      {.derive = val1}, {.derive = val2},
+  };
+
+  submit(dev, type, ti1, ti2, values, STATIC_ARRAY_SIZE(values));
 }
 
 static void submit_gauge(const char *dev, const char *type, const char *ti1,
-                         const char *ti2, gauge_t val) {
-  value_t item;
-  item.gauge = val;
-  submit(dev, type, ti1, ti2, &item, 1);
+                         const char *ti2, gauge_t value) {
+  submit(dev, type, ti1, ti2, &(value_t){.gauge = value}, 1);
 }
 
 static void submit_antx(const char *dev, const char *name, u_int32_t *vals,
index 9c70898..209f773 100644 (file)
 
 #include "collectd.h"
 
+#include "common.h"
 #include "filter_chain.h"
+#include "meta_data.h"
+#include "utils_llist.h"
 
 #include <regex.h>
 #include <sys/types.h>
@@ -62,6 +65,7 @@ struct mr_match_s {
   mr_regex_t *plugin_instance;
   mr_regex_t *type;
   mr_regex_t *type_instance;
+  llist_t *meta; /* Maps each meta key into mr_regex_t* */
   _Bool invert;
 };
 
@@ -75,7 +79,7 @@ static void mr_free_regex(mr_regex_t *r) /* {{{ */
 
   regfree(&r->re);
   memset(&r->re, 0, sizeof(r->re));
-  free(r->re_str);
+  sfree(r->re_str);
 
   if (r->next != NULL)
     mr_free_regex(r->next);
@@ -91,8 +95,13 @@ static void mr_free_match(mr_match_t *m) /* {{{ */
   mr_free_regex(m->plugin_instance);
   mr_free_regex(m->type);
   mr_free_regex(m->type_instance);
+  for (llentry_t *e = llist_head(m->meta); e != NULL; e = e->next) {
+    sfree(e->key);
+    mr_free_regex((mr_regex_t *)e->value);
+  }
+  llist_destroy(m->meta);
 
-  free(m);
+  sfree(m);
 } /* }}} void mr_free_match */
 
 static int mr_match_regexen(mr_regex_t *re_head, /* {{{ */
@@ -119,27 +128,22 @@ static int mr_match_regexen(mr_regex_t *re_head, /* {{{ */
   return (FC_MATCH_MATCHES);
 } /* }}} int mr_match_regexen */
 
-static int mr_config_add_regex(mr_regex_t **re_head, /* {{{ */
-                               oconfig_item_t *ci) {
+static int mr_add_regex(mr_regex_t **re_head, const char *re_str, /* {{{ */
+                        const char *option) {
   mr_regex_t *re;
   int status;
 
-  if ((ci->values_num != 1) || (ci->values[0].type != OCONFIG_TYPE_STRING)) {
-    log_warn("`%s' needs exactly one string argument.", ci->key);
-    return (-1);
-  }
-
   re = calloc(1, sizeof(*re));
   if (re == NULL) {
-    log_err("mr_config_add_regex: calloc failed.");
+    log_err("mr_add_regex: calloc failed.");
     return (-1);
   }
   re->next = NULL;
 
-  re->re_str = strdup(ci->values[0].value.string);
+  re->re_str = strdup(re_str);
   if (re->re_str == NULL) {
-    free(re);
-    log_err("mr_config_add_regex: strdup failed.");
+    sfree(re);
+    log_err("mr_add_regex: strdup failed.");
     return (-1);
   }
 
@@ -148,10 +152,10 @@ static int mr_config_add_regex(mr_regex_t **re_head, /* {{{ */
     char errmsg[1024];
     regerror(status, &re->re, errmsg, sizeof(errmsg));
     errmsg[sizeof(errmsg) - 1] = 0;
-    log_err("Compiling regex `%s' for `%s' failed: %s.", re->re_str, ci->key,
+    log_err("Compiling regex `%s' for `%s' failed: %s.", re->re_str, option,
             errmsg);
-    free(re->re_str);
-    free(re);
+    sfree(re->re_str);
+    sfree(re);
     return (-1);
   }
 
@@ -168,8 +172,68 @@ static int mr_config_add_regex(mr_regex_t **re_head, /* {{{ */
   }
 
   return (0);
+} /* }}} int mr_add_regex */
+
+static int mr_config_add_regex(mr_regex_t **re_head, /* {{{ */
+                               oconfig_item_t *ci) {
+  if ((ci->values_num != 1) || (ci->values[0].type != OCONFIG_TYPE_STRING)) {
+    log_warn("`%s' needs exactly one string argument.", ci->key);
+    return (-1);
+  }
+
+  return mr_add_regex(re_head, ci->values[0].value.string, ci->key);
 } /* }}} int mr_config_add_regex */
 
+static int mr_config_add_meta_regex(llist_t **meta, /* {{{ */
+                                    oconfig_item_t *ci) {
+  char *meta_key;
+  llentry_t *entry;
+  mr_regex_t *re_head;
+  int status;
+  char buffer[1024];
+
+  if ((ci->values_num != 2) || (ci->values[0].type != OCONFIG_TYPE_STRING) ||
+      (ci->values[1].type != OCONFIG_TYPE_STRING)) {
+    log_warn("`%s' needs exactly two string arguments.", ci->key);
+    return (-1);
+  }
+
+  if (*meta == NULL) {
+    *meta = llist_create();
+    if (*meta == NULL) {
+      log_err("mr_config_add_meta_regex: llist_create failed.");
+      return (-1);
+    }
+  }
+
+  meta_key = ci->values[0].value.string;
+  entry = llist_search(*meta, meta_key);
+  if (entry == NULL) {
+    meta_key = strdup(meta_key);
+    if (meta_key == NULL) {
+      log_err("mr_config_add_meta_regex: strdup failed.");
+      return (-1);
+    }
+    entry = llentry_create(meta_key, NULL);
+    if (entry == NULL) {
+      log_err("mr_config_add_meta_regex: llentry_create failed.");
+      sfree(meta_key);
+      return (-1);
+    }
+    /* meta_key and entry will now be freed by mr_free_match(). */
+    llist_append(*meta, entry);
+  }
+
+  ssnprintf(buffer, sizeof(buffer), "%s `%s'", ci->key, meta_key);
+  /* Can't pass &entry->value into mr_add_regex, so copy in/out. */
+  re_head = entry->value;
+  status = mr_add_regex(&re_head, ci->values[1].value.string, buffer);
+  if (status == 0) {
+    entry->value = re_head;
+  }
+  return status;
+} /* }}} int mr_config_add_meta_regex */
+
 static int mr_create(const oconfig_item_t *ci, void **user_data) /* {{{ */
 {
   mr_match_t *m;
@@ -198,6 +262,8 @@ static int mr_create(const oconfig_item_t *ci, void **user_data) /* {{{ */
       status = mr_config_add_regex(&m->type, child);
     else if (strcasecmp("TypeInstance", child->key) == 0)
       status = mr_config_add_regex(&m->type_instance, child);
+    else if (strcasecmp("MetaData", child->key) == 0)
+      status = mr_config_add_meta_regex(&m->meta, child);
     else if (strcasecmp("Invert", child->key) == 0)
       status = cf_util_get_boolean(child, &m->invert);
     else {
@@ -215,7 +281,7 @@ static int mr_create(const oconfig_item_t *ci, void **user_data) /* {{{ */
   while (status == 0) {
     if ((m->host == NULL) && (m->plugin == NULL) &&
         (m->plugin_instance == NULL) && (m->type == NULL) &&
-        (m->type_instance == NULL)) {
+        (m->type_instance == NULL) && (m->meta == NULL)) {
       log_err("No (valid) regular expressions have been configured. "
               "This match will be ignored.");
       status = -1;
@@ -270,6 +336,22 @@ static int mr_match(const data_set_t __attribute__((unused)) * ds, /* {{{ */
   if (mr_match_regexen(m->type_instance, vl->type_instance) ==
       FC_MATCH_NO_MATCH)
     return (nomatch_value);
+  if (vl->meta != NULL) {
+    for (llentry_t *e = llist_head(m->meta); e != NULL; e = e->next) {
+      mr_regex_t *meta_re = (mr_regex_t *)e->value;
+      char *value;
+      int status = meta_data_get_string(vl->meta, e->key, &value);
+      if (status == (-ENOENT)) /* key is not present */
+        return (nomatch_value);
+      if (status != 0) /* some other problem */
+        continue;      /* error will have already been printed. */
+      if (mr_match_regexen(meta_re, value) == FC_MATCH_NO_MATCH) {
+        sfree(value);
+        return (nomatch_value);
+      }
+      sfree(value);
+    }
+  }
 
   return (match_value);
 } /* }}} int mr_match */
index f321c8d..aa593a0 100644 (file)
@@ -191,14 +191,10 @@ static int mbmon_config(const char *key, const char *value) {
 
 static void mbmon_submit(const char *type, const char *type_instance,
                          double value) {
-  value_t values[1];
   value_list_t vl = VALUE_LIST_INIT;
 
-  values[0].gauge = value;
-
-  vl.values = values;
+  vl.values = &(value_t){.gauge = value};
   vl.values_len = 1;
-  sstrncpy(vl.host, hostname_g, sizeof(vl.host));
   sstrncpy(vl.plugin, "mbmon", sizeof(vl.plugin));
   sstrncpy(vl.type, type, sizeof(vl.type));
   sstrncpy(vl.type_instance, type_instance, sizeof(vl.type_instance));
index 94965e6..5e6b3ac 100644 (file)
--- a/src/md.c
+++ b/src/md.c
@@ -61,14 +61,10 @@ static int md_config(const char *key, const char *value) {
 
 static void md_submit(const int minor, const char *type_instance,
                       gauge_t value) {
-  value_t values[1];
   value_list_t vl = VALUE_LIST_INIT;
 
-  values[0].gauge = value;
-
-  vl.values = values;
+  vl.values = &(value_t){.gauge = value};
   vl.values_len = 1;
-  sstrncpy(vl.host, hostname_g, sizeof(vl.host));
   sstrncpy(vl.plugin, "md", sizeof(vl.plugin));
   ssnprintf(vl.plugin_instance, sizeof(vl.plugin_instance), "%i", minor);
   sstrncpy(vl.type, "md_disks", sizeof(vl.type));
index 0260a3e..bebbe40 100644 (file)
@@ -402,15 +402,11 @@ static int cmc_init(void) /* {{{ */
 } /* }}} int cmc_init */
 
 static void cmc_submit(const web_page_t *wp, const web_match_t *wm, /* {{{ */
-                       const cu_match_value_t *mv) {
-  value_t values[1];
+                       value_t value) {
   value_list_t vl = VALUE_LIST_INIT;
 
-  values[0] = mv->value;
-
-  vl.values = values;
+  vl.values = &value;
   vl.values_len = 1;
-  sstrncpy(vl.host, hostname_g, sizeof(vl.host));
   sstrncpy(vl.plugin, "memcachec", sizeof(vl.plugin));
   sstrncpy(vl.plugin_instance, wp->instance, sizeof(vl.plugin_instance));
   sstrncpy(vl.type, wm->type, sizeof(vl.type));
@@ -452,7 +448,7 @@ static int cmc_read_page(web_page_t *wp) /* {{{ */
       continue;
     }
 
-    cmc_submit(wp, wm, mv);
+    cmc_submit(wp, wm, mv->value);
     match_value_reset(mv);
   } /* for (wm = wp->matches; wm != NULL; wm = wm->next) */
 
index 02215e1..79c38a7 100644 (file)
 
 struct memcached_s {
   char *name;
-  char *socket;
   char *host;
-  char *port;
+  char *socket;
+  char *connhost;
+  char *connport;
 };
 typedef struct memcached_s memcached_t;
 
@@ -57,9 +58,10 @@ static void memcached_free(void *arg) {
     return;
 
   sfree(st->name);
-  sfree(st->socket);
   sfree(st->host);
-  sfree(st->port);
+  sfree(st->socket);
+  sfree(st->connhost);
+  sfree(st->connport);
   sfree(st);
 }
 
@@ -91,26 +93,20 @@ static int memcached_connect_unix(memcached_t *st) {
 } /* int memcached_connect_unix */
 
 static int memcached_connect_inet(memcached_t *st) {
-  const char *host;
-  const char *port;
-
   struct addrinfo *ai_list;
   int status;
   int fd = -1;
 
-  host = (st->host != NULL) ? st->host : MEMCACHED_DEF_HOST;
-  port = (st->port != NULL) ? st->port : MEMCACHED_DEF_PORT;
-
   struct addrinfo ai_hints = {.ai_family = AF_UNSPEC,
                               .ai_flags = AI_ADDRCONFIG,
                               .ai_socktype = SOCK_STREAM};
 
-  status = getaddrinfo(host, port, &ai_hints, &ai_list);
+  status = getaddrinfo(st->connhost, st->connport, &ai_hints, &ai_list);
   if (status != 0) {
     char errbuf[1024];
     ERROR("memcached plugin: memcached_connect_inet: "
           "getaddrinfo(%s,%s) failed: %s",
-          host, port,
+          st->connhost, st->connport,
           (status == EAI_SYSTEM) ? sstrerror(errno, errbuf, sizeof(errbuf))
                                  : gai_strerror(status));
     return (-1);
@@ -220,33 +216,19 @@ static int memcached_query_daemon(char *buffer, size_t buffer_size,
 } /* int memcached_query_daemon */
 
 static void memcached_init_vl(value_list_t *vl, memcached_t const *st) {
-  char const *host = st->host;
-
-  /* Set vl->host to hostname_g, if:
-   * - Legacy mode is used.
-   * - "Socket" option is given (doc: "Host option is ignored").
-   * - "Host" option is not provided.
-   * - "Host" option is set to "localhost" or "127.0.0.1". */
-  if ((strcmp(st->name, "__legacy__") == 0) || (st->socket != NULL) ||
-      (st->host == NULL) || (strcmp("127.0.0.1", st->host) == 0) ||
-      (strcmp("localhost", st->host) == 0))
-    host = hostname_g;
-
   sstrncpy(vl->plugin, "memcached", sizeof(vl->plugin));
-  sstrncpy(vl->host, host, sizeof(vl->host));
-  if (strcmp(st->name, "__legacy__") != 0)
+  if (st->host != NULL)
+    sstrncpy(vl->host, st->host, sizeof(vl->host));
+  if (st->name != NULL)
     sstrncpy(vl->plugin_instance, st->name, sizeof(vl->plugin_instance));
 }
 
 static void submit_derive(const char *type, const char *type_inst,
                           derive_t value, memcached_t *st) {
-  value_t values[1];
   value_list_t vl = VALUE_LIST_INIT;
-  memcached_init_vl(&vl, st);
-
-  values[0].derive = value;
 
-  vl.values = values;
+  memcached_init_vl(&vl, st);
+  vl.values = &(value_t){.derive = value};
   vl.values_len = 1;
   sstrncpy(vl.type, type, sizeof(vl.type));
   if (type_inst != NULL)
@@ -257,15 +239,14 @@ static void submit_derive(const char *type, const char *type_inst,
 
 static void submit_derive2(const char *type, const char *type_inst,
                            derive_t value0, derive_t value1, memcached_t *st) {
-  value_t values[2];
   value_list_t vl = VALUE_LIST_INIT;
-  memcached_init_vl(&vl, st);
-
-  values[0].derive = value0;
-  values[1].derive = value1;
+  value_t values[] = {
+      {.derive = value0}, {.derive = value1},
+  };
 
+  memcached_init_vl(&vl, st);
   vl.values = values;
-  vl.values_len = 2;
+  vl.values_len = STATIC_ARRAY_SIZE(values);
   sstrncpy(vl.type, type, sizeof(vl.type));
   if (type_inst != NULL)
     sstrncpy(vl.type_instance, type_inst, sizeof(vl.type_instance));
@@ -275,13 +256,10 @@ static void submit_derive2(const char *type, const char *type_inst,
 
 static void submit_gauge(const char *type, const char *type_inst, gauge_t value,
                          memcached_t *st) {
-  value_t values[1];
   value_list_t vl = VALUE_LIST_INIT;
-  memcached_init_vl(&vl, st);
 
-  values[0].gauge = value;
-
-  vl.values = values;
+  memcached_init_vl(&vl, st);
+  vl.values = &(value_t){.gauge = value};
   vl.values_len = 1;
   sstrncpy(vl.type, type, sizeof(vl.type));
   if (type_inst != NULL)
@@ -292,15 +270,14 @@ static void submit_gauge(const char *type, const char *type_inst, gauge_t value,
 
 static void submit_gauge2(const char *type, const char *type_inst,
                           gauge_t value0, gauge_t value1, memcached_t *st) {
-  value_t values[2];
   value_list_t vl = VALUE_LIST_INIT;
-  memcached_init_vl(&vl, st);
-
-  values[0].gauge = value0;
-  values[1].gauge = value1;
+  value_t values[] = {
+      {.gauge = value0}, {.gauge = value1},
+  };
 
+  memcached_init_vl(&vl, st);
   vl.values = values;
-  vl.values_len = 2;
+  vl.values_len = STATIC_ARRAY_SIZE(values);
   sstrncpy(vl.type, type, sizeof(vl.type));
   if (type_inst != NULL)
     sstrncpy(vl.type_instance, type_inst, sizeof(vl.type_instance));
@@ -494,16 +471,53 @@ static int memcached_add_read_callback(memcached_t *st) {
   char callback_name[3 * DATA_MAX_NAME_LEN];
   int status;
 
-  assert(st->name != NULL);
-  ssnprintf(callback_name, sizeof(callback_name), "memcached/%s", st->name);
+  ssnprintf(callback_name, sizeof(callback_name), "memcached/%s",
+            (st->name != NULL) ? st->name : "__legacy__");
+
+  /* If no <Address> used then:
+   * - Connect to the destination specified by <Host>, if present.
+   *   If not, use the default address.
+   * - Use the default hostname (set st->host to NULL), if
+   *    - Legacy mode is used (no configuration options at all), or
+   *    - "Host" option is not provided, or
+   *    - "Host" option is set to "localhost" or "127.0.0.1".
+   *
+   * If <Address> used then host may be set to "localhost" or "127.0.0.1"
+   * explicitly.
+   */
+  if (st->connhost == NULL) {
+    if (st->host) {
+      st->connhost = strdup(st->host);
+      if (st->connhost == NULL)
+        return (ENOMEM);
+
+      if ((strcmp("127.0.0.1", st->host) == 0) ||
+          (strcmp("localhost", st->host) == 0))
+        sfree(st->host);
+    } else {
+      st->connhost = strdup(MEMCACHED_DEF_HOST);
+      if (st->connhost == NULL)
+        return (ENOMEM);
+    }
+  }
+
+  if (st->connport == NULL) {
+    st->connport = strdup(MEMCACHED_DEF_PORT);
+    if (st->connport == NULL)
+      return (ENOMEM);
+  }
+
+  assert(st->connhost != NULL);
+  assert(st->connport != NULL);
 
-  user_data_t ud = {.data = st, .free_func = memcached_free};
+  status = plugin_register_complex_read(
+      /* group = */ "memcached",
+      /* name      = */ callback_name,
+      /* callback  = */ memcached_read,
+      /* interval  = */ 0, &(user_data_t){
+                               .data = st, .free_func = memcached_free,
+                           });
 
-  status = plugin_register_complex_read(/* group = */ "memcached",
-                                        /* name      = */ callback_name,
-                                        /* callback  = */ memcached_read,
-                                        /* interval  = */ 0,
-                                        /* user_data = */ &ud);
   return (status);
 } /* int memcached_add_read_callback */
 
@@ -511,6 +525,7 @@ static int memcached_add_read_callback(memcached_t *st) {
  * <Plugin memcached>
  *   <Instance "instance_name">
  *     Host foo.zomg.com
+ *     Address 1.2.3.4
  *     Port "1234"
  *   </Instance>
  * </Plugin>
@@ -525,23 +540,22 @@ static int config_add_instance(oconfig_item_t *ci) {
   st = calloc(1, sizeof(*st));
   if (st == NULL) {
     ERROR("memcached plugin: calloc failed.");
-    return (-1);
+    return (ENOMEM);
   }
 
   st->name = NULL;
-  st->socket = NULL;
   st->host = NULL;
-  st->port = NULL;
+  st->socket = NULL;
+  st->connhost = NULL;
+  st->connport = NULL;
 
-  if (strcasecmp(ci->key, "Plugin") == 0) /* default instance */
-    st->name = sstrdup("__legacy__");
-  else /* <Instance /> block */
+  if (strcasecmp(ci->key, "Instance") == 0)
     status = cf_util_get_string(ci, &st->name);
+
   if (status != 0) {
     sfree(st);
     return (status);
   }
-  assert(st->name != NULL);
 
   for (int i = 0; i < ci->children_num; i++) {
     oconfig_item_t *child = ci->children + i;
@@ -550,8 +564,10 @@ static int config_add_instance(oconfig_item_t *ci) {
       status = cf_util_get_string(child, &st->socket);
     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->connhost);
     else if (strcasecmp("Port", child->key) == 0)
-      status = cf_util_get_service(child, &st->port);
+      status = cf_util_get_service(child, &st->connport);
     else {
       WARNING("memcached plugin: Option `%s' not allowed here.", child->key);
       status = -1;
@@ -608,10 +624,11 @@ static int memcached_init(void) {
   st = calloc(1, sizeof(*st));
   if (st == NULL)
     return (ENOMEM);
-  st->name = sstrdup("__legacy__");
-  st->socket = NULL;
+  st->name = NULL;
   st->host = NULL;
-  st->port = NULL;
+  st->socket = NULL;
+  st->connhost = NULL;
+  st->connport = NULL;
 
   status = memcached_add_read_callback(st);
   if (status == 0)
index 3d6151c..14b3559 100644 (file)
@@ -483,7 +483,6 @@ static int memory_read(void) /* {{{ */
 
   vl.values = v;
   vl.values_len = STATIC_ARRAY_SIZE(v);
-  sstrncpy(vl.host, hostname_g, sizeof(vl.host));
   sstrncpy(vl.plugin, "memory", sizeof(vl.plugin));
   sstrncpy(vl.type, "memory", sizeof(vl.type));
   vl.time = cdtime();
index 40d7fda..ab5ffd1 100644 (file)
--- a/src/mic.c
+++ b/src/mic.c
@@ -121,19 +121,16 @@ static int mic_config(const char *key, const char *value) {
 }
 
 static void mic_submit_memory_use(int micnumber, const char *type_instance,
-                                  U32 val) {
-  value_t values[1];
+                                  U32 value) {
   value_list_t vl = VALUE_LIST_INIT;
 
   /* MicAccessAPI reports KB's of memory, adjust for this */
-  DEBUG("mic plugin: Memory Value Report; %u %lf", val,
-        ((gauge_t)val) * 1024.0);
-  values[0].gauge = ((gauge_t)val) * 1024.0;
+  DEBUG("mic plugin: Memory Value Report; %u %lf", value,
+        ((gauge_t)value) * 1024.0);
 
-  vl.values = values;
+  vl.values = &(value_t){.gauge = ((gauge_t)value) * 1024.0};
   vl.values_len = 1;
 
-  strncpy(vl.host, hostname_g, sizeof(vl.host));
   strncpy(vl.plugin, "mic", sizeof(vl.plugin));
   ssnprintf(vl.plugin_instance, sizeof(vl.plugin_instance), "%i", micnumber);
   strncpy(vl.type, "memory", sizeof(vl.type));
@@ -160,13 +157,10 @@ static int mic_read_memory(int mic) {
   return (0);
 }
 
-static void mic_submit_temp(int micnumber, const char *type, gauge_t val) {
-  value_t values[1];
+static void mic_submit_temp(int micnumber, const char *type, gauge_t value) {
   value_list_t vl = VALUE_LIST_INIT;
 
-  values[0].gauge = val;
-
-  vl.values = values;
+  vl.values = &(value_t){.gauge = value};
   vl.values_len = 1;
 
   strncpy(vl.host, hostname_g, sizeof(vl.host));
@@ -205,13 +199,10 @@ static int mic_read_temps(int mic) {
 }
 
 static void mic_submit_cpu(int micnumber, const char *type_instance, int core,
-                           derive_t val) {
-  value_t values[1];
+                           derive_t value) {
   value_list_t vl = VALUE_LIST_INIT;
 
-  values[0].derive = val;
-
-  vl.values = values;
+  vl.values = &(value_t){.derive = value};
   vl.values_len = 1;
 
   strncpy(vl.host, hostname_g, sizeof(vl.host));
@@ -262,13 +253,10 @@ static int mic_read_cpu(int mic) {
 }
 
 static void mic_submit_power(int micnumber, const char *type,
-                             const char *type_instance, gauge_t val) {
-  value_t values[1];
+                             const char *type_instance, gauge_t value) {
   value_list_t vl = VALUE_LIST_INIT;
 
-  values[0].gauge = val;
-
-  vl.values = values;
+  vl.values = &(value_t){.gauge = value};
   vl.values_len = 1;
 
   strncpy(vl.host, hostname_g, sizeof(vl.host));
index f2d07cc..5d29791 100644 (file)
@@ -443,6 +443,8 @@ static int mb_read_data(mb_host_t *host, mb_slave_t *slave, /* {{{ */
   if (host->connection == NULL) {
     status = EBADF;
   } else if (host->conntype == MBCONN_TCP) {
+    /* getpeername() is used only to determine if the socket is connected, not
+     * because we're really interested in the peer's IP address. */
     if (getpeername(modbus_get_socket(host->connection),
                     (void *)&(struct sockaddr_storage){0},
                     &(socklen_t){sizeof(struct sockaddr_storage)}) != 0)
@@ -930,17 +932,16 @@ static int mb_config_add_host(oconfig_item_t *ci) /* {{{ */
   }
 
   if (status == 0) {
-    user_data_t ud;
     char name[1024];
 
-    ud.data = host;
-    ud.free_func = host_free;
-
     ssnprintf(name, sizeof(name), "modbus-%s", host->host);
 
     plugin_register_complex_read(/* group = */ NULL, name,
                                  /* callback = */ mb_read,
-                                 /* interval = */ host->interval, &ud);
+                                 /* interval = */ host->interval,
+                                 &(user_data_t){
+                                     .data = host, .free_func = host_free,
+                                 });
   } else {
     host_free(host);
   }
index 8a24cca..315974b 100644 (file)
@@ -609,9 +609,9 @@ static int mqtt_config_publisher(oconfig_item_t *ci) {
   }
 
   ssnprintf(cb_name, sizeof(cb_name), "mqtt/%s", conf->name);
-  user_data_t user_data = {.data = conf};
-
-  plugin_register_write(cb_name, mqtt_write, &user_data);
+  plugin_register_write(cb_name, mqtt_write, &(user_data_t){
+                                                 .data = conf,
+                                             });
   return (0);
 } /* mqtt_config_publisher */
 
@@ -740,7 +740,8 @@ static int mqtt_init(void) {
     status = plugin_thread_create(&subscribers[i]->thread,
                                   /* attrs = */ NULL,
                                   /* func  = */ subscribers_thread,
-                                  /* args  = */ subscribers[i]);
+                                  /* args  = */ subscribers[i],
+                                  /* name  = */ "mqtt");
     if (status != 0) {
       char errbuf[1024];
       ERROR("mqtt plugin: pthread_create failed: %s",
index da1a8c8..913e277 100644 (file)
@@ -185,14 +185,10 @@ static int multimeter_init(void) {
 #undef LINE_LENGTH
 
 static void multimeter_submit(double value) {
-  value_t values[1];
   value_list_t vl = VALUE_LIST_INIT;
 
-  values[0].gauge = value;
-
-  vl.values = values;
+  vl.values = &(value_t){.gauge = value};
   vl.values_len = 1;
-  sstrncpy(vl.host, hostname_g, sizeof(vl.host));
   sstrncpy(vl.plugin, "multimeter", sizeof(vl.plugin));
   sstrncpy(vl.type, "multimeter", sizeof(vl.type));
 
index 2a63737..8409d1b 100644 (file)
@@ -222,10 +222,11 @@ static int mysql_config_database(oconfig_item_t *ci) /* {{{ */
     else
       sstrncpy(cb_name, "mysql", sizeof(cb_name));
 
-    user_data_t ud = {.data = db, .free_func = mysql_database_free};
-
-    plugin_register_complex_read(/* group = */ NULL, cb_name, mysql_read,
-                                 /* interval = */ 0, &ud);
+    plugin_register_complex_read(
+        /* group = */ NULL, cb_name, mysql_read, /* interval = */ 0,
+        &(user_data_t){
+            .data = db, .free_func = mysql_database_free,
+        });
   } else {
     mysql_database_free(db);
     return (-1);
@@ -337,35 +338,20 @@ static void submit(const char *type, const char *type_instance, value_t *values,
   plugin_dispatch_values(&vl);
 } /* submit */
 
-static void counter_submit(const char *type, const char *type_instance,
-                           derive_t value, mysql_database_t *db) {
-  value_t values[1];
-
-  values[0].derive = value;
-  submit(type, type_instance, values, STATIC_ARRAY_SIZE(values), db);
-} /* void counter_submit */
-
 static void gauge_submit(const char *type, const char *type_instance,
                          gauge_t value, mysql_database_t *db) {
-  value_t values[1];
-
-  values[0].gauge = value;
-  submit(type, type_instance, values, STATIC_ARRAY_SIZE(values), db);
+  submit(type, type_instance, &(value_t){.gauge = value}, 1, db);
 } /* void gauge_submit */
 
 static void derive_submit(const char *type, const char *type_instance,
                           derive_t value, mysql_database_t *db) {
-  value_t values[1];
-
-  values[0].derive = value;
-  submit(type, type_instance, values, STATIC_ARRAY_SIZE(values), db);
+  submit(type, type_instance, &(value_t){.derive = value}, 1, db);
 } /* void derive_submit */
 
 static void traffic_submit(derive_t rx, derive_t tx, mysql_database_t *db) {
-  value_t values[2];
-
-  values[0].derive = rx;
-  values[1].derive = tx;
+  value_t values[] = {
+      {.derive = rx}, {.derive = tx},
+  };
 
   submit("mysql_octets", NULL, values, STATIC_ARRAY_SIZE(values), db);
 } /* void traffic_submit */
@@ -424,7 +410,7 @@ static int mysql_read_master_stats(mysql_database_t *db, MYSQL *con) {
   }
 
   position = atoll(row[1]);
-  counter_submit("mysql_log_position", "master-bin", position, db);
+  derive_submit("mysql_log_position", "master-bin", position, db);
 
   row = mysql_fetch_row(res);
   if (row != NULL)
@@ -481,10 +467,10 @@ static int mysql_read_slave_stats(mysql_database_t *db, MYSQL *con) {
     double gauge;
 
     counter = atoll(row[READ_MASTER_LOG_POS_IDX]);
-    counter_submit("mysql_log_position", "slave-read", counter, db);
+    derive_submit("mysql_log_position", "slave-read", counter, db);
 
     counter = atoll(row[EXEC_MASTER_LOG_POS_IDX]);
-    counter_submit("mysql_log_position", "slave-exec", counter, db);
+    derive_submit("mysql_log_position", "slave-exec", counter, db);
 
     if (row[SECONDS_BEHIND_MASTER_IDX] != NULL) {
       gauge = atof(row[SECONDS_BEHIND_MASTER_IDX]);
@@ -623,7 +609,7 @@ static int mysql_read_innodb_stats(mysql_database_t *db, MYSQL *con) {
 
     switch (metrics[i].ds_type) {
     case DS_TYPE_COUNTER:
-      counter_submit(metrics[i].type, key, (counter_t)val, db);
+      derive_submit(metrics[i].type, key, (counter_t)val, db);
       break;
     case DS_TYPE_GAUGE:
       gauge_submit(metrics[i].type, key, (gauge_t)val, db);
@@ -779,12 +765,12 @@ static int mysql_read(user_data_t *ud) {
 
       /* Ignore `prepared statements' */
       if (strncmp(key, "Com_stmt_", strlen("Com_stmt_")) != 0)
-        counter_submit("mysql_commands", key + strlen("Com_"), val, db);
+        derive_submit("mysql_commands", key + strlen("Com_"), val, db);
     } else if (strncmp(key, "Handler_", strlen("Handler_")) == 0) {
       if (val == 0ULL)
         continue;
 
-      counter_submit("mysql_handler", key + strlen("Handler_"), val, db);
+      derive_submit("mysql_handler", key + strlen("Handler_"), val, db);
     } else if (strncmp(key, "Qcache_", strlen("Qcache_")) == 0) {
       if (strcmp(key, "Qcache_hits") == 0)
         qcache_hits = (derive_t)val;
@@ -811,7 +797,7 @@ static int mysql_read(user_data_t *ud) {
       else if (strcmp(key, "Threads_created") == 0)
         threads_created = (derive_t)val;
     } else if (strncmp(key, "Table_locks_", strlen("Table_locks_")) == 0) {
-      counter_submit("mysql_locks", key + strlen("Table_locks_"), val, db);
+      derive_submit("mysql_locks", key + strlen("Table_locks_"), val, db);
     } else if (db->innodb_stats &&
                strncmp(key, "Innodb_", strlen("Innodb_")) == 0) {
       /* buffer pool */
@@ -820,7 +806,7 @@ static int mysql_read(user_data_t *ud) {
       else if (strcmp(key, "Innodb_buffer_pool_pages_dirty") == 0)
         gauge_submit("mysql_bpool_pages", "dirty", val, db);
       else if (strcmp(key, "Innodb_buffer_pool_pages_flushed") == 0)
-        counter_submit("mysql_bpool_counters", "pages_flushed", val, db);
+        derive_submit("mysql_bpool_counters", "pages_flushed", val, db);
       else if (strcmp(key, "Innodb_buffer_pool_pages_free") == 0)
         gauge_submit("mysql_bpool_pages", "free", val, db);
       else if (strcmp(key, "Innodb_buffer_pool_pages_misc") == 0)
@@ -828,19 +814,19 @@ static int mysql_read(user_data_t *ud) {
       else if (strcmp(key, "Innodb_buffer_pool_pages_total") == 0)
         gauge_submit("mysql_bpool_pages", "total", val, db);
       else if (strcmp(key, "Innodb_buffer_pool_read_ahead_rnd") == 0)
-        counter_submit("mysql_bpool_counters", "read_ahead_rnd", val, db);
+        derive_submit("mysql_bpool_counters", "read_ahead_rnd", val, db);
       else if (strcmp(key, "Innodb_buffer_pool_read_ahead") == 0)
-        counter_submit("mysql_bpool_counters", "read_ahead", val, db);
+        derive_submit("mysql_bpool_counters", "read_ahead", val, db);
       else if (strcmp(key, "Innodb_buffer_pool_read_ahead_evicted") == 0)
-        counter_submit("mysql_bpool_counters", "read_ahead_evicted", val, db);
+        derive_submit("mysql_bpool_counters", "read_ahead_evicted", val, db);
       else if (strcmp(key, "Innodb_buffer_pool_read_requests") == 0)
-        counter_submit("mysql_bpool_counters", "read_requests", val, db);
+        derive_submit("mysql_bpool_counters", "read_requests", val, db);
       else if (strcmp(key, "Innodb_buffer_pool_reads") == 0)
-        counter_submit("mysql_bpool_counters", "reads", val, db);
+        derive_submit("mysql_bpool_counters", "reads", val, db);
       else if (strcmp(key, "Innodb_buffer_pool_wait_free") == 0)
-        counter_submit("mysql_bpool_counters", "wait_free", val, db);
+        derive_submit("mysql_bpool_counters", "wait_free", val, db);
       else if (strcmp(key, "Innodb_buffer_pool_write_requests") == 0)
-        counter_submit("mysql_bpool_counters", "write_requests", val, db);
+        derive_submit("mysql_bpool_counters", "write_requests", val, db);
       else if (strcmp(key, "Innodb_buffer_pool_bytes_data") == 0)
         gauge_submit("mysql_bpool_bytes", "data", val, db);
       else if (strcmp(key, "Innodb_buffer_pool_bytes_dirty") == 0)
@@ -848,73 +834,73 @@ static int mysql_read(user_data_t *ud) {
 
       /* data */
       if (strcmp(key, "Innodb_data_fsyncs") == 0)
-        counter_submit("mysql_innodb_data", "fsyncs", val, db);
+        derive_submit("mysql_innodb_data", "fsyncs", val, db);
       else if (strcmp(key, "Innodb_data_read") == 0)
-        counter_submit("mysql_innodb_data", "read", val, db);
+        derive_submit("mysql_innodb_data", "read", val, db);
       else if (strcmp(key, "Innodb_data_reads") == 0)
-        counter_submit("mysql_innodb_data", "reads", val, db);
+        derive_submit("mysql_innodb_data", "reads", val, db);
       else if (strcmp(key, "Innodb_data_writes") == 0)
-        counter_submit("mysql_innodb_data", "writes", val, db);
+        derive_submit("mysql_innodb_data", "writes", val, db);
       else if (strcmp(key, "Innodb_data_written") == 0)
-        counter_submit("mysql_innodb_data", "written", val, db);
+        derive_submit("mysql_innodb_data", "written", val, db);
 
       /* double write */
       else if (strcmp(key, "Innodb_dblwr_writes") == 0)
-        counter_submit("mysql_innodb_dblwr", "writes", val, db);
+        derive_submit("mysql_innodb_dblwr", "writes", val, db);
       else if (strcmp(key, "Innodb_dblwr_pages_written") == 0)
-        counter_submit("mysql_innodb_dblwr", "written", val, db);
+        derive_submit("mysql_innodb_dblwr", "written", val, db);
       else if (strcmp(key, "Innodb_dblwr_page_size") == 0)
         gauge_submit("mysql_innodb_dblwr", "page_size", val, db);
 
       /* log */
       else if (strcmp(key, "Innodb_log_waits") == 0)
-        counter_submit("mysql_innodb_log", "waits", val, db);
+        derive_submit("mysql_innodb_log", "waits", val, db);
       else if (strcmp(key, "Innodb_log_write_requests") == 0)
-        counter_submit("mysql_innodb_log", "write_requests", val, db);
+        derive_submit("mysql_innodb_log", "write_requests", val, db);
       else if (strcmp(key, "Innodb_log_writes") == 0)
-        counter_submit("mysql_innodb_log", "writes", val, db);
+        derive_submit("mysql_innodb_log", "writes", val, db);
       else if (strcmp(key, "Innodb_os_log_fsyncs") == 0)
-        counter_submit("mysql_innodb_log", "fsyncs", val, db);
+        derive_submit("mysql_innodb_log", "fsyncs", val, db);
       else if (strcmp(key, "Innodb_os_log_written") == 0)
-        counter_submit("mysql_innodb_log", "written", val, db);
+        derive_submit("mysql_innodb_log", "written", val, db);
 
       /* pages */
       else if (strcmp(key, "Innodb_pages_created") == 0)
-        counter_submit("mysql_innodb_pages", "created", val, db);
+        derive_submit("mysql_innodb_pages", "created", val, db);
       else if (strcmp(key, "Innodb_pages_read") == 0)
-        counter_submit("mysql_innodb_pages", "read", val, db);
+        derive_submit("mysql_innodb_pages", "read", val, db);
       else if (strcmp(key, "Innodb_pages_written") == 0)
-        counter_submit("mysql_innodb_pages", "written", val, db);
+        derive_submit("mysql_innodb_pages", "written", val, db);
 
       /* row lock */
       else if (strcmp(key, "Innodb_row_lock_time") == 0)
-        counter_submit("mysql_innodb_row_lock", "time", val, db);
+        derive_submit("mysql_innodb_row_lock", "time", val, db);
       else if (strcmp(key, "Innodb_row_lock_waits") == 0)
-        counter_submit("mysql_innodb_row_lock", "waits", val, db);
+        derive_submit("mysql_innodb_row_lock", "waits", val, db);
 
       /* rows */
       else if (strcmp(key, "Innodb_rows_deleted") == 0)
-        counter_submit("mysql_innodb_rows", "deleted", val, db);
+        derive_submit("mysql_innodb_rows", "deleted", val, db);
       else if (strcmp(key, "Innodb_rows_inserted") == 0)
-        counter_submit("mysql_innodb_rows", "inserted", val, db);
+        derive_submit("mysql_innodb_rows", "inserted", val, db);
       else if (strcmp(key, "Innodb_rows_read") == 0)
-        counter_submit("mysql_innodb_rows", "read", val, db);
+        derive_submit("mysql_innodb_rows", "read", val, db);
       else if (strcmp(key, "Innodb_rows_updated") == 0)
-        counter_submit("mysql_innodb_rows", "updated", val, db);
+        derive_submit("mysql_innodb_rows", "updated", val, db);
     } else if (strncmp(key, "Select_", strlen("Select_")) == 0) {
-      counter_submit("mysql_select", key + strlen("Select_"), val, db);
+      derive_submit("mysql_select", key + strlen("Select_"), val, db);
     } else if (strncmp(key, "Sort_", strlen("Sort_")) == 0) {
       if (strcmp(key, "Sort_merge_passes") == 0)
-        counter_submit("mysql_sort_merge_passes", NULL, val, db);
+        derive_submit("mysql_sort_merge_passes", NULL, val, db);
       else if (strcmp(key, "Sort_rows") == 0)
-        counter_submit("mysql_sort_rows", NULL, val, db);
+        derive_submit("mysql_sort_rows", NULL, val, db);
       else if (strcmp(key, "Sort_range") == 0)
-        counter_submit("mysql_sort", "range", val, db);
+        derive_submit("mysql_sort", "range", val, db);
       else if (strcmp(key, "Sort_scan") == 0)
-        counter_submit("mysql_sort", "scan", val, db);
+        derive_submit("mysql_sort", "scan", val, db);
 
     } else if (strncmp(key, "Slow_queries", strlen("Slow_queries")) == 0) {
-      counter_submit("mysql_slow_queries", NULL, val, db);
+      derive_submit("mysql_slow_queries", NULL, val, db);
     }
   }
   mysql_free_result(res);
index 4944416..45144bf 100644 (file)
@@ -611,8 +611,9 @@ static data_volume_perf_t *get_volume_perf(cfg_volume_perf_t *cvp, /* {{{ */
  */
 static int submit_values(const char *host, /* {{{ */
                          const char *plugin_inst, const char *type,
-                         const char *type_inst, value_t *values, int values_len,
-                         cdtime_t timestamp, cdtime_t interval) {
+                         const char *type_inst, value_t *values,
+                         size_t values_len, cdtime_t timestamp,
+                         cdtime_t interval) {
   value_list_t vl = VALUE_LIST_INIT;
 
   vl.values = values;
@@ -626,8 +627,6 @@ static int submit_values(const char *host, /* {{{ */
 
   if (host != NULL)
     sstrncpy(vl.host, host, sizeof(vl.host));
-  else
-    sstrncpy(vl.host, hostname_g, sizeof(vl.host));
   sstrncpy(vl.plugin, "netapp", sizeof(vl.plugin));
   if (plugin_inst != NULL)
     sstrncpy(vl.plugin_instance, plugin_inst, sizeof(vl.plugin_instance));
@@ -643,49 +642,39 @@ static int submit_two_derive(const char *host,
                              const char *type, const char *type_inst,
                              derive_t val0, derive_t val1, cdtime_t timestamp,
                              cdtime_t interval) {
-  value_t values[2];
-
-  values[0].derive = val0;
-  values[1].derive = val1;
+  value_t values[] = {
+      {.derive = val0}, {.derive = val1},
+  };
 
-  return (submit_values(host, plugin_inst, type, type_inst, values, 2,
-                        timestamp, interval));
+  return (submit_values(host, plugin_inst, type, type_inst, values,
+                        STATIC_ARRAY_SIZE(values), timestamp, interval));
 } /* }}} int submit_two_derive */
 
 static int submit_derive(const char *host, const char *plugin_inst, /* {{{ */
                          const char *type, const char *type_inst,
                          derive_t counter, cdtime_t timestamp,
                          cdtime_t interval) {
-  value_t v;
-
-  v.derive = counter;
-
-  return (submit_values(host, plugin_inst, type, type_inst, &v, 1, timestamp,
-                        interval));
+  return (submit_values(host, plugin_inst, type, type_inst,
+                        &(value_t){.derive = counter}, 1, timestamp, interval));
 } /* }}} int submit_derive */
 
 static int submit_two_gauge(const char *host, const char *plugin_inst, /* {{{ */
                             const char *type, const char *type_inst,
                             gauge_t val0, gauge_t val1, cdtime_t timestamp,
                             cdtime_t interval) {
-  value_t values[2];
+  value_t values[] = {
+      {.gauge = val0}, {.gauge = val1},
+  };
 
-  values[0].gauge = val0;
-  values[1].gauge = val1;
-
-  return (submit_values(host, plugin_inst, type, type_inst, values, 2,
-                        timestamp, interval));
+  return (submit_values(host, plugin_inst, type, type_inst, values,
+                        STATIC_ARRAY_SIZE(values), timestamp, interval));
 } /* }}} int submit_two_gauge */
 
 static int submit_double(const char *host, const char *plugin_inst, /* {{{ */
                          const char *type, const char *type_inst, double d,
                          cdtime_t timestamp, cdtime_t interval) {
-  value_t v;
-
-  v.gauge = (gauge_t)d;
-
-  return (submit_values(host, plugin_inst, type, type_inst, &v, 1, timestamp,
-                        interval));
+  return (submit_values(host, plugin_inst, type, type_inst,
+                        &(value_t){.gauge = d}, 1, timestamp, interval));
 } /* }}} int submit_uint64 */
 
 /* Calculate hit ratio from old and new counters and submit the resulting
@@ -695,7 +684,7 @@ static int submit_cache_ratio(const char *host, /* {{{ */
                               uint64_t new_hits, uint64_t new_misses,
                               uint64_t old_hits, uint64_t old_misses,
                               cdtime_t timestamp, cdtime_t interval) {
-  value_t v;
+  value_t v = {.gauge = NAN};
 
   if ((new_hits >= old_hits) && (new_misses >= old_misses)) {
     uint64_t hits;
@@ -705,8 +694,6 @@ static int submit_cache_ratio(const char *host, /* {{{ */
     misses = new_misses - old_misses;
 
     v.gauge = 100.0 * ((gauge_t)hits) / ((gauge_t)(hits + misses));
-  } else {
-    v.gauge = NAN;
   }
 
   return (submit_values(host, plugin_inst, "cache_ratio", type_inst, &v, 1,
@@ -1923,15 +1910,13 @@ static int cna_query_quota(host_config_t *host) /* {{{ */
 static int cna_handle_snapvault_data(const char *hostname, /* {{{ */
                                      cfg_snapvault_t *cfg_snapvault,
                                      na_elem_t *data, cdtime_t interval) {
-  na_elem_iter_t status_iter;
-
-  status = na_elem_child(data, "status-list");
-  if (!status) {
+  na_elem_t *status_list = na_elem_child(data, "status-list");
+  if (status_list == NULL) {
     ERROR("netapp plugin: SnapVault status record missing status-list");
     return (0);
   }
 
-  status_iter = na_child_iterator(status);
+  na_elem_iter_t status_iter = na_child_iterator(status_list);
   for (na_elem_t *status = na_iterator_next(&status_iter); status != NULL;
        status = na_iterator_next(&status_iter)) {
     const char *dest_sys, *dest_path, *src_sys, *src_path;
@@ -2834,13 +2819,13 @@ static int cna_register_host(host_config_t *host) /* {{{ */
   else
     ssnprintf(cb_name, sizeof(cb_name), "netapp-%s", host->name);
 
-  user_data_t ud = {.data = host,
-                    .free_func = (void (*)(void *))free_host_config};
-
-  plugin_register_complex_read(/* group = */ NULL, cb_name,
-                               /* callback  = */ cna_read,
-                               /* interval  = */ host->interval,
-                               /* user data = */ &ud);
+  plugin_register_complex_read(
+      /* group = */ NULL, cb_name,
+      /* callback  = */ cna_read,
+      /* interval  = */ host->interval,
+      &(user_data_t){
+          .data = host, .free_func = (void *)free_host_config,
+      });
 
   return (0);
 } /* }}} int cna_register_host */
index 0a33207..8076c1f 100644 (file)
@@ -174,14 +174,10 @@ static int check_ignorelist(const char *dev, const char *type,
 
 static void submit_one(const char *dev, const char *type,
                        const char *type_instance, derive_t value) {
-  value_t values[1];
   value_list_t vl = VALUE_LIST_INIT;
 
-  values[0].derive = value;
-
-  vl.values = values;
+  vl.values = &(value_t){.derive = value};
   vl.values_len = 1;
-  sstrncpy(vl.host, hostname_g, sizeof(vl.host));
   sstrncpy(vl.plugin, "netlink", sizeof(vl.plugin));
   sstrncpy(vl.plugin_instance, dev, sizeof(vl.plugin_instance));
   sstrncpy(vl.type, type, sizeof(vl.type));
@@ -194,15 +190,13 @@ static void submit_one(const char *dev, const char *type,
 
 static void submit_two(const char *dev, const char *type,
                        const char *type_instance, derive_t rx, derive_t tx) {
-  value_t values[2];
   value_list_t vl = VALUE_LIST_INIT;
-
-  values[0].derive = rx;
-  values[1].derive = tx;
+  value_t values[] = {
+      {.derive = rx}, {.derive = tx},
+  };
 
   vl.values = values;
-  vl.values_len = 2;
-  sstrncpy(vl.host, hostname_g, sizeof(vl.host));
+  vl.values_len = STATIC_ARRAY_SIZE(values);
   sstrncpy(vl.plugin, "netlink", sizeof(vl.plugin));
   sstrncpy(vl.plugin_instance, dev, sizeof(vl.plugin_instance));
   sstrncpy(vl.type, type, sizeof(vl.type));
index 2ff09af..f93f49c 100644 (file)
@@ -292,7 +292,7 @@ static char *send_buffer;
 static char *send_buffer_ptr;
 static int send_buffer_fill;
 static cdtime_t send_buffer_last_update;
-static value_list_t send_buffer_vl = VALUE_LIST_STATIC;
+static value_list_t send_buffer_vl = VALUE_LIST_INIT;
 static pthread_mutex_t send_buffer_lock = PTHREAD_MUTEX_INITIALIZER;
 
 /* XXX: These counters are incremented from one place only. The spot in which
@@ -3086,7 +3086,6 @@ static int network_stats_read(void) /* {{{ */
   vl.values = values;
   vl.values_len = 2;
   vl.time = 0;
-  sstrncpy(vl.host, hostname_g, sizeof(vl.host));
   sstrncpy(vl.plugin, "network", sizeof(vl.plugin));
 
   /* Octets received / sent */
@@ -3167,7 +3166,8 @@ static int network_init(void) {
   if (dispatch_thread_running == 0) {
     int status;
     status = plugin_thread_create(&dispatch_thread_id, NULL /* no attributes */,
-                                  dispatch_thread, NULL /* no argument */);
+                                  dispatch_thread, NULL /* no argument */,
+                                  "network disp");
     if (status != 0) {
       char errbuf[1024];
       ERROR("network: pthread_create failed: %s",
@@ -3180,7 +3180,8 @@ static int network_init(void) {
   if (receive_thread_running == 0) {
     int status;
     status = plugin_thread_create(&receive_thread_id, NULL /* no attributes */,
-                                  receive_thread, NULL /* no argument */);
+                                  receive_thread, NULL /* no argument */,
+                                  "network recv");
     if (status != 0) {
       char errbuf[1024];
       ERROR("network: pthread_create failed: %s",
index 0142011..74c6cbc 100644 (file)
--- a/src/nfs.c
+++ b/src/nfs.c
@@ -339,7 +339,6 @@ static void nfs_procedures_submit(const char *plugin_instance,
   value_list_t vl = VALUE_LIST_INIT;
 
   vl.values_len = 1;
-  sstrncpy(vl.host, hostname_g, sizeof(vl.host));
   sstrncpy(vl.plugin, "nfs", sizeof(vl.plugin));
   sstrncpy(vl.plugin_instance, plugin_instance, sizeof(vl.plugin_instance));
   sstrncpy(vl.type, "nfs_procedure", sizeof(vl.type));
index e320ea7..060eae2 100644 (file)
@@ -178,10 +178,8 @@ static void submit(const char *type, const char *inst, long long value) {
     return;
 
   vl.values = values;
-  vl.values_len = 1;
-  sstrncpy(vl.host, hostname_g, sizeof(vl.host));
+  vl.values_len = STATIC_ARRAY_SIZE(values);
   sstrncpy(vl.plugin, "nginx", sizeof(vl.plugin));
-  sstrncpy(vl.plugin_instance, "", sizeof(vl.plugin_instance));
   sstrncpy(vl.type, type, sizeof(vl.type));
 
   if (inst != NULL)
@@ -226,7 +224,7 @@ static int nginx_read(void) {
   /*
    * Active connections: 291
    * server accepts handled requests
-   *  16630948 16630948 31070465
+   *  101059015 100422216 347910649
    * Reading: 6 Writing: 179 Waiting: 106
    */
   for (int i = 0; i < lines_num; i++) {
@@ -240,7 +238,11 @@ static int nginx_read(void) {
       } else if ((atoll(fields[0]) != 0) && (atoll(fields[1]) != 0) &&
                  (atoll(fields[2]) != 0)) {
         submit("connections", "accepted", atoll(fields[0]));
+        /* TODO: The legacy metric "handled", which is the sum of "accepted" and
+         * "failed", is reported for backwards compatibility only. Remove in the
+         * next major version. */
         submit("connections", "handled", atoll(fields[1]));
+        submit("connections", "failed", (atoll(fields[0]) - atoll(fields[1])));
         submit("nginx_requests", NULL, atoll(fields[2]));
       }
     } else if (fields_num == 6) {
index d486a61..f42e66c 100644 (file)
@@ -204,7 +204,6 @@ static int notify_email_notification(const notification_t *n,
                                      user_data_t __attribute__((unused)) *
                                          user_data) {
 
-  time_t tt;
   struct tm timestamp_tm;
   char timestamp_str[64];
 
@@ -226,8 +225,7 @@ static int notify_email_notification(const notification_t *n,
             (email_subject == NULL) ? DEFAULT_SMTP_SUBJECT : email_subject,
             severity, n->host);
 
-  tt = CDTIME_T_TO_TIME_T(n->time);
-  localtime_r(&tt, &timestamp_tm);
+  localtime_r(&CDTIME_T_TO_TIME_T(n->time), &timestamp_tm);
   strftime(timestamp_str, sizeof(timestamp_str), "%Y-%m-%d %H:%M:%S",
            &timestamp_tm);
   timestamp_str[sizeof(timestamp_str) - 1] = '\0';
index 1cb59de..1dc1857 100644 (file)
@@ -292,16 +292,11 @@ static int ntpd_config(const char *key, const char *value) {
 
 static void ntpd_submit(const char *type, const char *type_inst,
                         gauge_t value) {
-  value_t values[1];
   value_list_t vl = VALUE_LIST_INIT;
 
-  values[0].gauge = value;
-
-  vl.values = values;
+  vl.values = &(value_t){.gauge = value};
   vl.values_len = 1;
-  sstrncpy(vl.host, hostname_g, sizeof(vl.host));
   sstrncpy(vl.plugin, "ntpd", sizeof(vl.plugin));
-  sstrncpy(vl.plugin_instance, "", sizeof(vl.plugin_instance));
   sstrncpy(vl.type, type, sizeof(vl.type));
   sstrncpy(vl.type_instance, type_inst, sizeof(vl.type_instance));
 
index 512520d..049daee 100644 (file)
@@ -46,7 +46,6 @@ static void numa_dispatch_value(int node, /* {{{ */
   vl.values = &v;
   vl.values_len = 1;
 
-  sstrncpy(vl.host, hostname_g, sizeof(vl.host));
   sstrncpy(vl.plugin, "numa", sizeof(vl.plugin));
   ssnprintf(vl.plugin_instance, sizeof(vl.plugin_instance), "node%i", node);
   sstrncpy(vl.type, "vmpage_action", sizeof(vl.type));
index d48bcaf..d40e095 100644 (file)
--- a/src/nut.c
+++ b/src/nut.c
@@ -107,13 +107,10 @@ static int nut_config(const char *key, const char *value) {
 
 static void nut_submit(nut_ups_t *ups, const char *type,
                        const char *type_instance, gauge_t value) {
-  value_t values[1];
   value_list_t vl = VALUE_LIST_INIT;
 
-  values[0].gauge = value;
-
-  vl.values = values;
-  vl.values_len = STATIC_ARRAY_SIZE(values);
+  vl.values = &(value_t){.gauge = value};
+  vl.values_len = 1;
   sstrncpy(vl.host,
            (strcasecmp(ups->hostname, "localhost") == 0) ? hostname_g
                                                          : ups->hostname,
index c58586d..d1caf46 100644 (file)
@@ -197,15 +197,11 @@ static FILE *olsrd_connect(void) /* {{{ */
 __attribute__((nonnull(2))) static void
 olsrd_submit(const char *plugin_instance, /* {{{ */
              const char *type, const char *type_instance, gauge_t value) {
-  value_t values[1];
   value_list_t vl = VALUE_LIST_INIT;
 
-  values[0].gauge = value;
-
-  vl.values = values;
+  vl.values = &(value_t){.gauge = value};
   vl.values_len = 1;
 
-  sstrncpy(vl.host, hostname_g, sizeof(vl.host));
   sstrncpy(vl.plugin, "olsrd", sizeof(vl.plugin));
   if (plugin_instance != NULL)
     sstrncpy(vl.plugin_instance, plugin_instance, sizeof(vl.plugin_instance));
index 2901b78..10fa5ca 100644 (file)
@@ -277,7 +277,6 @@ static int cow_load_config(const char *key, const char *value) {
 
 static int cow_read_values(const char *path, const char *name,
                            const ow_family_features_t *family_info) {
-  value_t values[1];
   value_list_t vl = VALUE_LIST_INIT;
   int success = 0;
 
@@ -287,10 +286,6 @@ static int cow_read_values(const char *path, const char *name,
       return 0;
   }
 
-  vl.values = values;
-  vl.values_len = 1;
-
-  sstrncpy(vl.host, hostname_g, sizeof(vl.host));
   sstrncpy(vl.plugin, "onewire", sizeof(vl.plugin));
   sstrncpy(vl.plugin_instance, name, sizeof(vl.plugin_instance));
 
@@ -298,6 +293,7 @@ static int cow_read_values(const char *path, const char *name,
     char *buffer;
     size_t buffer_size;
     int status;
+    char errbuf[1024];
 
     char file[4096];
     char *endptr;
@@ -311,14 +307,15 @@ static int cow_read_values(const char *path, const char *name,
     DEBUG("Start reading onewire device %s", file);
     status = OW_get(file, &buffer, &buffer_size);
     if (status < 0) {
-      ERROR("onewire plugin: OW_get (%s/%s) failed. status = %#x;", path,
-            family_info->features[i].filename, status);
+      ERROR("onewire plugin: OW_get (%s/%s) failed. error = %s;", path,
+            family_info->features[i].filename,
+            sstrerror(errno, errbuf, sizeof(errbuf)));
       return (-1);
     }
     DEBUG("Read onewire device %s as %s", file, buffer);
 
     endptr = NULL;
-    values[0].gauge = strtod(buffer, &endptr);
+    gauge_t g = strtod(buffer, &endptr);
     if (endptr == NULL) {
       ERROR("onewire plugin: Buffer is not a number: %s", buffer);
       continue;
@@ -328,6 +325,9 @@ static int cow_read_values(const char *path, const char *name,
     sstrncpy(vl.type_instance, family_info->features[i].type_instance,
              sizeof(vl.type_instance));
 
+    vl.values = &(value_t){.gauge = g};
+    vl.values_len = 1;
+
     plugin_dispatch_values(&vl);
     success++;
 
@@ -365,6 +365,7 @@ static int cow_read_bus(const char *path) {
   char *buffer;
   size_t buffer_size;
   int status;
+  char errbuf[1024];
 
   char *buffer_ptr;
   char *dummy;
@@ -373,7 +374,8 @@ static int cow_read_bus(const char *path) {
 
   status = OW_get(path, &buffer, &buffer_size);
   if (status < 0) {
-    ERROR("onewire plugin: OW_get (%s) failed. status = %#x;", path, status);
+    ERROR("onewire plugin: OW_get (%s) failed. error = %s;", path,
+          sstrerror(errno, errbuf, sizeof(errbuf)));
     return (-1);
   }
   DEBUG("onewire plugin: OW_get (%s) returned: %s", path, buffer);
@@ -420,34 +422,30 @@ static int cow_read_bus(const char *path) {
  */
 
 static int cow_simple_read(void) {
-  value_t values[1];
   value_list_t vl = VALUE_LIST_INIT;
   char *buffer;
   size_t buffer_size;
   int status;
+  char errbuf[1024];
   char *endptr;
   direct_access_element_t *traverse;
 
   /* traverse list and check entries */
   for (traverse = direct_list; traverse != NULL; traverse = traverse->next) {
-    vl.values = values;
-    vl.values_len = 1;
-
-    sstrncpy(vl.host, hostname_g, sizeof(vl.host));
     sstrncpy(vl.plugin, "onewire", sizeof(vl.plugin));
     sstrncpy(vl.plugin_instance, traverse->address, sizeof(vl.plugin_instance));
 
     status = OW_get(traverse->path, &buffer, &buffer_size);
     if (status < 0) {
-      ERROR("onewire plugin: OW_get (%s) failed. status = %#x;", traverse->path,
-            status);
+      ERROR("onewire plugin: OW_get (%s) failed. status = %s;", traverse->path,
+            sstrerror(errno, errbuf, sizeof(errbuf)));
       return (-1);
     }
     DEBUG("onewire plugin: Read onewire device %s as %s", traverse->path,
           buffer);
 
     endptr = NULL;
-    values[0].gauge = strtod(buffer, &endptr);
+    gauge_t g = strtod(buffer, &endptr);
     if (endptr == NULL) {
       ERROR("onewire plugin: Buffer is not a number: %s", buffer);
       continue;
@@ -456,6 +454,9 @@ static int cow_simple_read(void) {
     sstrncpy(vl.type, traverse->file, sizeof(vl.type));
     sstrncpy(vl.type_instance, "", sizeof(""));
 
+    vl.values = &(value_t){.gauge = g};
+    vl.values_len = 1;
+
     plugin_dispatch_values(&vl);
     free(buffer);
   } /* for (traverse) */
@@ -506,6 +507,7 @@ static int cow_shutdown(void) {
 
 static int cow_init(void) {
   int status;
+  char errbuf[1024];
 
   if (device_g == NULL) {
     ERROR("onewire plugin: cow_init: No device configured.");
@@ -515,7 +517,8 @@ static int cow_init(void) {
   DEBUG("onewire plugin: about to init device <%s>.", device_g);
   status = (int)OW_init(device_g);
   if (status != 0) {
-    ERROR("onewire plugin: OW_init(%s) failed: %i.", device_g, status);
+    ERROR("onewire plugin: OW_init(%s) failed: %s.", device_g,
+          sstrerror(errno, errbuf, sizeof(errbuf)));
     return (1);
   }
 
index c060bdc..50757db 100644 (file)
@@ -159,10 +159,7 @@ static void cldap_submit_value(const char *type,
   vl.values = &value;
   vl.values_len = 1;
 
-  if ((st->host == NULL) || (strcmp("", st->host) == 0) ||
-      (strcmp("localhost", st->host) == 0))
-    sstrncpy(vl.host, hostname_g, sizeof(vl.host));
-  else
+  if ((st->host != NULL) && (strcmp("localhost", st->host) != 0))
     sstrncpy(vl.host, st->host, sizeof(vl.host));
 
   sstrncpy(vl.plugin, "openldap", sizeof(vl.plugin));
@@ -179,17 +176,13 @@ static void cldap_submit_value(const char *type,
 static void cldap_submit_derive(const char *type,
                                 const char *type_instance, /* {{{ */
                                 derive_t d, cldap_t *st) {
-  value_t v;
-  v.derive = d;
-  cldap_submit_value(type, type_instance, v, st);
+  cldap_submit_value(type, type_instance, (value_t){.derive = d}, st);
 } /* }}} void cldap_submit_derive */
 
 static void cldap_submit_gauge(const char *type,
                                const char *type_instance, /* {{{ */
                                gauge_t g, cldap_t *st) {
-  value_t v;
-  v.gauge = g;
-  cldap_submit_value(type, type_instance, v, st);
+  cldap_submit_value(type, type_instance, (value_t){.gauge = g}, st);
 } /* }}} void cldap_submit_gauge */
 
 static int cldap_read_host(user_data_t *ud) /* {{{ */
@@ -414,7 +407,7 @@ static int cldap_config_add(oconfig_item_t *ci) /* {{{ */
   }
 
   st->starttls = 0;
-  st->timeout = (long)(CDTIME_T_TO_MS(plugin_get_interval()) / 1000);
+  st->timeout = (long)CDTIME_T_TO_TIME_T(plugin_get_interval());
   st->verifyhost = 1;
   st->version = LDAP_VERSION3;
 
@@ -491,13 +484,12 @@ static int cldap_config_add(oconfig_item_t *ci) /* {{{ */
                 (st->host != NULL) ? st->host : hostname_g,
                 (st->name != NULL) ? st->name : "default");
 
-      user_data_t ud = {.data = st};
-
       status = plugin_register_complex_read(/* group = */ NULL,
                                             /* name      = */ callback_name,
                                             /* callback  = */ cldap_read_host,
-                                            /* interval  = */ 0,
-                                            /* user_data = */ &ud);
+                                            /* interval  = */ 0, &(user_data_t){
+                                                                     .data = st,
+                                                                 });
     }
   }
 
index 11c119c..90694b7 100644 (file)
@@ -94,14 +94,10 @@ static int openvpn_strsplit(char *string, char **fields, size_t size) {
 /* dispatches number of users */
 static void numusers_submit(const char *pinst, const char *tinst,
                             gauge_t value) {
-  value_t values[1];
   value_list_t vl = VALUE_LIST_INIT;
 
-  values[0].gauge = value;
-
-  vl.values = values;
-  vl.values_len = STATIC_ARRAY_SIZE(values);
-  sstrncpy(vl.host, hostname_g, sizeof(vl.host));
+  vl.values = &(value_t){.gauge = value};
+  vl.values_len = 1;
   sstrncpy(vl.plugin, "openvpn", sizeof(vl.plugin));
   sstrncpy(vl.type, "users", sizeof(vl.type));
   if (pinst != NULL)
@@ -116,11 +112,10 @@ static void numusers_submit(const char *pinst, const char *tinst,
  * per single endpoint */
 static void iostats_submit(const char *pinst, const char *tinst, derive_t rx,
                            derive_t tx) {
-  value_t values[2];
   value_list_t vl = VALUE_LIST_INIT;
-
-  values[0].derive = rx;
-  values[1].derive = tx;
+  value_t values[] = {
+      {.derive = rx}, {.derive = tx},
+  };
 
   /* NOTE ON THE NEW NAMING SCHEMA:
    *       using plugin_instance to identify each vpn config (and
@@ -130,7 +125,6 @@ static void iostats_submit(const char *pinst, const char *tinst, derive_t rx,
 
   vl.values = values;
   vl.values_len = STATIC_ARRAY_SIZE(values);
-  sstrncpy(vl.host, hostname_g, sizeof(vl.host));
   sstrncpy(vl.plugin, "openvpn", sizeof(vl.plugin));
   if (pinst != NULL)
     sstrncpy(vl.plugin_instance, pinst, sizeof(vl.plugin_instance));
@@ -144,15 +138,13 @@ static void iostats_submit(const char *pinst, const char *tinst, derive_t rx,
 /* dispatches stats about data compression shown when in single mode */
 static void compression_submit(const char *pinst, const char *tinst,
                                derive_t uncompressed, derive_t compressed) {
-  value_t values[2];
   value_list_t vl = VALUE_LIST_INIT;
-
-  values[0].derive = uncompressed;
-  values[1].derive = compressed;
+  value_t values[] = {
+      {.derive = uncompressed}, {.derive = compressed},
+  };
 
   vl.values = values;
   vl.values_len = STATIC_ARRAY_SIZE(values);
-  sstrncpy(vl.host, hostname_g, sizeof(vl.host));
   sstrncpy(vl.plugin, "openvpn", sizeof(vl.plugin));
   if (pinst != NULL)
     sstrncpy(vl.plugin_instance, pinst, sizeof(vl.plugin_instance));
index d01f5c7..ea53fbf 100644 (file)
@@ -22,6 +22,7 @@
  *
  * Authors:
  *   Sebastian Harl <sh at tokkee.org>
+ *   Pavel Rochnyak <pavel2000 ngs.ru>
  **/
 
 /*
@@ -77,8 +78,9 @@
 #define PLUGIN_LOG 4
 #define PLUGIN_NOTIF 5
 #define PLUGIN_FLUSH 6
+#define PLUGIN_FLUSH_ALL 7 /* For collectd-5.6 only */
 
-#define PLUGIN_TYPES 7
+#define PLUGIN_TYPES 8
 
 #define PLUGIN_CONFIG 254
 #define PLUGIN_DATASET 255
 /* this is defined in DynaLoader.a */
 void boot_DynaLoader(PerlInterpreter *, CV *);
 
+static XS(Collectd_plugin_register_read);
+static XS(Collectd_plugin_register_write);
+static XS(Collectd_plugin_register_log);
+static XS(Collectd_plugin_register_notification);
+static XS(Collectd_plugin_register_flush);
+static XS(Collectd_plugin_unregister_read);
+static XS(Collectd_plugin_unregister_write);
+static XS(Collectd_plugin_unregister_log);
+static XS(Collectd_plugin_unregister_notification);
+static XS(Collectd_plugin_unregister_flush);
 static XS(Collectd_plugin_register_ds);
 static XS(Collectd_plugin_unregister_ds);
 static XS(Collectd_plugin_dispatch_values);
@@ -113,6 +125,14 @@ static XS(Collectd_plugin_log);
 static XS(Collectd__fc_register);
 static XS(Collectd_call_by_name);
 
+static int perl_read(user_data_t *ud);
+static int perl_write(const data_set_t *ds, const value_list_t *vl,
+                      user_data_t *user_data);
+static void perl_log(int level, const char *msg, user_data_t *user_data);
+static int perl_notify(const notification_t *notif, user_data_t *user_data);
+static int perl_flush(cdtime_t timeout, const char *identifier,
+                      user_data_t *user_data);
+
 /*
  * private data types
  */
@@ -165,6 +185,8 @@ extern char **environ;
  * private variables
  */
 
+static _Bool register_legacy_flush = 1;
+
 /* if perl_threads != NULL perl_threads->head must
  * point to the "base" thread */
 static c_ithread_list_t *perl_threads = NULL;
@@ -181,6 +203,18 @@ static struct {
   char name[64];
   XS((*f));
 } api[] = {
+    {"Collectd::plugin_register_read", Collectd_plugin_register_read},
+    {"Collectd::plugin_register_write", Collectd_plugin_register_write},
+    {"Collectd::plugin_register_log", Collectd_plugin_register_log},
+    {"Collectd::plugin_register_notification",
+     Collectd_plugin_register_notification},
+    {"Collectd::plugin_register_flush", Collectd_plugin_register_flush},
+    {"Collectd::plugin_unregister_read", Collectd_plugin_unregister_read},
+    {"Collectd::plugin_unregister_write", Collectd_plugin_unregister_write},
+    {"Collectd::plugin_unregister_log", Collectd_plugin_unregister_log},
+    {"Collectd::plugin_unregister_notification",
+     Collectd_plugin_unregister_notification},
+    {"Collectd::plugin_unregister_flush", Collectd_plugin_unregister_flush},
     {"Collectd::plugin_register_data_set", Collectd_plugin_register_ds},
     {"Collectd::plugin_unregister_data_set", Collectd_plugin_unregister_ds},
     {"Collectd::plugin_dispatch_values", Collectd_plugin_dispatch_values},
@@ -973,7 +1007,7 @@ static int call_pv_locked(pTHX_ const char *sub_name) {
     return 0;
   }
 
-  ret = call_pv(sub_name, G_SCALAR);
+  ret = call_pv(sub_name, G_SCALAR | G_EVAL);
 
   t->running = old_running;
   return ret;
@@ -982,11 +1016,12 @@ static int call_pv_locked(pTHX_ const char *sub_name) {
 /*
  * Call all working functions of the given type.
  */
-static int pplugin_call_all(pTHX_ int type, ...) {
+static int pplugin_call(pTHX_ int type, ...) {
   int retvals = 0;
 
   va_list ap;
   int ret = 0;
+  char *subname;
 
   dSP;
 
@@ -1000,9 +1035,16 @@ static int pplugin_call_all(pTHX_ int type, ...) {
 
   PUSHMARK(SP);
 
-  XPUSHs(sv_2mortal(newSViv((IV)type)));
+  if (PLUGIN_READ == type) {
+    subname = va_arg(ap, char *);
+  } else if (PLUGIN_WRITE == type) {
+    data_set_t *ds;
+    value_list_t *vl;
 
-  if (PLUGIN_WRITE == type) {
+    AV *pds = newAV();
+    HV *pvl = newHV();
+
+    subname = va_arg(ap, char *);
     /*
      * $_[0] = $plugin_type;
      *
@@ -1028,12 +1070,6 @@ static int pplugin_call_all(pTHX_ int type, ...) {
      *   type_instance   => $type_instance
      * };
      */
-    data_set_t *ds;
-    value_list_t *vl;
-
-    AV *pds = newAV();
-    HV *pvl = newHV();
-
     ds = va_arg(ap, data_set_t *);
     vl = va_arg(ap, value_list_t *);
 
@@ -1055,6 +1091,7 @@ static int pplugin_call_all(pTHX_ int type, ...) {
     XPUSHs(sv_2mortal(newRV_noinc((SV *)pds)));
     XPUSHs(sv_2mortal(newRV_noinc((SV *)pvl)));
   } else if (PLUGIN_LOG == type) {
+    subname = va_arg(ap, char *);
     /*
      * $_[0] = $level;
      *
@@ -1063,6 +1100,10 @@ static int pplugin_call_all(pTHX_ int type, ...) {
     XPUSHs(sv_2mortal(newSViv(va_arg(ap, int))));
     XPUSHs(sv_2mortal(newSVpv(va_arg(ap, char *), 0)));
   } else if (PLUGIN_NOTIF == type) {
+    notification_t *n;
+    HV *notif = newHV();
+
+    subname = va_arg(ap, char *);
     /*
      * $_[0] =
      * {
@@ -1076,9 +1117,6 @@ static int pplugin_call_all(pTHX_ int type, ...) {
      *   type_instance   => $type_instance
      * };
      */
-    notification_t *n;
-    HV *notif = newHV();
-
     n = va_arg(ap, notification_t *);
 
     if (-1 == notification2hv(aTHX_ n, notif)) {
@@ -1091,23 +1129,48 @@ static int pplugin_call_all(pTHX_ int type, ...) {
     XPUSHs(sv_2mortal(newRV_noinc((SV *)notif)));
   } else if (PLUGIN_FLUSH == type) {
     cdtime_t timeout;
+    subname = va_arg(ap, char *);
+    /*
+     * $_[0] = $timeout;
+     * $_[1] = $identifier;
+     */
+    timeout = va_arg(ap, cdtime_t);
 
+    XPUSHs(sv_2mortal(newSVnv(CDTIME_T_TO_DOUBLE(timeout))));
+    XPUSHs(sv_2mortal(newSVpv(va_arg(ap, char *), 0)));
+  } else if (PLUGIN_FLUSH_ALL == type) {
+    cdtime_t timeout;
+    subname = "Collectd::plugin_call_all";
     /*
      * $_[0] = $timeout;
      * $_[1] = $identifier;
      */
     timeout = va_arg(ap, cdtime_t);
 
+    XPUSHs(sv_2mortal(newSViv((IV)PLUGIN_FLUSH)));
     XPUSHs(sv_2mortal(newSVnv(CDTIME_T_TO_DOUBLE(timeout))));
     XPUSHs(sv_2mortal(newSVpv(va_arg(ap, char *), 0)));
+  } else if (PLUGIN_INIT == type) {
+    subname = "Collectd::plugin_call_all";
+    XPUSHs(sv_2mortal(newSViv((IV)type)));
+  } else if (PLUGIN_SHUTDOWN == type) {
+    subname = "Collectd::plugin_call_all";
+    XPUSHs(sv_2mortal(newSViv((IV)type)));
+  } else { /* Unknown type. Run 'plugin_call_all' and make compiler happy */
+    subname = "Collectd::plugin_call_all";
+    XPUSHs(sv_2mortal(newSViv((IV)type)));
   }
 
   PUTBACK;
 
-  retvals = call_pv_locked(aTHX_ "Collectd::plugin_call_all");
+  retvals = call_pv_locked(aTHX_ subname);
 
   SPAGAIN;
-  if (0 < retvals) {
+  if (SvTRUE(ERRSV)) {
+    if (PLUGIN_LOG != type)
+      ERROR("perl: %s error: %s", subname, SvPV_nolen(ERRSV));
+    ret = -1;
+  } else if (0 < retvals) {
     SV *tmp = POPs;
     if (!SvTRUE(tmp))
       ret = -1;
@@ -1119,7 +1182,7 @@ static int pplugin_call_all(pTHX_ int type, ...) {
 
   va_end(ap);
   return ret;
-} /* static int pplugin_call_all (int, ...) */
+} /* static int pplugin_call (int, ...) */
 
 /*
  * collectd's Perl interpreter based thread implementation.
@@ -1134,6 +1197,10 @@ static void c_ithread_destroy(c_ithread_t *ithread) {
   assert(NULL != perl_threads);
 
   PERL_SET_CONTEXT(aTHX);
+  /* Mark as running to avoid deadlock:
+     c_ithread_destroy -> log_debug -> perl_log()
+  */
+  ithread->running = 1;
   log_debug("Shutting down Perl interpreter %p...", aTHX);
 
 #if COLLECT_DEBUG
@@ -1347,7 +1414,10 @@ static int fc_call(pTHX_ int type, int cb_type, pfc_user_data_t *data, ...) {
   }
 
   SPAGAIN;
-  if (0 < retvals) {
+  if (SvTRUE(ERRSV)) {
+    ERROR("perl: Collectd::fc_call error: %s", SvPV_nolen(ERRSV));
+    ret = -1;
+  } else if (0 < retvals) {
     SV *tmp = POPs;
 
     /* the exec callbacks return a status, while
@@ -1503,6 +1573,163 @@ static target_proc_t ptarget = {ptarget_create, ptarget_destroy,
  * Exported Perl API.
  */
 
+static void _plugin_register_generic_userdata(pTHX, int type,
+                                              const char *desc) {
+  int ret = 0;
+  user_data_t userdata;
+  char *pluginname;
+
+  dXSARGS;
+
+  if (2 != items) {
+    log_err("Usage: Collectd::plugin_register_%s(pluginname, subname)", desc);
+    XSRETURN_EMPTY;
+  }
+
+  if (!SvOK(ST(0))) {
+    log_err("Collectd::plugin_register_%s(pluginname, subname): "
+            "Invalid pluginname",
+            desc);
+    XSRETURN_EMPTY;
+  }
+  if (!SvOK(ST(1))) {
+    log_err("Collectd::plugin_register_%s(pluginname, subname): "
+            "Invalid subname",
+            desc);
+    XSRETURN_EMPTY;
+  }
+
+  /* Use pluginname as-is to allow flush a single perl plugin */
+  pluginname = SvPV_nolen(ST(0));
+
+  log_debug("Collectd::plugin_register_%s: "
+            "plugin = \"%s\", sub = \"%s\"",
+            desc, pluginname, SvPV_nolen(ST(1)));
+
+  memset(&userdata, 0, sizeof(userdata));
+  userdata.data = strdup(SvPV_nolen(ST(1)));
+  userdata.free_func = free;
+
+  if (PLUGIN_READ == type) {
+    ret = plugin_register_complex_read(
+        "perl",                                       /* group */
+        pluginname, perl_read, plugin_get_interval(), /* Default interval */
+        &userdata);
+  } else if (PLUGIN_WRITE == type) {
+    ret = plugin_register_write(pluginname, perl_write, &userdata);
+  } else if (PLUGIN_LOG == type) {
+    ret = plugin_register_log(pluginname, perl_log, &userdata);
+  } else if (PLUGIN_NOTIF == type) {
+    ret = plugin_register_notification(pluginname, perl_notify, &userdata);
+  } else if (PLUGIN_FLUSH == type) {
+    if (1 == register_legacy_flush) { /* For collectd-5.7 only, #1731 */
+      register_legacy_flush = 0;
+      ret = plugin_register_flush("perl", perl_flush, /* user_data = */ NULL);
+    }
+
+    if (0 == ret)
+      ret = plugin_register_flush(pluginname, perl_flush, &userdata);
+  } else {
+    ret = -1;
+  }
+
+  if (0 == ret)
+    XSRETURN_YES;
+  else {
+    free(userdata.data);
+    XSRETURN_EMPTY;
+  }
+} /* static void _plugin_register_generic_userdata ( ... ) */
+
+/*
+ * Collectd::plugin_register_TYPE (pluginname, subname).
+ *
+ * pluginname:
+ *   name of the perl plugin
+ *
+ * subname:
+ *   name of the plugin's subroutine that does the work
+ */
+
+static XS(Collectd_plugin_register_read) {
+  return _plugin_register_generic_userdata(aTHX, PLUGIN_READ, "read");
+}
+
+static XS(Collectd_plugin_register_write) {
+  return _plugin_register_generic_userdata(aTHX, PLUGIN_WRITE, "write");
+}
+
+static XS(Collectd_plugin_register_log) {
+  return _plugin_register_generic_userdata(aTHX, PLUGIN_LOG, "log");
+}
+
+static XS(Collectd_plugin_register_notification) {
+  return _plugin_register_generic_userdata(aTHX, PLUGIN_NOTIF, "notification");
+}
+
+static XS(Collectd_plugin_register_flush) {
+  return _plugin_register_generic_userdata(aTHX, PLUGIN_FLUSH, "flush");
+}
+
+typedef int perl_unregister_function_t(const char *name);
+
+static void _plugin_unregister_generic(pTHX, perl_unregister_function_t *unreg,
+                                       const char *desc) {
+  dXSARGS;
+
+  if (1 != items) {
+    log_err("Usage: Collectd::plugin_unregister_%s(pluginname)", desc);
+    XSRETURN_EMPTY;
+  }
+
+  if (!SvOK(ST(0))) {
+    log_err("Collectd::plugin_unregister_%s(pluginname): "
+            "Invalid pluginname",
+            desc);
+    XSRETURN_EMPTY;
+  }
+
+  log_debug("Collectd::plugin_unregister_%s: plugin = \"%s\"", desc,
+            SvPV_nolen(ST(0)));
+
+  unreg(SvPV_nolen(ST(0)));
+
+  XSRETURN_EMPTY;
+
+  return;
+} /* static void _plugin_unregister_generic ( ... ) */
+
+/*
+ * Collectd::plugin_unregister_TYPE (pluginname).
+ *
+ * TYPE:
+ *   type of callback to be unregistered: read, write, log, notification, flush
+ *
+ * pluginname:
+ *   name of the perl plugin
+ */
+
+static XS(Collectd_plugin_unregister_read) {
+  return _plugin_unregister_generic(aTHX, plugin_unregister_read, "read");
+}
+
+static XS(Collectd_plugin_unregister_write) {
+  return _plugin_unregister_generic(aTHX, plugin_unregister_write, "write");
+}
+
+static XS(Collectd_plugin_unregister_log) {
+  return _plugin_unregister_generic(aTHX, plugin_unregister_log, "log");
+}
+
+static XS(Collectd_plugin_unregister_notification) {
+  return _plugin_unregister_generic(aTHX, plugin_unregister_notification,
+                                    "notification");
+}
+
+static XS(Collectd_plugin_unregister_flush) {
+  return _plugin_unregister_generic(aTHX, plugin_unregister_flush, "flush");
+}
+
 /*
  * Collectd::plugin_register_data_set (type, dataset).
  *
@@ -1878,14 +2105,14 @@ static int perl_init(void) {
   assert(aTHX == perl_threads->head->interp);
   pthread_mutex_lock(&perl_threads->mutex);
 
-  status = pplugin_call_all(aTHX_ PLUGIN_INIT);
+  status = pplugin_call(aTHX_ PLUGIN_INIT);
 
   pthread_mutex_unlock(&perl_threads->mutex);
 
   return status;
 } /* static int perl_init (void) */
 
-static int perl_read(void) {
+static int perl_read(user_data_t *user_data) {
   dTHX;
 
   if (NULL == perl_threads)
@@ -1908,11 +2135,12 @@ static int perl_read(void) {
 
   log_debug("perl_read: c_ithread: interp = %p (active threads: %i)", aTHX,
             perl_threads->number_of_threads);
-  return pplugin_call_all(aTHX_ PLUGIN_READ);
-} /* static int perl_read (void) */
+
+  return pplugin_call(aTHX_ PLUGIN_READ, user_data->data);
+} /* static int perl_read (user_data_t *user_data) */
 
 static int perl_write(const data_set_t *ds, const value_list_t *vl,
-                      user_data_t __attribute__((unused)) * user_data) {
+                      user_data_t *user_data) {
   int status;
   dTHX;
 
@@ -1937,7 +2165,7 @@ static int perl_write(const data_set_t *ds, const value_list_t *vl,
 
   log_debug("perl_write: c_ithread: interp = %p (active threads: %i)", aTHX,
             perl_threads->number_of_threads);
-  status = pplugin_call_all(aTHX_ PLUGIN_WRITE, ds, vl);
+  status = pplugin_call(aTHX_ PLUGIN_WRITE, user_data->data, ds, vl);
 
   if (aTHX == perl_threads->head->interp)
     pthread_mutex_unlock(&perl_threads->mutex);
@@ -1945,8 +2173,7 @@ static int perl_write(const data_set_t *ds, const value_list_t *vl,
   return status;
 } /* static int perl_write (const data_set_t *, const value_list_t *) */
 
-static void perl_log(int level, const char *msg,
-                     user_data_t __attribute__((unused)) * user_data) {
+static void perl_log(int level, const char *msg, user_data_t *user_data) {
   dTHX;
 
   if (NULL == perl_threads)
@@ -1970,7 +2197,7 @@ static void perl_log(int level, const char *msg,
   if (aTHX == perl_threads->head->interp)
     pthread_mutex_lock(&perl_threads->mutex);
 
-  pplugin_call_all(aTHX_ PLUGIN_LOG, level, msg);
+  pplugin_call(aTHX_ PLUGIN_LOG, user_data->data, level, msg);
 
   if (aTHX == perl_threads->head->interp)
     pthread_mutex_unlock(&perl_threads->mutex);
@@ -1978,8 +2205,7 @@ static void perl_log(int level, const char *msg,
   return;
 } /* static void perl_log (int, const char *) */
 
-static int perl_notify(const notification_t *notif,
-                       user_data_t __attribute__((unused)) * user_data) {
+static int perl_notify(const notification_t *notif, user_data_t *user_data) {
   dTHX;
 
   if (NULL == perl_threads)
@@ -1994,11 +2220,11 @@ static int perl_notify(const notification_t *notif,
 
     aTHX = t->interp;
   }
-  return pplugin_call_all(aTHX_ PLUGIN_NOTIF, notif);
+  return pplugin_call(aTHX_ PLUGIN_NOTIF, user_data->data, notif);
 } /* static int perl_notify (const notification_t *) */
 
 static int perl_flush(cdtime_t timeout, const char *identifier,
-                      user_data_t __attribute__((unused)) * user_data) {
+                      user_data_t *user_data) {
   dTHX;
 
   if (NULL == perl_threads)
@@ -2013,7 +2239,12 @@ static int perl_flush(cdtime_t timeout, const char *identifier,
 
     aTHX = t->interp;
   }
-  return pplugin_call_all(aTHX_ PLUGIN_FLUSH, timeout, identifier);
+
+  /* For collectd-5.6 only, #1731 */
+  if (user_data == NULL || user_data->data == NULL)
+    return pplugin_call(aTHX_ PLUGIN_FLUSH_ALL, timeout, identifier);
+
+  return pplugin_call(aTHX_ PLUGIN_FLUSH, user_data->data, timeout, identifier);
 } /* static int perl_flush (const int) */
 
 static int perl_shutdown(void) {
@@ -2023,6 +2254,7 @@ static int perl_shutdown(void) {
   dTHX;
 
   plugin_unregister_complex_config("perl");
+  plugin_unregister_read_group("perl");
 
   if (NULL == perl_threads)
     return 0;
@@ -2038,14 +2270,10 @@ static int perl_shutdown(void) {
   log_debug("perl_shutdown: c_ithread: interp = %p (active threads: %i)", aTHX,
             perl_threads->number_of_threads);
 
-  plugin_unregister_log("perl");
-  plugin_unregister_notification("perl");
   plugin_unregister_init("perl");
-  plugin_unregister_read("perl");
-  plugin_unregister_write("perl");
-  plugin_unregister_flush("perl");
+  plugin_unregister_flush("perl"); /* For collectd-5.6 only, #1731 */
 
-  ret = pplugin_call_all(aTHX_ PLUGIN_SHUTDOWN);
+  ret = pplugin_call(aTHX_ PLUGIN_SHUTDOWN);
 
   pthread_mutex_lock(&perl_threads->mutex);
   t = perl_threads->tail;
@@ -2248,15 +2476,7 @@ static int init_pi(int argc, char **argv) {
 
   perl_run(aTHX);
 
-  plugin_register_log("perl", perl_log, /* user_data = */ NULL);
-  plugin_register_notification("perl", perl_notify,
-                               /* user_data = */ NULL);
   plugin_register_init("perl", perl_init);
-
-  plugin_register_read("perl", perl_read);
-
-  plugin_register_write("perl", perl_write, /* user_data = */ NULL);
-  plugin_register_flush("perl", perl_flush, /* user_data = */ NULL);
   plugin_register_shutdown("perl", perl_shutdown);
   return 0;
 } /* static int init_pi (const char **, const int) */
@@ -2466,6 +2686,8 @@ static int perl_config(oconfig_item_t *ci) {
       current_status = perl_config_includedir(aTHX_ c);
     else if (0 == strcasecmp(c->key, "Plugin"))
       current_status = perl_config_plugin(aTHX_ c);
+    else if (0 == strcasecmp(c->key, "RegisterLegacyFlush"))
+      cf_util_get_boolean(c, &register_legacy_flush);
     else {
       log_warn("Ignoring unknown config key \"%s\".", c->key);
       current_status = 0;
index 9dd5f93..533dffe 100644 (file)
--- a/src/pf.c
+++ b/src/pf.c
@@ -69,7 +69,6 @@ static void pf_submit(char const *type, char const *type_instance, uint64_t val,
 
   vl.values = values;
   vl.values_len = 1;
-  sstrncpy(vl.host, hostname_g, sizeof(vl.host));
   sstrncpy(vl.plugin, "pf", sizeof(vl.plugin));
   sstrncpy(vl.type, type, sizeof(vl.type));
   sstrncpy(vl.type_instance, type_instance, sizeof(vl.type_instance));
index 227e9dc..7fd23a0 100644 (file)
@@ -591,7 +591,7 @@ static int plugin_init(void) /* {{{ */
 
   status = plugin_thread_create(&collector_thread_id,
                                 /* attrs = */ NULL, collector_thread,
-                                /* args = */ NULL);
+                                /* args = */ NULL, "pinba collector");
   if (status != 0) {
     char errbuf[1024];
     ERROR("pinba plugin: pthread_create(3) failed: %s",
@@ -627,38 +627,38 @@ static int plugin_shutdown(void) /* {{{ */
 
 static int plugin_submit(const pinba_statnode_t *res) /* {{{ */
 {
-  value_t value;
   value_list_t vl = VALUE_LIST_INIT;
 
-  vl.values = &value;
   vl.values_len = 1;
-  sstrncpy(vl.host, hostname_g, sizeof(vl.host));
   sstrncpy(vl.plugin, "pinba", sizeof(vl.plugin));
   sstrncpy(vl.plugin_instance, res->name, sizeof(vl.plugin_instance));
 
-  value.derive = res->req_count;
+  vl.values = &(value_t){.derive = res->req_count};
   sstrncpy(vl.type, "total_requests", sizeof(vl.type));
   plugin_dispatch_values(&vl);
 
-  value.derive = float_counter_get(&res->req_time, /* factor = */ 1000);
+  vl.values = &(value_t){
+      .derive = float_counter_get(&res->req_time, /* factor = */ 1000)};
   sstrncpy(vl.type, "total_time_in_ms", sizeof(vl.type));
   plugin_dispatch_values(&vl);
 
-  value.derive = res->doc_size;
+  vl.values = &(value_t){.derive = res->doc_size};
   sstrncpy(vl.type, "total_bytes", sizeof(vl.type));
   plugin_dispatch_values(&vl);
 
-  value.derive = float_counter_get(&res->ru_utime, /* factor = */ 100);
+  vl.values = &(value_t){
+      .derive = float_counter_get(&res->ru_utime, /* factor = */ 100)};
   sstrncpy(vl.type, "cpu", sizeof(vl.type));
   sstrncpy(vl.type_instance, "user", sizeof(vl.type_instance));
   plugin_dispatch_values(&vl);
 
-  value.derive = float_counter_get(&res->ru_stime, /* factor = */ 100);
+  vl.values = &(value_t){
+      .derive = float_counter_get(&res->ru_stime, /* factor = */ 100)};
   sstrncpy(vl.type, "cpu", sizeof(vl.type));
   sstrncpy(vl.type_instance, "system", sizeof(vl.type_instance));
   plugin_dispatch_values(&vl);
 
-  value.gauge = res->mem_peak;
+  vl.values = &(value_t){.gauge = res->mem_peak};
   sstrncpy(vl.type, "memory", sizeof(vl.type));
   sstrncpy(vl.type_instance, "peak", sizeof(vl.type_instance));
   plugin_dispatch_values(&vl);
index e3608c3..82de59f 100644 (file)
@@ -359,7 +359,7 @@ static int start_thread(void) /* {{{ */
   ping_thread_loop = 1;
   ping_thread_error = 0;
   status = plugin_thread_create(&ping_thread_id, /* attr = */ NULL, ping_thread,
-                                /* arg = */ (void *)0);
+                                /* arg = */ (void *)0, "ping");
   if (status != 0) {
     ping_thread_loop = 0;
     ERROR("ping plugin: Starting thread failed.");
@@ -554,16 +554,11 @@ static int ping_config(const char *key, const char *value) /* {{{ */
 
 static void submit(const char *host, const char *type, /* {{{ */
                    gauge_t value) {
-  value_t values[1];
   value_list_t vl = VALUE_LIST_INIT;
 
-  values[0].gauge = value;
-
-  vl.values = values;
+  vl.values = &(value_t){.gauge = value};
   vl.values_len = 1;
-  sstrncpy(vl.host, hostname_g, sizeof(vl.host));
   sstrncpy(vl.plugin, "ping", sizeof(vl.plugin));
-  sstrncpy(vl.plugin_instance, "", sizeof(vl.plugin_instance));
   sstrncpy(vl.type_instance, host, sizeof(vl.type_instance));
   sstrncpy(vl.type, type, sizeof(vl.type));
 
index 891cf0d..2af8988 100644 (file)
@@ -790,7 +790,7 @@ static int c_psql_write(const data_set_t *ds, const value_list_t *vl,
   assert(db->database != NULL);
   assert(db->writers != NULL);
 
-  if (rfc3339nano(time_str, sizeof(time_str), vl->time) != 0) {
+  if (rfc3339nano_local(time_str, sizeof(time_str), vl->time) != 0) {
     log_err("c_psql_write: Failed to convert time to RFC 3339 format");
     return -1;
   }
index 5f7a542..f2149ef 100644 (file)
     ERROR("powerdns plugin: %s failed: %s", func,                              \
           sstrerror(errno, errbuf, sizeof(errbuf)));                           \
   } while (0)
+#define SOCK_ERROR(func, sockpath)                                             \
+  do {                                                                         \
+    char errbuf[1024];                                                         \
+    ERROR("powerdns plugin: Socket `%s` %s failed: %s", sockpath, func,        \
+          sstrerror(errno, errbuf, sizeof(errbuf)));                           \
+  } while (0)
 
 #define SERVER_SOCKET LOCALSTATEDIR "/run/pdns.controlsocket"
 #define SERVER_COMMAND "SHOW * \n"
@@ -314,9 +320,9 @@ static char *local_sockpath = NULL;
 
 /* <https://doc.powerdns.com/md/recursor/stats/> */
 static void submit(const char *plugin_instance, /* {{{ */
-                   const char *pdns_type, const char *value) {
+                   const char *pdns_type, const char *value_str) {
   value_list_t vl = VALUE_LIST_INIT;
-  value_t values[1];
+  value_t value;
 
   const char *type = NULL;
   const char *type_instance = NULL;
@@ -330,7 +336,7 @@ static void submit(const char *plugin_instance, /* {{{ */
 
   if (i >= lookup_table_length) {
     INFO("powerdns plugin: submit: Not found in lookup table: %s = %s;",
-         pdns_type, value);
+         pdns_type, value_str);
     return;
   }
 
@@ -355,16 +361,15 @@ static void submit(const char *plugin_instance, /* {{{ */
     return;
   }
 
-  if (0 != parse_value(value, &values[0], ds->ds[0].type)) {
+  if (0 != parse_value(value_str, &value, ds->ds[0].type)) {
     ERROR("powerdns plugin: Cannot convert `%s' "
           "to a number.",
-          value);
+          value_str);
     return;
   }
 
-  vl.values = values;
+  vl.values = &value;
   vl.values_len = 1;
-  sstrncpy(vl.host, hostname_g, sizeof(vl.host));
   sstrncpy(vl.plugin, "powerdns", sizeof(vl.plugin));
   sstrncpy(vl.type, type, sizeof(vl.type));
   if (type_instance != NULL)
@@ -385,7 +390,6 @@ static int powerdns_get_data_dgram(list_item_t *item, /* {{{ */
 
   struct sockaddr_un sa_unix = {0};
 
-  struct timeval stv_timeout;
   cdtime_t cdt_timeout;
 
   sd = socket(PF_UNIX, item->socktype, 0);
@@ -401,7 +405,7 @@ static int powerdns_get_data_dgram(list_item_t *item, /* {{{ */
 
   status = unlink(sa_unix.sun_path);
   if ((status != 0) && (errno != ENOENT)) {
-    FUNC_ERROR("unlink");
+    SOCK_ERROR("unlink", sa_unix.sun_path);
     close(sd);
     return (-1);
   }
@@ -412,14 +416,14 @@ static int powerdns_get_data_dgram(list_item_t *item, /* {{{ */
      * and otherwise the daemon cannot answer. */
     status = bind(sd, (struct sockaddr *)&sa_unix, sizeof(sa_unix));
     if (status != 0) {
-      FUNC_ERROR("bind");
+      SOCK_ERROR("bind", sa_unix.sun_path);
       break;
     }
 
     /* Make the socket writeable by the daemon.. */
     status = chmod(sa_unix.sun_path, 0666);
     if (status != 0) {
-      FUNC_ERROR("chmod");
+      SOCK_ERROR("chmod", sa_unix.sun_path);
       break;
     }
 
@@ -427,31 +431,30 @@ static int powerdns_get_data_dgram(list_item_t *item, /* {{{ */
     if (cdt_timeout < TIME_T_TO_CDTIME_T(2))
       cdt_timeout = TIME_T_TO_CDTIME_T(2);
 
-    CDTIME_T_TO_TIMEVAL(cdt_timeout, &stv_timeout);
-
-    status = setsockopt(sd, SOL_SOCKET, SO_RCVTIMEO, &stv_timeout,
-                        sizeof(stv_timeout));
+    status =
+        setsockopt(sd, SOL_SOCKET, SO_RCVTIMEO,
+                   &CDTIME_T_TO_TIMEVAL(cdt_timeout), sizeof(struct timeval));
     if (status != 0) {
-      FUNC_ERROR("setsockopt");
+      SOCK_ERROR("setsockopt", sa_unix.sun_path);
       break;
     }
 
     status =
         connect(sd, (struct sockaddr *)&item->sockaddr, sizeof(item->sockaddr));
     if (status != 0) {
-      FUNC_ERROR("connect");
+      SOCK_ERROR("connect", sa_unix.sun_path);
       break;
     }
 
     status = send(sd, item->command, strlen(item->command), 0);
     if (status < 0) {
-      FUNC_ERROR("send");
+      SOCK_ERROR("send", sa_unix.sun_path);
       break;
     }
 
     status = recv(sd, temp, sizeof(temp), /* flags = */ 0);
     if (status < 0) {
-      FUNC_ERROR("recv");
+      SOCK_ERROR("recv", sa_unix.sun_path);
       break;
     }
     buffer_size = status + 1;
@@ -509,7 +512,7 @@ static int powerdns_get_data_stream(list_item_t *item, /* {{{ */
   status =
       connect(sd, (struct sockaddr *)&item->sockaddr, sizeof(item->sockaddr));
   if (status != 0) {
-    FUNC_ERROR("connect");
+    SOCK_ERROR("connect", item->sockaddr.sun_path);
     close(sd);
     return (-1);
   }
@@ -518,7 +521,7 @@ static int powerdns_get_data_stream(list_item_t *item, /* {{{ */
   status = send(sd, item->command, strlen(item->command) + 1,
                 /* flags = */ 0);
   if (status < 0) {
-    FUNC_ERROR("send");
+    SOCK_ERROR("send", item->sockaddr.sun_path);
     close(sd);
     return (-1);
   }
@@ -528,7 +531,7 @@ static int powerdns_get_data_stream(list_item_t *item, /* {{{ */
 
     status = recv(sd, temp, sizeof(temp), /* flags = */ 0);
     if (status < 0) {
-      FUNC_ERROR("recv");
+      SOCK_ERROR("recv", item->sockaddr.sun_path);
       break;
     } else if (status == 0)
       break;
index 63c3c0e..87c96c8 100644 (file)
@@ -171,13 +171,9 @@ typedef struct procstat_entry_s {
   unsigned long vmem_code;
   unsigned long stack_size;
 
-  unsigned long vmem_minflt;
-  unsigned long vmem_majflt;
   derive_t vmem_minflt_counter;
   derive_t vmem_majflt_counter;
 
-  unsigned long cpu_user;
-  unsigned long cpu_system;
   derive_t cpu_user_counter;
   derive_t cpu_system_counter;
 
@@ -186,9 +182,11 @@ typedef struct procstat_entry_s {
   derive_t io_wchar;
   derive_t io_syscr;
   derive_t io_syscw;
+  _Bool has_io;
 
   derive_t cswitch_vol;
   derive_t cswitch_invol;
+  _Bool has_cswitch;
 
   struct procstat_entry_s *next;
 } procstat_entry_t;
@@ -229,6 +227,7 @@ typedef struct procstat {
 
 static procstat_t *list_head_g = NULL;
 
+static _Bool want_init = 1;
 static _Bool report_ctx_switch = 0;
 
 #if HAVE_THREAD_INFO
@@ -241,6 +240,7 @@ static mach_msg_type_number_t pset_list_len;
 
 #elif KERNEL_LINUX
 static long pagesize_g;
+static void ps_fill_details(const procstat_t *ps, procstat_entry_t *entry);
 /* #endif KERNEL_LINUX */
 
 #elif HAVE_LIBKVM_GETPROCS &&                                                  \
@@ -361,23 +361,22 @@ static int ps_list_match(const char *name, const char *cmdline,
   return (0);
 } /* int ps_list_match */
 
-static void ps_update_counter(_Bool init, derive_t *group_counter,
-                              derive_t *curr_counter, unsigned long *curr_value,
-                              derive_t new_counter, unsigned long new_value) {
-  if (init) {
-    *curr_value = new_value;
-    *curr_counter += new_value;
-    *group_counter += new_value;
+static void ps_update_counter(derive_t *group_counter, derive_t *curr_counter,
+                              derive_t new_counter) {
+  unsigned long curr_value;
+
+  if (want_init) {
+    *curr_counter = new_counter;
     return;
   }
 
   if (new_counter < *curr_counter)
-    *curr_value = new_counter + (ULONG_MAX - *curr_counter);
+    curr_value = new_counter + (ULONG_MAX - *curr_counter);
   else
-    *curr_value = new_counter - *curr_counter;
+    curr_value = new_counter - *curr_counter;
 
   *curr_counter = new_counter;
-  *group_counter += *curr_value;
+  *group_counter += curr_value;
 }
 
 /* add process entry to 'instances' of process 'name' (or refresh it) */
@@ -389,11 +388,13 @@ static void ps_list_add(const char *name, const char *cmdline,
     return;
 
   for (procstat_t *ps = list_head_g; ps != NULL; ps = ps->next) {
-    _Bool want_init;
-
     if ((ps_list_match(name, cmdline, ps)) == 0)
       continue;
 
+#if KERNEL_LINUX
+    ps_fill_details(ps, entry);
+#endif
+
     for (pse = ps->instances; pse != NULL; pse = pse->next)
       if ((pse->id == entry->id) || (pse->next == NULL))
         break;
@@ -445,22 +446,15 @@ static void ps_list_add(const char *name, const char *cmdline,
     ps->cswitch_vol += ((pse->cswitch_vol == -1) ? 0 : pse->cswitch_vol);
     ps->cswitch_invol += ((pse->cswitch_invol == -1) ? 0 : pse->cswitch_invol);
 
-    want_init =
-        (entry->vmem_minflt_counter == 0) && (entry->vmem_majflt_counter == 0);
-    ps_update_counter(want_init, &ps->vmem_minflt_counter,
-                      &pse->vmem_minflt_counter, &pse->vmem_minflt,
-                      entry->vmem_minflt_counter, entry->vmem_minflt);
-    ps_update_counter(want_init, &ps->vmem_majflt_counter,
-                      &pse->vmem_majflt_counter, &pse->vmem_majflt,
-                      entry->vmem_majflt_counter, entry->vmem_majflt);
-
-    want_init =
-        (entry->cpu_user_counter == 0) && (entry->cpu_system_counter == 0);
-    ps_update_counter(want_init, &ps->cpu_user_counter, &pse->cpu_user_counter,
-                      &pse->cpu_user, entry->cpu_user_counter, entry->cpu_user);
-    ps_update_counter(want_init, &ps->cpu_system_counter,
-                      &pse->cpu_system_counter, &pse->cpu_system,
-                      entry->cpu_system_counter, entry->cpu_system);
+    ps_update_counter(&ps->vmem_minflt_counter, &pse->vmem_minflt_counter,
+                      entry->vmem_minflt_counter);
+    ps_update_counter(&ps->vmem_majflt_counter, &pse->vmem_majflt_counter,
+                      entry->vmem_majflt_counter);
+
+    ps_update_counter(&ps->cpu_user_counter, &pse->cpu_user_counter,
+                      entry->cpu_user_counter);
+    ps_update_counter(&ps->cpu_system_counter, &pse->cpu_system_counter,
+                      entry->cpu_system_counter);
   }
 }
 
@@ -621,14 +615,10 @@ static int ps_init(void) {
 
 /* submit global state (e.g.: qty of zombies, running, etc..) */
 static void ps_submit_state(const char *state, double value) {
-  value_t values[1];
   value_list_t vl = VALUE_LIST_INIT;
 
-  values[0].gauge = value;
-
-  vl.values = values;
+  vl.values = &(value_t){.gauge = value};
   vl.values_len = 1;
-  sstrncpy(vl.host, hostname_g, sizeof(vl.host));
   sstrncpy(vl.plugin, "processes", sizeof(vl.plugin));
   sstrncpy(vl.plugin_instance, "", sizeof(vl.plugin_instance));
   sstrncpy(vl.type, "ps_state", sizeof(vl.type));
@@ -639,12 +629,10 @@ static void ps_submit_state(const char *state, double value) {
 
 /* submit info about specific process (e.g.: memory taken, cpu usage, etc..) */
 static void ps_submit_proc_list(procstat_t *ps) {
-  value_t values[2];
   value_list_t vl = VALUE_LIST_INIT;
+  value_t values[2];
 
   vl.values = values;
-  vl.values_len = 2;
-  sstrncpy(vl.host, hostname_g, sizeof(vl.host));
   sstrncpy(vl.plugin, "processes", sizeof(vl.plugin));
   sstrncpy(vl.plugin_instance, ps->name, sizeof(vl.plugin_instance));
 
@@ -738,14 +726,10 @@ static void ps_submit_proc_list(procstat_t *ps) {
 
 #if KERNEL_LINUX || KERNEL_SOLARIS
 static void ps_submit_fork_rate(derive_t value) {
-  value_t values[1];
   value_list_t vl = VALUE_LIST_INIT;
 
-  values[0].derive = value;
-
-  vl.values = values;
+  vl.values = &(value_t){.derive = value};
   vl.values_len = 1;
-  sstrncpy(vl.host, hostname_g, sizeof(vl.host));
   sstrncpy(vl.plugin, "processes", sizeof(vl.plugin));
   sstrncpy(vl.plugin_instance, "", sizeof(vl.plugin_instance));
   sstrncpy(vl.type, "fork_rate", sizeof(vl.type));
@@ -757,7 +741,7 @@ static void ps_submit_fork_rate(derive_t value) {
 
 /* ------- additional functions for KERNEL_LINUX/HAVE_THREAD_INFO ------- */
 #if KERNEL_LINUX
-static procstat_t *ps_read_tasks_status(long pid, procstat_t *ps) {
+static int ps_read_tasks_status(procstat_entry_t *ps) {
   char dirname[64];
   DIR *dh;
   char filename[64];
@@ -769,11 +753,11 @@ static procstat_t *ps_read_tasks_status(long pid, procstat_t *ps) {
   char *fields[8];
   int numfields;
 
-  ssnprintf(dirname, sizeof(dirname), "/proc/%li/task", pid);
+  ssnprintf(dirname, sizeof(dirname), "/proc/%li/task", ps->id);
 
   if ((dh = opendir(dirname)) == NULL) {
     DEBUG("Failed to open directory `%s'", dirname);
-    return (NULL);
+    return (-1);
   }
 
   while ((ent = readdir(dh)) != NULL) {
@@ -784,7 +768,7 @@ static procstat_t *ps_read_tasks_status(long pid, procstat_t *ps) {
 
     tpid = ent->d_name;
 
-    ssnprintf(filename, sizeof(filename), "/proc/%li/task/%s/status", pid,
+    ssnprintf(filename, sizeof(filename), "/proc/%li/task/%s/status", ps->id,
               tpid);
     if ((fh = fopen(filename, "r")) == NULL) {
       DEBUG("Failed to open file `%s'", filename);
@@ -827,7 +811,7 @@ static procstat_t *ps_read_tasks_status(long pid, procstat_t *ps) {
   ps->cswitch_vol = cswitch_vol;
   ps->cswitch_invol = cswitch_invol;
 
-  return (ps);
+  return (0);
 } /* int *ps_read_tasks_status */
 
 /* Read data from /proc/pid/status */
@@ -887,7 +871,7 @@ static procstat_t *ps_read_status(long pid, procstat_t *ps) {
   return (ps);
 } /* procstat_t *ps_read_vmem */
 
-static procstat_t *ps_read_io(long pid, procstat_t *ps) {
+static int ps_read_io(procstat_entry_t *ps) {
   FILE *fh;
   char buffer[1024];
   char filename[64];
@@ -895,9 +879,9 @@ static procstat_t *ps_read_io(long pid, procstat_t *ps) {
   char *fields[8];
   int numfields;
 
-  ssnprintf(filename, sizeof(filename), "/proc/%li/io", pid);
+  ssnprintf(filename, sizeof(filename), "/proc/%li/io", ps->id);
   if ((fh = fopen(filename, "r")) == NULL)
-    return (NULL);
+    return (-1);
 
   while (fgets(buffer, sizeof(buffer), fh) != NULL) {
     derive_t *val = NULL;
@@ -933,9 +917,33 @@ static procstat_t *ps_read_io(long pid, procstat_t *ps) {
     char errbuf[1024];
     WARNING("processes: fclose: %s", sstrerror(errno, errbuf, sizeof(errbuf)));
   }
+  return (0);
+} /* int ps_read_io (...) */
 
-  return (ps);
-} /* procstat_t *ps_read_io */
+static void ps_fill_details(const procstat_t *ps, procstat_entry_t *entry) {
+  if (entry->has_io == 0 && ps_read_io(entry) != 0) {
+    /* no io data */
+    entry->io_rchar = -1;
+    entry->io_wchar = -1;
+    entry->io_syscr = -1;
+    entry->io_syscw = -1;
+
+    DEBUG("ps_read_process: not get io data for pid %li", entry->id);
+  }
+  entry->has_io = 1;
+
+  if (report_ctx_switch) {
+    if (entry->has_cswitch == 0 && ps_read_tasks_status(entry) != 0) {
+      entry->cswitch_vol = -1;
+      entry->cswitch_invol = -1;
+
+      DEBUG("ps_read_tasks_status: not get context "
+            "switch data for pid %li",
+            entry->id);
+    }
+    entry->has_cswitch = 1;
+  }
+} /* void ps_fill_details (...) */
 
 static int ps_read_process(long pid, procstat_t *ps, char *state) {
   char filename[64];
@@ -1060,27 +1068,6 @@ static int ps_read_process(long pid, procstat_t *ps, char *state) {
   ps->vmem_rss = (unsigned long)vmem_rss;
   ps->stack_size = (unsigned long)stack_size;
 
-  if ((ps_read_io(pid, ps)) == NULL) {
-    /* no io data */
-    ps->io_rchar = -1;
-    ps->io_wchar = -1;
-    ps->io_syscr = -1;
-    ps->io_syscw = -1;
-
-    DEBUG("ps_read_process: not get io data for pid %li", pid);
-  }
-
-  if (report_ctx_switch) {
-    if ((ps_read_tasks_status(pid, ps)) == NULL) {
-      ps->cswitch_vol = -1;
-      ps->cswitch_invol = -1;
-
-      DEBUG("ps_read_tasks_status: not get context "
-            "switch data for pid %li",
-            pid);
-    }
-  }
-
   /* success */
   return (0);
 } /* int ps_read_process (...) */
@@ -1708,14 +1695,10 @@ static int ps_read(void) {
     pse.vmem_code = ps.vmem_code;
     pse.stack_size = ps.stack_size;
 
-    pse.vmem_minflt = 0;
     pse.vmem_minflt_counter = ps.vmem_minflt_counter;
-    pse.vmem_majflt = 0;
     pse.vmem_majflt_counter = ps.vmem_majflt_counter;
 
-    pse.cpu_user = 0;
     pse.cpu_user_counter = ps.cpu_user_counter;
-    pse.cpu_system = 0;
     pse.cpu_system_counter = ps.cpu_system_counter;
 
     pse.io_rchar = ps.io_rchar;
@@ -1842,13 +1825,9 @@ static int ps_read(void) {
       pse.vmem_data = procs[i].ki_dsize * pagesize;
       pse.vmem_code = procs[i].ki_tsize * pagesize;
       pse.stack_size = procs[i].ki_ssize * pagesize;
-      pse.vmem_minflt = 0;
       pse.vmem_minflt_counter = procs[i].ki_rusage.ru_minflt;
-      pse.vmem_majflt = 0;
       pse.vmem_majflt_counter = procs[i].ki_rusage.ru_majflt;
 
-      pse.cpu_user = 0;
-      pse.cpu_system = 0;
       pse.cpu_user_counter = 0;
       pse.cpu_system_counter = 0;
       /*
@@ -1994,13 +1973,9 @@ static int ps_read(void) {
       pse.vmem_code = procs[i].p_vm_tsize * pagesize;
       pse.stack_size = procs[i].p_vm_ssize * pagesize;
       pse.vmem_size = pse.stack_size + pse.vmem_code + pse.vmem_data;
-      pse.vmem_minflt = 0;
       pse.vmem_minflt_counter = procs[i].p_uru_minflt;
-      pse.vmem_majflt = 0;
       pse.vmem_majflt_counter = procs[i].p_uru_majflt;
 
-      pse.cpu_user = 0;
-      pse.cpu_system = 0;
       pse.cpu_user_counter =
           procs[i].p_uutime_usec + (1000000lu * procs[i].p_uutime_sec);
       pse.cpu_system_counter =
@@ -2148,19 +2123,15 @@ static int ps_read(void) {
           break;
       }
 
-      pse.cpu_user = 0;
       /* tv_usec is nanosec ??? */
       pse.cpu_user_counter = procentry[i].pi_ru.ru_utime.tv_sec * 1000000 +
                              procentry[i].pi_ru.ru_utime.tv_usec / 1000;
 
-      pse.cpu_system = 0;
       /* tv_usec is nanosec ??? */
       pse.cpu_system_counter = procentry[i].pi_ru.ru_stime.tv_sec * 1000000 +
                                procentry[i].pi_ru.ru_stime.tv_usec / 1000;
 
-      pse.vmem_minflt = 0;
       pse.vmem_minflt_counter = procentry[i].pi_minflt;
-      pse.vmem_majflt = 0;
       pse.vmem_majflt_counter = procentry[i].pi_majflt;
 
       pse.vmem_size = procentry[i].pi_tsize + procentry[i].pi_dvm * pagesize;
@@ -2256,14 +2227,10 @@ static int ps_read(void) {
     pse.vmem_code = ps.vmem_code;
     pse.stack_size = ps.stack_size;
 
-    pse.vmem_minflt = 0;
     pse.vmem_minflt_counter = ps.vmem_minflt_counter;
-    pse.vmem_majflt = 0;
     pse.vmem_majflt_counter = ps.vmem_majflt_counter;
 
-    pse.cpu_user = 0;
     pse.cpu_user_counter = ps.cpu_user_counter;
-    pse.cpu_system = 0;
     pse.cpu_system_counter = ps.cpu_system_counter;
 
     pse.io_rchar = ps.io_rchar;
@@ -2321,6 +2288,8 @@ static int ps_read(void) {
   read_fork_rate();
 #endif /* KERNEL_SOLARIS */
 
+  want_init = 0;
+
   return (0);
 } /* int ps_read */
 
index 393c12d..64735e6 100644 (file)
@@ -52,19 +52,18 @@ static ignorelist_t *values_list = NULL;
  */
 static void submit(const char *protocol_name, const char *str_key,
                    const char *str_value) {
-  value_t values[1];
+  value_t value;
   value_list_t vl = VALUE_LIST_INIT;
   int status;
 
-  status = parse_value(str_value, values, DS_TYPE_DERIVE);
+  status = parse_value(str_value, &value, DS_TYPE_DERIVE);
   if (status != 0) {
     ERROR("protocols plugin: Parsing string as integer failed: %s", str_value);
     return;
   }
 
-  vl.values = values;
+  vl.values = &value;
   vl.values_len = 1;
-  sstrncpy(vl.host, hostname_g, sizeof(vl.host));
   sstrncpy(vl.plugin, "protocols", sizeof(vl.plugin));
   sstrncpy(vl.plugin_instance, protocol_name, sizeof(vl.plugin_instance));
   sstrncpy(vl.type, "protocol_counter", sizeof(vl.type));
index 9c25823..c6ee554 100644 (file)
@@ -718,9 +718,11 @@ static PyObject *cpy_register_generic_userdata(void *reg, void *handler,
   c->data = data;
   c->next = NULL;
 
-  user_data_t user_data = {.data = c, .free_func = cpy_destroy_user_data};
+  register_function(buf, handler,
+                    &(user_data_t){
+                        .data = c, .free_func = cpy_destroy_user_data,
+                    });
 
-  register_function(buf, handler, &user_data);
   ++cpy_num_callbacks;
   return cpy_string_to_unicode_or_bytes(buf);
 }
@@ -757,10 +759,12 @@ static PyObject *cpy_register_read(PyObject *self, PyObject *args,
   c->data = data;
   c->next = NULL;
 
-  user_data_t user_data = {.data = c, .free_func = cpy_destroy_user_data};
-
-  plugin_register_complex_read(/* group = */ "python", buf, cpy_read_callback,
-                               DOUBLE_TO_CDTIME_T(interval), &user_data);
+  plugin_register_complex_read(
+      /* group = */ "python", buf, cpy_read_callback,
+      DOUBLE_TO_CDTIME_T(interval),
+      &(user_data_t){
+          .data = c, .free_func = cpy_destroy_user_data,
+      });
   ++cpy_num_callbacks;
   return cpy_string_to_unicode_or_bytes(buf);
 }
@@ -1094,7 +1098,8 @@ static int cpy_init(void) {
       ERROR("python: Unable to create pipe.");
       return 1;
     }
-    if (plugin_thread_create(&thread, NULL, cpy_interactive, pipefd + 1)) {
+    if (plugin_thread_create(&thread, NULL, cpy_interactive, pipefd + 1,
+                             "python interpreter")) {
       ERROR("python: Error creating thread for interactive interpreter.");
     }
     if (read(pipefd[0], &buf, 1))
index 5b77484..7875c7e 100644 (file)
@@ -237,14 +237,10 @@ __attribute__((nonnull(2))) static void
 redis_submit(char *plugin_instance, const char *type, const char *type_instance,
              value_t value) /* {{{ */
 {
-  value_t values[1];
   value_list_t vl = VALUE_LIST_INIT;
 
-  values[0] = value;
-
-  vl.values = values;
+  vl.values = &value;
   vl.values_len = 1;
-  sstrncpy(vl.host, hostname_g, sizeof(vl.host));
   sstrncpy(vl.plugin, "redis", sizeof(vl.plugin));
   if (plugin_instance != NULL)
     sstrncpy(vl.plugin_instance, plugin_instance, sizeof(vl.plugin_instance));
index 843f6cc..07493c7 100644 (file)
@@ -50,11 +50,10 @@ typedef struct cr_data_s cr_data_t;
 
 static void cr_submit_io(cr_data_t *rd, const char *type, /* {{{ */
                          const char *type_instance, derive_t rx, derive_t tx) {
-  value_t values[2];
   value_list_t vl = VALUE_LIST_INIT;
-
-  values[0].derive = rx;
-  values[1].derive = tx;
+  value_t values[] = {
+      {.derive = rx}, {.derive = tx},
+  };
 
   vl.values = values;
   vl.values_len = STATIC_ARRAY_SIZE(values);
@@ -306,7 +305,6 @@ static int cr_config_router(oconfig_item_t *ci) /* {{{ */
 {
   cr_data_t *router_data;
   char read_name[128];
-  user_data_t user_data;
   int status;
 
   router_data = calloc(1, sizeof(*router_data));
@@ -381,11 +379,12 @@ static int cr_config_router(oconfig_item_t *ci) /* {{{ */
   }
 
   ssnprintf(read_name, sizeof(read_name), "routeros/%s", router_data->node);
-  user_data.data = router_data;
-  user_data.free_func = (void *)cr_free_data;
   if (status == 0)
     status = plugin_register_complex_read(
-        /* group = */ NULL, read_name, cr_read, /* interval = */ 0, &user_data);
+        /* group = */ NULL, read_name, cr_read, /* interval = */ 0,
+        &(user_data_t){
+            .data = router_data, .free_func = (void *)cr_free_data,
+        });
 
   if (status != 0)
     cr_free_data(router_data);
index f78f4da..6eb67d4 100644 (file)
@@ -286,8 +286,9 @@ static int rc_read(void) {
   rrdc_stats_t *head;
   _Bool retried = 0;
 
-  value_t values[1];
   value_list_t vl = VALUE_LIST_INIT;
+  vl.values = &(value_t){.gauge = NAN};
+  vl.values_len = 1;
 
   if (daemon_address == NULL)
     return (-1);
@@ -295,13 +296,8 @@ static int rc_read(void) {
   if (!config_collect_stats)
     return (-1);
 
-  vl.values = values;
-  vl.values_len = 1;
-
-  if ((strncmp("unix:", daemon_address, strlen("unix:")) == 0) ||
-      (daemon_address[0] == '/'))
-    sstrncpy(vl.host, hostname_g, sizeof(vl.host));
-  else
+  if ((strncmp("unix:", daemon_address, strlen("unix:")) != 0) &&
+      (daemon_address[0] != '/'))
     sstrncpy(vl.host, daemon_address, sizeof(vl.host));
   sstrncpy(vl.plugin, "rrdcached", sizeof(vl.plugin));
 
@@ -337,9 +333,9 @@ static int rc_read(void) {
 
   for (rrdc_stats_t *ptr = head; ptr != NULL; ptr = ptr->next) {
     if (ptr->type == RRDC_STATS_TYPE_GAUGE)
-      values[0].gauge = (gauge_t)ptr->value.gauge;
+      vl.values[0].gauge = (gauge_t)ptr->value.gauge;
     else if (ptr->type == RRDC_STATS_TYPE_COUNTER)
-      values[0].counter = (counter_t)ptr->value.counter;
+      vl.values[0].counter = (counter_t)ptr->value.counter;
     else
       continue;
 
index d357cad..e29d637 100644 (file)
@@ -87,7 +87,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(1);
+static cdtime_t random_timeout = TIME_T_TO_CDTIME_T_STATIC(1);
 static cdtime_t cache_flush_last;
 static c_avl_tree_t *cache = NULL;
 static pthread_mutex_t cache_lock = PTHREAD_MUTEX_INITIALIZER;
@@ -1071,8 +1071,9 @@ static int rrd_init(void) {
 
   pthread_mutex_unlock(&cache_lock);
 
-  status = plugin_thread_create(&queue_thread, /* attr = */ NULL,
-                                rrd_queue_thread, /* args = */ NULL);
+  status =
+      plugin_thread_create(&queue_thread, /* attr = */ NULL, rrd_queue_thread,
+                           /* args = */ NULL, "rrdtool queue");
   if (status != 0) {
     ERROR("rrdtool plugin: Cannot create queue-thread.");
     return (-1);
index 189a17e..8f1824d 100644 (file)
@@ -424,11 +424,10 @@ static int sensors_shutdown(void) {
 } /* int sensors_shutdown */
 
 static void sensors_submit(const char *plugin_instance, const char *type,
-                           const char *type_instance, double val) {
+                           const char *type_instance, double value) {
   char match_key[1024];
   int status;
 
-  value_t values[1];
   value_list_t vl = VALUE_LIST_INIT;
 
   status = ssnprintf(match_key, sizeof(match_key), "%s/%s-%s", plugin_instance,
@@ -442,12 +441,9 @@ static void sensors_submit(const char *plugin_instance, const char *type,
       return;
   }
 
-  values[0].gauge = val;
-
-  vl.values = values;
+  vl.values = &(value_t){.gauge = value};
   vl.values_len = 1;
 
-  sstrncpy(vl.host, hostname_g, sizeof(vl.host));
   sstrncpy(vl.plugin, "sensors", sizeof(vl.plugin));
   sstrncpy(vl.plugin_instance, plugin_instance, sizeof(vl.plugin_instance));
   sstrncpy(vl.type, type, sizeof(vl.type));
index 10396fc..5057fd3 100644 (file)
 #endif
 
 static void serial_submit(const char *type_instance, derive_t rx, derive_t tx) {
-  value_t values[2];
   value_list_t vl = VALUE_LIST_INIT;
-
-  values[0].derive = rx;
-  values[1].derive = tx;
+  value_t values[] = {
+      {.derive = rx}, {.derive = tx},
+  };
 
   vl.values = values;
-  vl.values_len = 2;
-  sstrncpy(vl.host, hostname_g, sizeof(vl.host));
+  vl.values_len = STATIC_ARRAY_SIZE(values);
   sstrncpy(vl.plugin, "serial", sizeof(vl.plugin));
   sstrncpy(vl.type, "serial_octets", sizeof(vl.type));
   sstrncpy(vl.type_instance, type_instance, sizeof(vl.type_instance));
index f673635..44920ad 100644 (file)
@@ -155,7 +155,6 @@ static void sigrok_feed_callback(const struct sr_dev_inst *sdi,
                                  void *cb_data) {
   const struct sr_datafeed_analog *analog;
   struct config_device *cfdev;
-  value_t value;
   value_list_t vl = VALUE_LIST_INIT;
 
   /* Find this device's configuration. */
@@ -192,12 +191,10 @@ static void sigrok_feed_callback(const struct sr_dev_inst *sdi,
 
   /* Ignore all but the first sample on the first probe. */
   analog = packet->payload;
-  value.gauge = analog->data[0];
-  vl.values = &value;
+  vl.values = &(value_t){.gauge = analog->data[0]};
   vl.values_len = 1;
-  sstrncpy(vl.host, hostname_g, sizeof(vl.host));
   sstrncpy(vl.plugin, "sigrok", sizeof(vl.plugin));
-  ssnprintf(vl.plugin_instance, sizeof(vl.plugin_instance), "%s", cfdev->name);
+  sstrncpy(vl.plugin_instance, cfdev->name, sizeof(vl.plugin_instance));
   sstrncpy(vl.type, sigrok_value_type(analog), sizeof(vl.type));
 
   plugin_dispatch_values(&vl);
@@ -340,7 +337,8 @@ static int sigrok_init(void) {
     return -1;
   }
 
-  status = plugin_thread_create(&sr_thread, NULL, sigrok_read_thread, NULL);
+  status = plugin_thread_create(&sr_thread, NULL, sigrok_read_thread, NULL,
+                                "sigrok read");
   if (status != 0) {
     char errbuf[1024];
     ERROR("sigrok plugin: Failed to create thread: %s.",
index 5bb6af4..3188d1c 100644 (file)
@@ -74,14 +74,10 @@ static int smart_config(const char *key, const char *value) {
 
 static void smart_submit(const char *dev, const char *type,
                          const char *type_inst, double value) {
-  value_t values[1];
   value_list_t vl = VALUE_LIST_INIT;
 
-  values[0].gauge = value;
-
-  vl.values = values;
+  vl.values = &(value_t){.gauge = value};
   vl.values_len = 1;
-  sstrncpy(vl.host, hostname_g, sizeof(vl.host));
   sstrncpy(vl.plugin, "smart", sizeof(vl.plugin));
   sstrncpy(vl.plugin_instance, dev, sizeof(vl.plugin_instance));
   sstrncpy(vl.type, type, sizeof(vl.type));
@@ -93,19 +89,20 @@ static void smart_submit(const char *dev, const char *type,
 static void handle_attribute(SkDisk *d, const SkSmartAttributeParsedData *a,
                              void *userdata) {
   char const *name = userdata;
-  value_t values[4];
-  value_list_t vl = VALUE_LIST_INIT;
 
   if (!a->current_value_valid || !a->worst_value_valid)
     return;
-  values[0].gauge = a->current_value;
-  values[1].gauge = a->worst_value;
-  values[2].gauge = a->threshold_valid ? a->threshold : 0;
-  values[3].gauge = a->pretty_value;
+
+  value_list_t vl = VALUE_LIST_INIT;
+  value_t values[] = {
+      {.gauge = a->current_value},
+      {.gauge = a->worst_value},
+      {.gauge = a->threshold_valid ? a->threshold : 0},
+      {.gauge = a->pretty_value},
+  };
 
   vl.values = values;
-  vl.values_len = 4;
-  sstrncpy(vl.host, hostname_g, sizeof(vl.host));
+  vl.values_len = STATIC_ARRAY_SIZE(values);
   sstrncpy(vl.plugin, "smart", sizeof(vl.plugin));
   sstrncpy(vl.plugin_instance, name, sizeof(vl.plugin_instance));
   sstrncpy(vl.type, "smart_attribute", sizeof(vl.type));
index 287f44b..3bfec47 100644 (file)
@@ -706,11 +706,11 @@ static int csnmp_config_add_host(oconfig_item_t *ci) {
 
   ssnprintf(cb_name, sizeof(cb_name), "snmp-%s", hd->name);
 
-  user_data_t ud = {.data = hd, .free_func = csnmp_host_definition_destroy};
-
-  status =
-      plugin_register_complex_read(/* group = */ NULL, cb_name, csnmp_read_host,
-                                   hd->interval, /* user_data = */ &ud);
+  status = plugin_register_complex_read(
+      /* group = */ NULL, cb_name, csnmp_read_host, hd->interval,
+      &(user_data_t){
+          .data = hd, .free_func = csnmp_host_definition_destroy,
+      });
   if (status != 0) {
     ERROR("snmp plugin: Registering complex read function failed.");
     csnmp_host_definition_destroy(hd);
index dd15d59..47e01d8 100644 (file)
@@ -706,12 +706,10 @@ static int statsd_metric_clear_set_unsafe(statsd_metric_t *metric) /* {{{ */
 static int statsd_metric_submit_unsafe(char const *name,
                                        statsd_metric_t *metric) /* {{{ */
 {
-  value_t values[1];
   value_list_t vl = VALUE_LIST_INIT;
 
-  vl.values = values;
+  vl.values = &(value_t){.gauge = NAN};
   vl.values_len = 1;
-  sstrncpy(vl.host, hostname_g, sizeof(vl.host));
   sstrncpy(vl.plugin, "statsd", sizeof(vl.plugin));
 
   if (metric->type == STATSD_GAUGE)
@@ -726,7 +724,7 @@ static int statsd_metric_submit_unsafe(char const *name,
   sstrncpy(vl.type_instance, name, sizeof(vl.type_instance));
 
   if (metric->type == STATSD_GAUGE)
-    values[0].gauge = (gauge_t)metric->value;
+    vl.values[0].gauge = (gauge_t)metric->value;
   else if (metric->type == STATSD_TIMER) {
     _Bool have_events = (metric->updates_num > 0);
 
@@ -734,7 +732,7 @@ static int statsd_metric_submit_unsafe(char const *name,
     vl.time = cdtime();
 
     ssnprintf(vl.type_instance, sizeof(vl.type_instance), "%s-average", name);
-    values[0].gauge =
+    vl.values[0].gauge =
         have_events
             ? CDTIME_T_TO_DOUBLE(latency_counter_get_average(metric->latency))
             : NAN;
@@ -742,7 +740,7 @@ static int statsd_metric_submit_unsafe(char const *name,
 
     if (conf_timer_lower) {
       ssnprintf(vl.type_instance, sizeof(vl.type_instance), "%s-lower", name);
-      values[0].gauge =
+      vl.values[0].gauge =
           have_events
               ? CDTIME_T_TO_DOUBLE(latency_counter_get_min(metric->latency))
               : NAN;
@@ -751,7 +749,7 @@ static int statsd_metric_submit_unsafe(char const *name,
 
     if (conf_timer_upper) {
       ssnprintf(vl.type_instance, sizeof(vl.type_instance), "%s-upper", name);
-      values[0].gauge =
+      vl.values[0].gauge =
           have_events
               ? CDTIME_T_TO_DOUBLE(latency_counter_get_max(metric->latency))
               : NAN;
@@ -760,7 +758,7 @@ static int statsd_metric_submit_unsafe(char const *name,
 
     if (conf_timer_sum) {
       ssnprintf(vl.type_instance, sizeof(vl.type_instance), "%s-sum", name);
-      values[0].gauge =
+      vl.values[0].gauge =
           have_events
               ? CDTIME_T_TO_DOUBLE(latency_counter_get_sum(metric->latency))
               : NAN;
@@ -770,10 +768,10 @@ static int statsd_metric_submit_unsafe(char const *name,
     for (size_t i = 0; i < conf_timer_percentile_num; i++) {
       ssnprintf(vl.type_instance, sizeof(vl.type_instance),
                 "%s-percentile-%.0f", name, conf_timer_percentile[i]);
-      values[0].gauge = have_events
-                            ? CDTIME_T_TO_DOUBLE(latency_counter_get_percentile(
-                                  metric->latency, conf_timer_percentile[i]))
-                            : NAN;
+      vl.values[0].gauge =
+          have_events ? CDTIME_T_TO_DOUBLE(latency_counter_get_percentile(
+                            metric->latency, conf_timer_percentile[i]))
+                      : NAN;
       plugin_dispatch_values(&vl);
     }
 
@@ -782,7 +780,7 @@ static int statsd_metric_submit_unsafe(char const *name,
     if (conf_timer_count) {
       sstrncpy(vl.type, "gauge", sizeof(vl.type));
       ssnprintf(vl.type_instance, sizeof(vl.type_instance), "%s-count", name);
-      values[0].gauge = latency_counter_get_num(metric->latency);
+      vl.values[0].gauge = latency_counter_get_num(metric->latency);
       plugin_dispatch_values(&vl);
     }
 
@@ -790,9 +788,9 @@ static int statsd_metric_submit_unsafe(char const *name,
     return (0);
   } else if (metric->type == STATSD_SET) {
     if (metric->set == NULL)
-      values[0].gauge = 0.0;
+      vl.values[0].gauge = 0.0;
     else
-      values[0].gauge = (gauge_t)c_avl_size(metric->set);
+      vl.values[0].gauge = (gauge_t)c_avl_size(metric->set);
   } else { /* STATSD_COUNTER */
     gauge_t delta = nearbyint(metric->value);
 
@@ -802,7 +800,7 @@ static int statsd_metric_submit_unsafe(char const *name,
      * anyway - if requested by the user - for compatibility reasons. */
     if (conf_counter_sum) {
       sstrncpy(vl.type, "count", sizeof(vl.type));
-      values[0].gauge = delta;
+      vl.values[0].gauge = delta;
       plugin_dispatch_values(&vl);
 
       /* restore vl.type */
@@ -814,7 +812,7 @@ static int statsd_metric_submit_unsafe(char const *name,
     metric->value -= delta;
     metric->counter += (derive_t)delta;
 
-    values[0].derive = metric->counter;
+    vl.values[0].derive = metric->counter;
   }
 
   return (plugin_dispatch_values(&vl));
index f64a9b3..cbe1ab1 100644 (file)
@@ -190,12 +190,10 @@ static int swap_init(void) /* {{{ */
 static void swap_submit_usage(char const *plugin_instance, /* {{{ */
                               gauge_t used, gauge_t free,
                               char const *other_name, gauge_t other_value) {
-  value_t v[1];
   value_list_t vl = VALUE_LIST_INIT;
 
-  vl.values = v;
-  vl.values_len = STATIC_ARRAY_SIZE(v);
-  sstrncpy(vl.host, hostname_g, sizeof(vl.host));
+  vl.values = &(value_t){.gauge = NAN};
+  vl.values_len = 1;
   sstrncpy(vl.plugin, "swap", sizeof(vl.plugin));
   if (plugin_instance != NULL)
     sstrncpy(vl.plugin_instance, plugin_instance, sizeof(vl.plugin_instance));
@@ -214,13 +212,9 @@ __attribute__((nonnull(1))) static void
 swap_submit_derive(char const *type_instance, /* {{{ */
                    derive_t value) {
   value_list_t vl = VALUE_LIST_INIT;
-  value_t v[1];
 
-  v[0].derive = value;
-
-  vl.values = v;
-  vl.values_len = STATIC_ARRAY_SIZE(v);
-  sstrncpy(vl.host, hostname_g, sizeof(vl.host));
+  vl.values = &(value_t){.derive = value};
+  vl.values_len = 1;
   sstrncpy(vl.plugin, "swap", sizeof(vl.plugin));
   sstrncpy(vl.type, "swap_io", sizeof(vl.type));
   sstrncpy(vl.type_instance, type_instance, sizeof(vl.type_instance));
index 438bd7a..3e8fead 100644 (file)
@@ -367,7 +367,6 @@ 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.host, hostname_g, sizeof(vl.host));
   sstrncpy(vl.plugin, "table", sizeof(vl.plugin));
   sstrncpy(vl.plugin_instance, tbl->instance, sizeof(vl.plugin_instance));
   sstrncpy(vl.type, res->type, sizeof(vl.type));
index 2b18afd..bb4eb30 100644 (file)
@@ -28,6 +28,7 @@
 
 #include "common.h"
 #include "plugin.h"
+#include "utils_latency_config.h"
 #include "utils_tail_match.h"
 
 /*
@@ -53,6 +54,7 @@ struct ctail_config_match_s {
   char *type;
   char *type_instance;
   cdtime_t interval;
+  latency_config_t latency;
 };
 typedef struct ctail_config_match_s ctail_config_match_t;
 
@@ -67,48 +69,54 @@ static int ctail_config_add_match_dstype(ctail_config_match_t *cm,
     return (-1);
   }
 
-  if (strncasecmp("Gauge", ci->values[0].value.string, strlen("Gauge")) == 0) {
+  char const *ds_type = ci->values[0].value.string;
+  if (strncasecmp("Gauge", ds_type, strlen("Gauge")) == 0) {
     cm->flags = UTILS_MATCH_DS_TYPE_GAUGE;
-    if (strcasecmp("GaugeAverage", ci->values[0].value.string) == 0)
+    if (strcasecmp("GaugeAverage", ds_type) == 0)
       cm->flags |= UTILS_MATCH_CF_GAUGE_AVERAGE;
-    else if (strcasecmp("GaugeMin", ci->values[0].value.string) == 0)
+    else if (strcasecmp("GaugeMin", ds_type) == 0)
       cm->flags |= UTILS_MATCH_CF_GAUGE_MIN;
-    else if (strcasecmp("GaugeMax", ci->values[0].value.string) == 0)
+    else if (strcasecmp("GaugeMax", ds_type) == 0)
       cm->flags |= UTILS_MATCH_CF_GAUGE_MAX;
-    else if (strcasecmp("GaugeLast", ci->values[0].value.string) == 0)
+    else if (strcasecmp("GaugeLast", ds_type) == 0)
       cm->flags |= UTILS_MATCH_CF_GAUGE_LAST;
-    else if (strcasecmp("GaugeInc", ci->values[0].value.string) == 0)
+    else if (strcasecmp("GaugeInc", ds_type) == 0)
       cm->flags |= UTILS_MATCH_CF_GAUGE_INC;
-    else if (strcasecmp("GaugeAdd", ci->values[0].value.string) == 0)
+    else if (strcasecmp("GaugeAdd", ds_type) == 0)
       cm->flags |= UTILS_MATCH_CF_GAUGE_ADD;
+    else if (strcasecmp("GaugePersist", ci->values[0].value.string) == 0)
+      cm->flags |= UTILS_MATCH_CF_GAUGE_PERSIST;
     else
       cm->flags = 0;
-  } else if (strncasecmp("Counter", ci->values[0].value.string,
-                         strlen("Counter")) == 0) {
+  } else if (strcasecmp("Distribution", ds_type) == 0) {
+    cm->flags = UTILS_MATCH_DS_TYPE_GAUGE | UTILS_MATCH_CF_GAUGE_DIST;
+
+    int status = latency_config(&cm->latency, ci, "tail");
+    if (status != 0)
+      return (status);
+  } else if (strncasecmp("Counter", ds_type, strlen("Counter")) == 0) {
     cm->flags = UTILS_MATCH_DS_TYPE_COUNTER;
-    if (strcasecmp("CounterSet", ci->values[0].value.string) == 0)
+    if (strcasecmp("CounterSet", ds_type) == 0)
       cm->flags |= UTILS_MATCH_CF_COUNTER_SET;
-    else if (strcasecmp("CounterAdd", ci->values[0].value.string) == 0)
+    else if (strcasecmp("CounterAdd", ds_type) == 0)
       cm->flags |= UTILS_MATCH_CF_COUNTER_ADD;
-    else if (strcasecmp("CounterInc", ci->values[0].value.string) == 0)
+    else if (strcasecmp("CounterInc", ds_type) == 0)
       cm->flags |= UTILS_MATCH_CF_COUNTER_INC;
     else
       cm->flags = 0;
-  } else if (strncasecmp("Derive", ci->values[0].value.string,
-                         strlen("Derive")) == 0) {
+  } else if (strncasecmp("Derive", ds_type, strlen("Derive")) == 0) {
     cm->flags = UTILS_MATCH_DS_TYPE_DERIVE;
-    if (strcasecmp("DeriveSet", ci->values[0].value.string) == 0)
+    if (strcasecmp("DeriveSet", ds_type) == 0)
       cm->flags |= UTILS_MATCH_CF_DERIVE_SET;
-    else if (strcasecmp("DeriveAdd", ci->values[0].value.string) == 0)
+    else if (strcasecmp("DeriveAdd", ds_type) == 0)
       cm->flags |= UTILS_MATCH_CF_DERIVE_ADD;
-    else if (strcasecmp("DeriveInc", ci->values[0].value.string) == 0)
+    else if (strcasecmp("DeriveInc", ds_type) == 0)
       cm->flags |= UTILS_MATCH_CF_DERIVE_INC;
     else
       cm->flags = 0;
-  } else if (strncasecmp("Absolute", ci->values[0].value.string,
-                         strlen("Absolute")) == 0) {
+  } else if (strncasecmp("Absolute", ds_type, strlen("Absolute")) == 0) {
     cm->flags = UTILS_MATCH_DS_TYPE_ABSOLUTE;
-    if (strcasecmp("AbsoluteSet", ci->values[0].value.string) == 0)
+    if (strcasecmp("AbsoluteSet", ds_type) == 0)
       cm->flags |= UTILS_MATCH_CF_ABSOLUTE_SET;
     else
       cm->flags = 0;
@@ -181,19 +189,20 @@ static int ctail_config_add_match(cu_tail_match_t *tm,
   } /* while (status == 0) */
 
   if (status == 0) {
-    status = tail_match_add_match_simple(tm, cm.regex, cm.excluderegex,
-                                         cm.flags, "tail", plugin_instance,
-                                         cm.type, cm.type_instance, interval);
+    // 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,
+        cm.type, cm.type_instance, cm.latency, interval);
 
-    if (status != 0) {
+    if (status != 0)
       ERROR("tail plugin: tail_match_add_match_simple failed.");
-    }
   }
 
   sfree(cm.regex);
   sfree(cm.excluderegex);
   sfree(cm.type);
   sfree(cm.type_instance);
+  latency_config_free(cm.latency);
 
   return (status);
 } /* int ctail_config_add_match */
@@ -302,10 +311,11 @@ static int ctail_init(void) {
   for (size_t i = 0; i < tail_match_list_num; i++) {
     ssnprintf(str, sizeof(str), "tail-%zu", i);
 
-    user_data_t ud = {.data = tail_match_list[i]};
-
     plugin_register_complex_read(NULL, str, ctail_read,
-                                 tail_match_list_intervals[i], &ud);
+                                 tail_match_list_intervals[i],
+                                 &(user_data_t){
+                                     .data = tail_match_list[i],
+                                 });
   }
 
   return (0);
index ee539eb..b1a3292 100644 (file)
@@ -67,7 +67,6 @@ static int tcsv_submit(instance_definition_t *id, metric_definition_t *md,
   vl.values_len = 1;
   vl.values = &v;
 
-  sstrncpy(vl.host, hostname_g, sizeof(vl.host));
   sstrncpy(vl.plugin, "tail_csv", sizeof(vl.plugin));
   if (id->instance != NULL)
     sstrncpy(vl.plugin_instance, id->instance, sizeof(vl.plugin_instance));
@@ -478,11 +477,11 @@ static int tcsv_config_add_file(oconfig_item_t *ci) {
 
   ssnprintf(cb_name, sizeof(cb_name), "tail_csv/%s", id->path);
 
-  user_data_t ud = {.data = id, .free_func = tcsv_instance_definition_destroy};
-
-  status =
-      plugin_register_complex_read(NULL, cb_name, tcsv_read, id->interval, &ud);
-
+  status = plugin_register_complex_read(
+      NULL, cb_name, tcsv_read, id->interval,
+      &(user_data_t){
+          .data = id, .free_func = tcsv_instance_definition_destroy,
+      });
   if (status != 0) {
     ERROR("tail_csv plugin: Registering complex read function failed.");
     tcsv_instance_definition_destroy(id);
index 368acc4..3f4bb55 100644 (file)
@@ -57,15 +57,13 @@ static int tape_init(void) {
 
 static void tape_submit(const char *plugin_instance, const char *type,
                         derive_t read, derive_t write) {
-  value_t values[2];
   value_list_t vl = VALUE_LIST_INIT;
-
-  values[0].derive = read;
-  values[1].derive = write;
+  value_t values[] = {
+      {.derive = read}, {.derive = write},
+  };
 
   vl.values = values;
-  vl.values_len = 2;
-  sstrncpy(vl.host, hostname_g, sizeof(vl.host));
+  vl.values_len = STATIC_ARRAY_SIZE(values);
   sstrncpy(vl.plugin, "tape", sizeof(vl.plugin));
   sstrncpy(vl.plugin_instance, plugin_instance, sizeof(vl.plugin_instance));
   sstrncpy(vl.type, type, sizeof(vl.type));
index 382fc35..b43a025 100644 (file)
@@ -37,17 +37,28 @@ typedef struct tr_action_s tr_action_t;
 struct tr_action_s {
   regex_t re;
   char *replacement;
-  int may_be_empty;
+  _Bool may_be_empty;
 
   tr_action_t *next;
 };
 
+struct tr_meta_data_action_s;
+typedef struct tr_meta_data_action_s tr_meta_data_action_t;
+struct tr_meta_data_action_s {
+  char *key;
+  regex_t re;
+  char *replacement;
+
+  tr_meta_data_action_t *next;
+};
+
 struct tr_data_s {
   tr_action_t *host;
   tr_action_t *plugin;
   tr_action_t *plugin_instance;
   /* tr_action_t *type; */
   tr_action_t *type_instance;
+  tr_meta_data_action_t *meta;
 };
 typedef struct tr_data_s tr_data_t;
 
@@ -83,8 +94,23 @@ static void tr_action_destroy(tr_action_t *act) /* {{{ */
   sfree(act);
 } /* }}} void tr_action_destroy */
 
+static void tr_meta_data_action_destroy(tr_meta_data_action_t *act) /* {{{ */
+{
+  if (act == NULL)
+    return;
+
+  sfree(act->key);
+  regfree(&act->re);
+  sfree(act->replacement);
+
+  if (act->next != NULL)
+    tr_meta_data_action_destroy(act->next);
+
+  sfree(act);
+} /* }}} void tr_meta_data_action_destroy */
+
 static int tr_config_add_action(tr_action_t **dest, /* {{{ */
-                                const oconfig_item_t *ci, int may_be_empty) {
+                                const oconfig_item_t *ci, _Bool may_be_empty) {
   tr_action_t *act;
   int status;
 
@@ -124,8 +150,7 @@ static int tr_config_add_action(tr_action_t **dest, /* {{{ */
   act->replacement = tr_strdup(ci->values[1].value.string);
   if (act->replacement == NULL) {
     ERROR("tr_config_add_action: tr_strdup failed.");
-    regfree(&act->re);
-    sfree(act);
+    tr_action_destroy(act);
     return (-ENOMEM);
   }
 
@@ -145,9 +170,99 @@ static int tr_config_add_action(tr_action_t **dest, /* {{{ */
   return (0);
 } /* }}} int tr_config_add_action */
 
+static int tr_config_add_meta_action(tr_meta_data_action_t **dest, /* {{{ */
+                                     const oconfig_item_t *ci,
+                                     _Bool should_delete) {
+  tr_meta_data_action_t *act;
+  int status;
+
+  if (dest == NULL)
+    return (-EINVAL);
+
+  if (should_delete) {
+    if ((ci->values_num != 2) || (ci->values[0].type != OCONFIG_TYPE_STRING) ||
+        (ci->values[1].type != OCONFIG_TYPE_STRING)) {
+      ERROR("Target `replace': The `%s' option requires exactly two string "
+            "arguments.",
+            ci->key);
+      return (-1);
+    }
+  } else {
+    if ((ci->values_num != 3) || (ci->values[0].type != OCONFIG_TYPE_STRING) ||
+        (ci->values[1].type != OCONFIG_TYPE_STRING) ||
+        (ci->values[2].type != OCONFIG_TYPE_STRING)) {
+      ERROR("Target `replace': The `%s' option requires exactly three string "
+            "arguments.",
+            ci->key);
+      return (-1);
+    }
+  }
+
+  if (strlen(ci->values[0].value.string) == 0) {
+    ERROR("Target `replace': The `%s' option does not accept empty string as "
+          "first argument.",
+          ci->key);
+    return (-1);
+  }
+
+  act = calloc(1, sizeof(*act));
+  if (act == NULL) {
+    ERROR("tr_config_add_meta_action: calloc failed.");
+    return (-ENOMEM);
+  }
+
+  act->key = NULL;
+  act->replacement = NULL;
+
+  status = regcomp(&act->re, ci->values[1].value.string, REG_EXTENDED);
+  if (status != 0) {
+    char errbuf[1024] = "";
+
+    /* regerror assures null termination. */
+    regerror(status, &act->re, errbuf, sizeof(errbuf));
+    ERROR("Target `replace': Compiling the regular expression `%s' "
+          "failed: %s.",
+          ci->values[1].value.string, errbuf);
+    sfree(act->key);
+    sfree(act);
+    return (-EINVAL);
+  }
+
+  act->key = tr_strdup(ci->values[0].value.string);
+  if (act->key == NULL) {
+    ERROR("tr_config_add_meta_action: tr_strdup failed.");
+    tr_meta_data_action_destroy(act);
+    return (-ENOMEM);
+  }
+
+  if (!should_delete) {
+    act->replacement = tr_strdup(ci->values[2].value.string);
+    if (act->replacement == NULL) {
+      ERROR("tr_config_add_meta_action: tr_strdup failed.");
+      tr_meta_data_action_destroy(act);
+      return (-ENOMEM);
+    }
+  }
+
+  /* Insert action at end of list. */
+  if (*dest == NULL)
+    *dest = act;
+  else {
+    tr_meta_data_action_t *prev;
+
+    prev = *dest;
+    while (prev->next != NULL)
+      prev = prev->next;
+
+    prev->next = act;
+  }
+
+  return (0);
+} /* }}} int tr_config_add_meta_action */
+
 static int tr_action_invoke(tr_action_t *act_head, /* {{{ */
                             char *buffer_in, size_t buffer_in_size,
-                            int may_be_empty) {
+                            _Bool may_be_empty) {
   int status;
   char buffer[DATA_MAX_NAME_LEN];
   regmatch_t matches[8] = {[0] = {0}};
@@ -202,6 +317,110 @@ static int tr_action_invoke(tr_action_t *act_head, /* {{{ */
   return (0);
 } /* }}} int tr_action_invoke */
 
+static int tr_meta_data_action_invoke(/* {{{ */
+                                      tr_meta_data_action_t *act_head,
+                                      meta_data_t **dest) {
+  int status;
+  regmatch_t matches[8] = {[0] = {0}};
+
+  if (act_head == NULL)
+    return (-EINVAL);
+
+  if ((*dest) == NULL) /* nothing to do */
+    return (0);
+
+  for (tr_meta_data_action_t *act = act_head; act != NULL; act = act->next) {
+    char temp[DATA_MAX_NAME_LEN];
+    char *subst_status;
+    int value_type;
+    int meta_data_status;
+    char *value;
+    meta_data_t *result;
+
+    value_type = meta_data_type(*dest, act->key);
+    if (value_type == 0) /* not found */
+      continue;
+    if (value_type != MD_TYPE_STRING) {
+      WARNING("Target `replace': Attempting replace on metadata key `%s', "
+              "which isn't a string.",
+              act->key);
+      continue;
+    }
+
+    meta_data_status = meta_data_get_string(*dest, act->key, &value);
+    if (meta_data_status != 0) {
+      ERROR("Target `replace': Unable to retrieve metadata value for `%s'.",
+            act->key);
+      return (meta_data_status);
+    }
+
+    DEBUG("target_replace plugin: tr_meta_data_action_invoke: `%s' "
+          "old value = `%s'",
+          act->key, value);
+
+    status = regexec(&act->re, value, STATIC_ARRAY_SIZE(matches), matches,
+                     /* flags = */ 0);
+    if (status == REG_NOMATCH) {
+      sfree(value);
+      continue;
+    } else if (status != 0) {
+      char errbuf[1024] = "";
+
+      regerror(status, &act->re, errbuf, sizeof(errbuf));
+      ERROR("Target `replace': Executing a regular expression failed: %s.",
+            errbuf);
+      sfree(value);
+      continue;
+    }
+
+    if (act->replacement == NULL) {
+      /* no replacement; delete the key */
+      DEBUG("target_replace plugin: tr_meta_data_action_invoke: "
+            "deleting `%s'",
+            act->key);
+      meta_data_delete(*dest, act->key);
+      sfree(value);
+      continue;
+    }
+
+    subst_status = subst(temp, sizeof(temp), value, (size_t)matches[0].rm_so,
+                         (size_t)matches[0].rm_eo, act->replacement);
+    if (subst_status == NULL) {
+      ERROR("Target `replace': subst (value = %s, start = %zu, end = %zu, "
+            "replacement = %s) failed.",
+            value, (size_t)matches[0].rm_so, (size_t)matches[0].rm_eo,
+            act->replacement);
+      sfree(value);
+      continue;
+    }
+
+    DEBUG("target_replace plugin: tr_meta_data_action_invoke: `%s' "
+          "value `%s' -> `%s'",
+          act->key, value, temp);
+
+    if ((result = meta_data_create()) == NULL) {
+      ERROR("Target `replace': failed to create metadata for `%s'.", act->key);
+      sfree(value);
+      return (-ENOMEM);
+    }
+
+    meta_data_status = meta_data_add_string(result, act->key, temp);
+    if (meta_data_status != 0) {
+      ERROR("Target `replace': Unable to set metadata value for `%s'.",
+            act->key);
+      meta_data_destroy(result);
+      sfree(value);
+      return (meta_data_status);
+    }
+
+    meta_data_clone_merge(dest, result);
+    meta_data_destroy(result);
+    sfree(value);
+  } /* for (act = act_head; act != NULL; act = act->next) */
+
+  return (0);
+} /* }}} int tr_meta_data_action_invoke */
+
 static int tr_destroy(void **user_data) /* {{{ */
 {
   tr_data_t *data;
@@ -218,6 +437,7 @@ static int tr_destroy(void **user_data) /* {{{ */
   tr_action_destroy(data->plugin_instance);
   /* tr_action_destroy (data->type); */
   tr_action_destroy(data->type_instance);
+  tr_meta_data_action_destroy(data->meta);
   sfree(data);
 
   return (0);
@@ -239,6 +459,7 @@ static int tr_create(const oconfig_item_t *ci, void **user_data) /* {{{ */
   data->plugin_instance = NULL;
   /* data->type = NULL; */
   data->type_instance = NULL;
+  data->meta = NULL;
 
   status = 0;
   for (int i = 0; i < ci->children_num; i++) {
@@ -262,6 +483,12 @@ static int tr_create(const oconfig_item_t *ci, void **user_data) /* {{{ */
     else if (strcasecmp("TypeInstance", child->key) == 0)
       status = tr_config_add_action(&data->type_instance, child,
                                     /* may be empty = */ 1);
+    else if (strcasecmp("MetaData", child->key) == 0)
+      status = tr_config_add_meta_action(&data->meta, child,
+                                         /* should delete = */ 0);
+    else if (strcasecmp("DeleteMetaData", child->key) == 0)
+      status = tr_config_add_meta_action(&data->meta, child,
+                                         /* should delete = */ 1);
     else {
       ERROR("Target `replace': The `%s' configuration option is not understood "
             "and will be ignored.",
@@ -278,7 +505,7 @@ static int tr_create(const oconfig_item_t *ci, void **user_data) /* {{{ */
     if ((data->host == NULL) && (data->plugin == NULL) &&
         (data->plugin_instance == NULL)
         /* && (data->type == NULL) */
-        && (data->type_instance == NULL)) {
+        && (data->type_instance == NULL) && (data->meta == NULL)) {
       ERROR("Target `replace': You need to set at least one of `Host', "
             "`Plugin', `PluginInstance' or `TypeInstance'.");
       status = -1;
@@ -310,13 +537,17 @@ static int tr_invoke(const data_set_t *ds, value_list_t *vl, /* {{{ */
     return (-EINVAL);
   }
 
+  if (data->meta != NULL) {
+    tr_meta_data_action_invoke(data->meta, &(vl->meta));
+  }
+
 #define HANDLE_FIELD(f, e)                                                     \
   if (data->f != NULL)                                                         \
   tr_action_invoke(data->f, vl->f, sizeof(vl->f), e)
   HANDLE_FIELD(host, 0);
   HANDLE_FIELD(plugin, 0);
   HANDLE_FIELD(plugin_instance, 1);
-  /* HANDLE_FIELD (type); */
+  /* HANDLE_FIELD (type, 0); */
   HANDLE_FIELD(type_instance, 1);
 
   return (FC_TARGET_CONTINUE);
index 1b26a55..a40273c 100644 (file)
 
 #include "common.h"
 #include "filter_chain.h"
+#include "meta_data.h"
+#include "utils_subst.h"
+
+struct ts_key_list_s {
+  char *key;
+  struct ts_key_list_s *next;
+};
+typedef struct ts_key_list_s ts_key_list_t;
+
+static void ts_key_list_free(ts_key_list_t *l) /* {{{ */
+{
+  if (l == NULL)
+    return;
+
+  sfree(l->key);
+
+  if (l->next != NULL)
+    ts_key_list_free(l->next);
+
+  sfree(l);
+} /* }}} void ts_name_list_free */
 
 struct ts_data_s {
   char *host;
@@ -36,6 +57,7 @@ struct ts_data_s {
   /* char *type; */
   char *type_instance;
   meta_data_t *meta;
+  ts_key_list_t *meta_delete;
 };
 typedef struct ts_data_s ts_data_t;
 
@@ -46,7 +68,7 @@ static int ts_util_get_key_and_string_wo_strdup(const oconfig_item_t *ci,
   if ((ci->values_num != 2) || (ci->values[0].type != OCONFIG_TYPE_STRING) ||
       (ci->values[1].type != OCONFIG_TYPE_STRING)) {
     ERROR("ts_util_get_key_and_string_wo_strdup: The %s option requires "
-          "exactly two string argument.",
+          "exactly two string arguments.",
           ci->key);
     return (-1);
   }
@@ -88,8 +110,8 @@ static int ts_config_add_meta(meta_data_t **dest, /* {{{ */
     return (status);
 
   if (strlen(key) == 0) {
-    ERROR("Target `set': The `%s' option does not accept empty string as first "
-          "argument.",
+    ERROR("Target `set': The `%s' option does not accept empty string as "
+          "first argument.",
           ci->key);
     return (-1);
   }
@@ -102,16 +124,87 @@ static int ts_config_add_meta(meta_data_t **dest, /* {{{ */
   }
 
   if ((*dest) == NULL) {
-    // Create a new meta_data_t
+    /* Create a new meta_data_t */
     if ((*dest = meta_data_create()) == NULL) {
       ERROR("Target `set': failed to create a meta data for `%s'.", ci->key);
-      return (-1);
+      return (-ENOMEM);
     }
   }
 
   return (meta_data_add_string(*dest, key, string));
 } /* }}} int ts_config_add_meta */
 
+static int ts_config_add_meta_delete(ts_key_list_t **dest, /* {{{ */
+                                     const oconfig_item_t *ci) {
+  ts_key_list_t *entry = NULL;
+
+  entry = calloc(1, sizeof(*entry));
+  if (entry == NULL) {
+    ERROR("ts_config_add_meta_delete: calloc failed.");
+    return (-ENOMEM);
+  }
+
+  if (cf_util_get_string(ci, &entry->key) != 0) {
+    ts_key_list_free(entry);
+    return (-1); /* An error has already been reported. */
+  }
+
+  if (strlen(entry->key) == 0) {
+    ERROR("Target `set': The `%s' option does not accept empty string as "
+          "first argument.",
+          ci->key);
+    ts_key_list_free(entry);
+    return (-1);
+  }
+
+  entry->next = *dest;
+  *dest = entry;
+
+  return (0);
+} /* }}} int ts_config_add_meta_delete */
+
+static void ts_subst(char *dest, size_t size, const char *string, /* {{{ */
+                     const value_list_t *vl) {
+  char temp[DATA_MAX_NAME_LEN];
+
+  /* Initialize the field with the template. */
+  sstrncpy(dest, string, size);
+
+  if (strchr(dest, '%') == NULL)
+    return;
+
+#define REPLACE_FIELD(t, v)                                                    \
+  if (subst_string(temp, sizeof(temp), dest, t, v) != NULL)                    \
+    sstrncpy(dest, temp, size);
+  REPLACE_FIELD("%{host}", vl->host);
+  REPLACE_FIELD("%{plugin}", vl->plugin);
+  REPLACE_FIELD("%{plugin_instance}", vl->plugin_instance);
+  REPLACE_FIELD("%{type}", vl->type);
+  REPLACE_FIELD("%{type_instance}", vl->type_instance);
+
+  if (vl->meta != NULL) {
+    char **meta_toc = NULL;
+    int meta_entries = meta_data_toc(vl->meta, &meta_toc);
+    if (meta_entries <= 0)
+      return;
+
+    for (int i = 0; i < meta_entries; i++) {
+      char meta_name[DATA_MAX_NAME_LEN];
+      char *value_str;
+      const char *key = meta_toc[i];
+
+      ssnprintf(meta_name, sizeof(meta_name), "%%{meta:%s}", key);
+      if (meta_data_as_string(vl->meta, key, &value_str) != 0)
+        continue;
+
+      REPLACE_FIELD(meta_name, value_str);
+      sfree(value_str);
+    }
+
+    strarray_free(meta_toc, (size_t)meta_entries);
+  }
+} /* }}} int ts_subst */
+
 static int ts_destroy(void **user_data) /* {{{ */
 {
   ts_data_t *data;
@@ -129,6 +222,7 @@ static int ts_destroy(void **user_data) /* {{{ */
   /* free (data->type); */
   free(data->type_instance);
   meta_data_destroy(data->meta);
+  ts_key_list_free(data->meta_delete);
   free(data);
 
   return (0);
@@ -151,6 +245,7 @@ static int ts_create(const oconfig_item_t *ci, void **user_data) /* {{{ */
   /* data->type = NULL; */
   data->type_instance = NULL;
   data->meta = NULL;
+  data->meta_delete = NULL;
 
   status = 0;
   for (int i = 0; i < ci->children_num; i++) {
@@ -177,6 +272,8 @@ static int ts_create(const oconfig_item_t *ci, void **user_data) /* {{{ */
     else if (strcasecmp("MetaData", child->key) == 0)
       status = ts_config_add_meta(&data->meta, child,
                                   /* may be empty = */ 1);
+    else if (strcasecmp("DeleteMetaData", child->key) == 0)
+      status = ts_config_add_meta_delete(&data->meta_delete, child);
     else {
       ERROR("Target `set': The `%s' configuration option is not understood "
             "and will be ignored.",
@@ -193,12 +290,26 @@ static int ts_create(const oconfig_item_t *ci, void **user_data) /* {{{ */
     if ((data->host == NULL) && (data->plugin == NULL) &&
         (data->plugin_instance == NULL)
         /* && (data->type == NULL) */
-        && (data->type_instance == NULL) && (data->meta == NULL)) {
+        && (data->type_instance == NULL) && (data->meta == NULL) &&
+        (data->meta_delete == NULL)) {
       ERROR("Target `set': You need to set at least one of `Host', "
-            "`Plugin', `PluginInstance', `TypeInstance', `MetaData'.");
+            "`Plugin', `PluginInstance', `TypeInstance', "
+            "`MetaData', or `DeleteMetaData'.");
       status = -1;
     }
 
+    if (data->meta != NULL) {
+      /* If data->meta_delete is NULL, this loop is a no-op. */
+      for (ts_key_list_t *l = data->meta_delete; l != NULL; l = l->next) {
+        if (meta_data_type(data->meta, l->key) != 0) {
+          /* MetaData and DeleteMetaData for the same key. */
+          ERROR("Target `set': Can only have one of `MetaData' or "
+                "`DeleteMetaData' for any given key.");
+          status = -1;
+        }
+      }
+    }
+
     break;
   }
 
@@ -215,6 +326,8 @@ static int ts_invoke(const data_set_t *ds, value_list_t *vl, /* {{{ */
                      notification_meta_t __attribute__((unused)) * *meta,
                      void **user_data) {
   ts_data_t *data;
+  value_list_t orig;
+  meta_data_t *new_meta = NULL;
 
   if ((ds == NULL) || (vl == NULL) || (user_data == NULL))
     return (-EINVAL);
@@ -225,19 +338,76 @@ static int ts_invoke(const data_set_t *ds, value_list_t *vl, /* {{{ */
     return (-EINVAL);
   }
 
+  orig = *vl;
+
   if (data->meta != NULL) {
-    meta_data_clone_merge(&(vl->meta), data->meta);
+    char temp[DATA_MAX_NAME_LEN * 2];
+    int meta_entries;
+    char **meta_toc;
+
+    if ((new_meta = meta_data_create()) == NULL) {
+      ERROR("Target `set': failed to create replacement metadata.");
+      return (-ENOMEM);
+    }
+
+    meta_entries = meta_data_toc(data->meta, &meta_toc);
+    for (int i = 0; i < meta_entries; i++) {
+      const char *key = meta_toc[i];
+      char *string;
+      int status;
+
+      status = meta_data_get_string(data->meta, key, &string);
+      if (status) {
+        ERROR("Target `set': Unable to get replacement metadata value `%s'.",
+              key);
+        strarray_free(meta_toc, (size_t)meta_entries);
+        meta_data_destroy(new_meta);
+        return (status);
+      }
+
+      ts_subst(temp, sizeof(temp), string, &orig);
+
+      DEBUG("target_set: ts_invoke: setting metadata value for key `%s': "
+            "`%s'.",
+            key, temp);
+
+      sfree(string);
+
+      status = meta_data_add_string(new_meta, key, temp);
+      if (status) {
+        ERROR("Target `set': Unable to set metadata value `%s'.", key);
+        strarray_free(meta_toc, (size_t)meta_entries);
+        meta_data_destroy(new_meta);
+        return (status);
+      }
+    }
+
+    strarray_free(meta_toc, (size_t)meta_entries);
   }
 
-#define SET_FIELD(f)                                                           \
+#define SUBST_FIELD(f)                                                         \
   if (data->f != NULL) {                                                       \
-    sstrncpy(vl->f, data->f, sizeof(vl->f));                                   \
+    ts_subst(vl->f, sizeof(vl->f), data->f, &orig);                            \
+    DEBUG("target_set: ts_invoke: setting " #f ": `%s'.", vl->f);              \
+  }
+  SUBST_FIELD(host);
+  SUBST_FIELD(plugin);
+  SUBST_FIELD(plugin_instance);
+  /* SUBST_FIELD (type); */
+  SUBST_FIELD(type_instance);
+
+  /* Need to merge the metadata in now, because of the shallow copy. */
+  if (new_meta != NULL) {
+    meta_data_clone_merge(&(vl->meta), new_meta);
+    meta_data_destroy(new_meta);
+  }
+
+  /* If data->meta_delete is NULL, this loop is a no-op. */
+  for (ts_key_list_t *l = data->meta_delete; l != NULL; l = l->next) {
+    DEBUG("target_set: ts_invoke: deleting metadata value for key `%s'.",
+          l->key);
+    meta_data_delete(vl->meta, l->key);
   }
-  SET_FIELD(host);
-  SET_FIELD(plugin);
-  SET_FIELD(plugin_instance);
-  /* SET_FIELD (type); */
-  SET_FIELD(type_instance);
 
   return (FC_TARGET_CONTINUE);
 } /* }}} int ts_invoke */
index ea0157b..54e37e1 100644 (file)
@@ -53,7 +53,6 @@ static void v5_swap_instances(value_list_t *vl) /* {{{ */
 static int v5_df(const data_set_t *ds, value_list_t *vl) /* {{{ */
 {
   value_list_t new_vl;
-  value_t new_value;
 
   /* Can't upgrade if both instances have been set. */
   if ((vl->plugin_instance[0] != 0) && (vl->type_instance[0] != 0))
@@ -63,7 +62,7 @@ static int v5_df(const data_set_t *ds, value_list_t *vl) /* {{{ */
   memcpy(&new_vl, vl, sizeof(new_vl));
 
   /* Reset data we can't simply copy */
-  new_vl.values = &new_value;
+  new_vl.values = &(value_t){.gauge = NAN};
   new_vl.values_len = 1;
   new_vl.meta = NULL;
 
@@ -112,7 +111,6 @@ static int v5_interface(const data_set_t *ds, value_list_t *vl) /* {{{ */
 static int v5_mysql_qcache(const data_set_t *ds, value_list_t *vl) /* {{{ */
 {
   value_list_t new_vl;
-  value_t new_value;
 
   if (vl->values_len != 5)
     return (FC_TARGET_STOP);
@@ -121,7 +119,7 @@ static int v5_mysql_qcache(const data_set_t *ds, value_list_t *vl) /* {{{ */
   memcpy(&new_vl, vl, sizeof(new_vl));
 
   /* Reset data we can't simply copy */
-  new_vl.values = &new_value;
+  new_vl.values = &(value_t){.gauge = NAN};
   new_vl.values_len = 1;
   new_vl.meta = NULL;
 
@@ -167,7 +165,6 @@ static int v5_mysql_qcache(const data_set_t *ds, value_list_t *vl) /* {{{ */
 static int v5_mysql_threads(const data_set_t *ds, value_list_t *vl) /* {{{ */
 {
   value_list_t new_vl;
-  value_t new_value;
 
   if (vl->values_len != 4)
     return (FC_TARGET_STOP);
@@ -176,7 +173,7 @@ static int v5_mysql_threads(const data_set_t *ds, value_list_t *vl) /* {{{ */
   memcpy(&new_vl, vl, sizeof(new_vl));
 
   /* Reset data we can't simply copy */
-  new_vl.values = &new_value;
+  new_vl.values = &(value_t){.gauge = NAN};
   new_vl.values_len = 1;
   new_vl.meta = NULL;
 
@@ -216,7 +213,6 @@ static int v5_mysql_threads(const data_set_t *ds, value_list_t *vl) /* {{{ */
 static int v5_zfs_arc_counts(const data_set_t *ds, value_list_t *vl) /* {{{ */
 {
   value_list_t new_vl;
-  value_t new_value;
   _Bool is_hits;
 
   if (vl->values_len != 4)
@@ -233,7 +229,7 @@ static int v5_zfs_arc_counts(const data_set_t *ds, value_list_t *vl) /* {{{ */
   memcpy(&new_vl, vl, sizeof(new_vl));
 
   /* Reset data we can't simply copy */
-  new_vl.values = &new_value;
+  new_vl.values = &(value_t){.gauge = NAN};
   new_vl.values_len = 1;
   new_vl.meta = NULL;
 
@@ -273,7 +269,6 @@ static int v5_zfs_arc_counts(const data_set_t *ds, value_list_t *vl) /* {{{ */
 static int v5_zfs_arc_l2_bytes(const data_set_t *ds, value_list_t *vl) /* {{{ */
 {
   value_list_t new_vl;
-  value_t new_values[2];
 
   if (vl->values_len != 2)
     return (FC_TARGET_STOP);
@@ -282,8 +277,6 @@ static int v5_zfs_arc_l2_bytes(const data_set_t *ds, value_list_t *vl) /* {{{ */
   memcpy(&new_vl, vl, sizeof(new_vl));
 
   /* Reset data we can't simply copy */
-  new_vl.values = new_values;
-  new_vl.values_len = 2;
   new_vl.meta = NULL;
 
   /* Change the type/-instance to "io_octets-L2" */
@@ -291,8 +284,12 @@ static int v5_zfs_arc_l2_bytes(const data_set_t *ds, value_list_t *vl) /* {{{ */
   sstrncpy(new_vl.type_instance, "L2", sizeof(new_vl.type_instance));
 
   /* Copy the actual values. */
-  new_vl.values[0].derive = (derive_t)vl->values[0].counter;
-  new_vl.values[1].derive = (derive_t)vl->values[1].counter;
+  value_t values[] = {
+      {.derive = (derive_t)vl->values[0].counter},
+      {.derive = (derive_t)vl->values[1].counter},
+  };
+  new_vl.values = values;
+  new_vl.values_len = STATIC_ARRAY_SIZE(values);
 
   /* Dispatch new value lists instead of this one */
   plugin_dispatch_values(&new_vl);
@@ -310,7 +307,6 @@ static int v5_zfs_arc_l2_bytes(const data_set_t *ds, value_list_t *vl) /* {{{ */
 static int v5_zfs_arc_l2_size(const data_set_t *ds, value_list_t *vl) /* {{{ */
 {
   value_list_t new_vl;
-  value_t new_value;
 
   if (vl->values_len != 1)
     return (FC_TARGET_STOP);
@@ -319,7 +315,7 @@ static int v5_zfs_arc_l2_size(const data_set_t *ds, value_list_t *vl) /* {{{ */
   memcpy(&new_vl, vl, sizeof(new_vl));
 
   /* Reset data we can't simply copy */
-  new_vl.values = &new_value;
+  new_vl.values = &(value_t){.gauge = NAN};
   new_vl.values_len = 1;
   new_vl.meta = NULL;
 
@@ -347,7 +343,6 @@ static int v5_zfs_arc_l2_size(const data_set_t *ds, value_list_t *vl) /* {{{ */
 static int v5_zfs_arc_ratio(const data_set_t *ds, value_list_t *vl) /* {{{ */
 {
   value_list_t new_vl;
-  value_t new_value;
 
   if (vl->values_len != 1)
     return (FC_TARGET_STOP);
@@ -356,7 +351,7 @@ static int v5_zfs_arc_ratio(const data_set_t *ds, value_list_t *vl) /* {{{ */
   memcpy(&new_vl, vl, sizeof(new_vl));
 
   /* Reset data we can't simply copy */
-  new_vl.values = &new_value;
+  new_vl.values = &(value_t){.gauge = NAN};
   new_vl.values_len = 1;
   new_vl.meta = NULL;
 
@@ -385,7 +380,6 @@ static int v5_zfs_arc_ratio(const data_set_t *ds, value_list_t *vl) /* {{{ */
 static int v5_zfs_arc_size(const data_set_t *ds, value_list_t *vl) /* {{{ */
 {
   value_list_t new_vl;
-  value_t new_value;
 
   if (vl->values_len != 4)
     return (FC_TARGET_STOP);
@@ -394,7 +388,7 @@ static int v5_zfs_arc_size(const data_set_t *ds, value_list_t *vl) /* {{{ */
   memcpy(&new_vl, vl, sizeof(new_vl));
 
   /* Reset data we can't simply copy */
-  new_vl.values = &new_value;
+  new_vl.values = &(value_t){.gauge = NAN};
   new_vl.values_len = 1;
   new_vl.meta = NULL;
 
index 150a7b7..7036d08 100644 (file)
@@ -251,7 +251,6 @@ static enum { SRC_DUNNO, SRC_NETLINK, SRC_PROC } linux_source = SRC_DUNNO;
 static void conn_prepare_vl(value_list_t *vl, value_t *values) {
   vl->values = values;
   vl->values_len = 1;
-  sstrncpy(vl->host, hostname_g, sizeof(vl->host));
   sstrncpy(vl->plugin, "tcpconns", sizeof(vl->plugin));
   sstrncpy(vl->type, "tcp_connections", sizeof(vl->type));
 }
@@ -746,9 +745,15 @@ static int conn_read(void) {
   for (in_ptr = (struct xinpgen *)(((char *)in_orig) + in_orig->xig_len);
        in_ptr->xig_len > sizeof(struct xinpgen);
        in_ptr = (struct xinpgen *)(((char *)in_ptr) + in_ptr->xig_len)) {
+#if __FreeBSD_version >= 1200026
+    struct xtcpcb *tp = (struct xtcpcb *)in_ptr;
+    struct xinpcb *inp = &tp->xt_inp;
+    struct xsocket *so = &inp->xi_socket;
+#else
     struct tcpcb *tp = &((struct xtcpcb *)in_ptr)->xt_tp;
     struct inpcb *inp = &((struct xtcpcb *)in_ptr)->xt_inp;
     struct xsocket *so = &((struct xtcpcb *)in_ptr)->xt_socket;
+#endif
 
     /* Ignore non-TCP sockets */
     if (so->xso_protocol != IPPROTO_TCP)
index 5b583b8..960d646 100644 (file)
@@ -108,14 +108,10 @@ static void tss2_submit_gauge(const char *plugin_instance, const char *type,
   /*
    * Submits a gauge value to the collectd daemon
    */
-  value_t values[1];
   value_list_t vl = VALUE_LIST_INIT;
 
-  values[0].gauge = value;
-
-  vl.values = values;
+  vl.values = &(value_t){.gauge = value};
   vl.values_len = 1;
-  sstrncpy(vl.host, hostname_g, sizeof(vl.host));
   sstrncpy(vl.plugin, "teamspeak2", sizeof(vl.plugin));
 
   if (plugin_instance != NULL)
@@ -134,15 +130,13 @@ static void tss2_submit_io(const char *plugin_instance, const char *type,
   /*
    * Submits the io rx/tx tuple to the collectd daemon
    */
-  value_t values[2];
   value_list_t vl = VALUE_LIST_INIT;
-
-  values[0].derive = rx;
-  values[1].derive = tx;
+  value_t values[] = {
+      {.derive = rx}, {.derive = tx},
+  };
 
   vl.values = values;
-  vl.values_len = 2;
-  sstrncpy(vl.host, hostname_g, sizeof(vl.host));
+  vl.values_len = STATIC_ARRAY_SIZE(values);
   sstrncpy(vl.plugin, "teamspeak2", sizeof(vl.plugin));
 
   if (plugin_instance != NULL)
index 4422ff9..cccb3db 100644 (file)
--- a/src/ted.c
+++ b/src/ted.c
@@ -229,14 +229,10 @@ static int ted_open_device(void) {
 } /* int ted_open_device */
 
 static void ted_submit(const char *type, double value) {
-  value_t values[1];
   value_list_t vl = VALUE_LIST_INIT;
 
-  values[0].gauge = value;
-
-  vl.values = values;
+  vl.values = &(value_t){.gauge = value};
   vl.values_len = 1;
-  sstrncpy(vl.host, hostname_g, sizeof(vl.host));
   sstrncpy(vl.plugin, "ted", sizeof(vl.plugin));
   sstrncpy(vl.type, type, sizeof(vl.type));
 
index 49124da..42f45ce 100644 (file)
@@ -51,10 +51,13 @@ static int check_count__ = 0;
 
 #define END_TEST exit((fail_count__ == 0) ? 0 : 1);
 
+#define LOG(result, text)                                                      \
+  printf("%s %i - %s\n", result ? "ok" : "not ok", ++check_count__, text)
+
 #define OK1(cond, text)                                                        \
   do {                                                                         \
     _Bool result = (cond);                                                     \
-    printf("%s %i - %s\n", result ? "ok" : "not ok", ++check_count__, text);   \
+    LOG(result, text);                                                         \
     if (!result) {                                                             \
       return -1;                                                               \
     }                                                                          \
index d2d31e1..fbae6cc 100644 (file)
@@ -41,16 +41,12 @@ static ignorelist_t *device_list;
 enum dev_type { TEMP = 0, COOLING_DEV };
 
 static void thermal_submit(const char *plugin_instance, enum dev_type dt,
-                           gauge_t value) {
+                           value_t value) {
   value_list_t vl = VALUE_LIST_INIT;
-  value_t v;
-
-  v.gauge = value;
-  vl.values = &v;
 
+  vl.values = &value;
   vl.values_len = 1;
 
-  sstrncpy(vl.host, hostname_g, sizeof(vl.host));
   sstrncpy(vl.plugin, "thermal", sizeof(vl.plugin));
   if (plugin_instance != NULL)
     sstrncpy(vl.plugin_instance, plugin_instance, sizeof(vl.plugin_instance));
@@ -62,52 +58,24 @@ static void thermal_submit(const char *plugin_instance, enum dev_type dt,
 static int thermal_sysfs_device_read(const char __attribute__((unused)) * dir,
                                      const char *name,
                                      void __attribute__((unused)) * user_data) {
-  char filename[256];
-  char data[1024];
-  int len;
+  char filename[PATH_MAX];
   _Bool success = 0;
+  value_t value;
 
   if (device_list && ignorelist_match(device_list, name))
     return -1;
 
-  len =
-      ssnprintf(filename, sizeof(filename), "%s/%s/temp", dirname_sysfs, name);
-  if ((len < 0) || ((size_t)len >= sizeof(filename)))
-    return -1;
-
-  len = (ssize_t)read_file_contents(filename, data, sizeof(data));
-  if (len > 1 && data[--len] == '\n') {
-    char *endptr = NULL;
-    double temp;
-
-    data[len] = 0;
-    errno = 0;
-    temp = strtod(data, &endptr) / 1000.0;
-
-    if (endptr == data + len && errno == 0) {
-      thermal_submit(name, TEMP, temp);
-      success = 1;
-    }
+  ssnprintf(filename, sizeof(filename), "%s/%s/temp", dirname_sysfs, name);
+  if (parse_value_file(filename, &value, DS_TYPE_GAUGE) == 0) {
+    value.gauge /= 1000.0;
+    thermal_submit(name, TEMP, value);
+    success = 1;
   }
 
-  len = ssnprintf(filename, sizeof(filename), "%s/%s/cur_state", dirname_sysfs,
-                  name);
-  if ((len < 0) || ((size_t)len >= sizeof(filename)))
-    return -1;
-
-  len = (ssize_t)read_file_contents(filename, data, sizeof(data));
-  if (len > 1 && data[--len] == '\n') {
-    char *endptr = NULL;
-    double state;
-
-    data[len] = 0;
-    errno = 0;
-    state = strtod(data, &endptr);
-
-    if (endptr == data + len && errno == 0) {
-      thermal_submit(name, COOLING_DEV, state);
-      success = 1;
-    }
+  ssnprintf(filename, sizeof(filename), "%s/%s/cur_state", dirname_sysfs, name);
+  if (parse_value_file(filename, &value, DS_TYPE_GAUGE) == 0) {
+    thermal_submit(name, COOLING_DEV, value);
+    success = 1;
   }
 
   return (success ? 0 : -1);
@@ -166,7 +134,7 @@ static int thermal_procfs_device_read(const char __attribute__((unused)) * dir,
     temp = (strtod(data + len, &endptr) + add) * factor;
 
     if (endptr != data + len && errno == 0) {
-      thermal_submit(name, TEMP, temp);
+      thermal_submit(name, TEMP, (value_t){.gauge = temp});
       return 0;
     }
   }
index dd1fb3a..cb366d3 100644 (file)
@@ -72,14 +72,11 @@ static void printerr(void) {
   ERROR("tokyotyrant plugin: error: %d, %s", ecode, tcrdberrmsg(ecode));
 }
 
-static void tt_submit(gauge_t val, const char *type) {
-  value_t values[1];
+static void tt_submit(gauge_t value, const char *type) {
   value_list_t vl = VALUE_LIST_INIT;
 
-  values[0].gauge = val;
-
-  vl.values = values;
-  vl.values_len = STATIC_ARRAY_SIZE(values);
+  vl.values = &(value_t){.gauge = value};
+  vl.values_len = 1;
 
   sstrncpy(vl.host, config_host, sizeof(vl.host));
   sstrncpy(vl.plugin, "tokyotyrant", sizeof(vl.plugin));
index 1e3578a..62bd92b 100644 (file)
 static _Bool aperf_mperf_unstable;
 
 /*
+ * If set, use kernel logical core numbering for all "per core" metrics.
+ */
+static _Bool config_lcn;
+
+/*
  * Bitmask of the list of core C states supported by the processor.
  * Currently supported C-states (by this plugin): 3, 6, 7
  */
@@ -227,6 +232,7 @@ static const char *config_keys[] = {
     "PackageThermalManagement",
     "TCCActivationTemp",
     "RunningAveragePowerLimit",
+    "LogicalCoreNames",
 };
 static const int config_keys_num = STATIC_ARRAY_SIZE(config_keys);
 
@@ -519,12 +525,9 @@ delta_thread(struct thread_data *delta, const struct thread_data *new,
 static void turbostat_submit(const char *plugin_instance, const char *type,
                              const char *type_instance, gauge_t value) {
   value_list_t vl = VALUE_LIST_INIT;
-  value_t v;
 
-  v.gauge = value;
-  vl.values = &v;
+  vl.values = &(value_t){.gauge = value};
   vl.values_len = 1;
-  sstrncpy(vl.host, hostname_g, sizeof(vl.host));
   sstrncpy(vl.plugin, PLUGIN_NAME, sizeof(vl.plugin));
   if (plugin_instance != NULL)
     sstrncpy(vl.plugin_instance, plugin_instance, sizeof(vl.plugin_instance));
@@ -550,6 +553,9 @@ static int submit_counters(struct thread_data *t, struct core_data *c,
 
   interval_float = CDTIME_T_TO_DOUBLE(time_delta);
 
+  DEBUG("turbostat plugin: submit stats for cpu: %d, core: %d, pkg: %d",
+        t->cpu_id, c->core_id, p->package_id);
+
   ssnprintf(name, sizeof(name), "cpu%02d", t->cpu_id);
 
   if (!aperf_mperf_unstable)
@@ -577,7 +583,10 @@ static int submit_counters(struct thread_data *t, struct core_data *c,
   if (!(t->flags & CPU_IS_FIRST_THREAD_IN_CORE))
     goto done;
 
-  ssnprintf(name, sizeof(name), "core%02d", c->core_id);
+  /* If not using logical core numbering, set core id */
+  if (!config_lcn) {
+    ssnprintf(name, sizeof(name), "core%02d", c->core_id);
+  }
 
   if (do_core_cstate & (1 << 3))
     turbostat_submit(name, "percent", "c3", 100.0 * c->c3 / t->tsc);
@@ -1029,8 +1038,7 @@ static int __attribute__((format(printf, 1, 2)))
 parse_int_file(const char *fmt, ...) {
   va_list args;
   char path[PATH_MAX];
-  FILE *filep;
-  int len, value;
+  int len;
 
   va_start(args, fmt);
   len = vsnprintf(path, sizeof(path), fmt, args);
@@ -1040,18 +1048,13 @@ parse_int_file(const char *fmt, ...) {
     return -1;
   }
 
-  filep = fopen(path, "r");
-  if (!filep) {
-    ERROR("turbostat plugin: Failed to open '%s'", path);
-    return -1;
-  }
-  if (fscanf(filep, "%d", &value) != 1) {
-    ERROR("turbostat plugin: Failed to parse number from '%s'", path);
-    fclose(filep);
+  value_t v;
+  if (parse_value_file(path, &v, DS_TYPE_DERIVE) != 0) {
+    ERROR("turbostat plugin: Parsing \"%s\" failed.", path);
     return -1;
   }
-  fclose(filep);
-  return value;
+
+  return (int)v.derive;
 }
 
 static int get_threads_on_core(unsigned int cpu) {
@@ -1564,6 +1567,8 @@ static int turbostat_config(const char *key, const char *value) {
   } else if (strcasecmp("PackageThermalManagement", key) == 0) {
     config_ptm = IS_TRUE(value);
     apply_config_ptm = 1;
+  } else if (strcasecmp("LogicalCoreNames", key) == 0) {
+    config_lcn = IS_TRUE(value);
   } else if (strcasecmp("RunningAveragePowerLimit", key) == 0) {
     tmp_val = strtoul(value, &end, 0);
     if (*end != '\0' || tmp_val > UINT_MAX) {
index 9ebc5fc..6855187 100644 (file)
@@ -9,6 +9,7 @@ ath_stat                value:DERIVE:0:U
 backends                value:GAUGE:0:65535
 bitrate                 value:GAUGE:0:4294967295
 blocked_clients         value:GAUGE:0:U
+bucket                  value:GAUGE:0:U
 bytes                   value:GAUGE:0:U
 cache_eviction          value:DERIVE:0:U
 cache_operation         value:DERIVE:0:U
@@ -74,13 +75,17 @@ duration                seconds:GAUGE:0:U
 email_check             value:GAUGE:0:U
 email_count             value:GAUGE:0:U
 email_size              value:GAUGE:0:U
+energy                  value:GAUGE:U:U
+energy_wh               value:GAUGE:U:U
 entropy                 value:GAUGE:0:4294967295
+errors                  value:DERIVE:0:U
 evicted_keys            value:DERIVE:0:U
 expired_keys            value:DERIVE:0:U
 fanspeed                value:GAUGE:0:U
 file_handles            value:GAUGE:0:U
 file_size               value:GAUGE:0:U
 files                   value:GAUGE:0:U
+filter_result           value:DERIVE:0:U
 flow                    value:GAUGE:0:U
 fork_rate               value:DERIVE:0:U
 frequency               value:GAUGE:0:U
@@ -99,19 +104,25 @@ if_errors               rx:DERIVE:0:U, tx:DERIVE:0:U
 if_multicast            value:DERIVE:0:U
 if_octets               rx:DERIVE:0:U, tx:DERIVE:0:U
 if_packets              rx:DERIVE:0:U, tx:DERIVE:0:U
+if_rx_dropped           value:DERIVE:0:U
 if_rx_errors            value:DERIVE:0:U
 if_rx_octets            value:DERIVE:0:U
+if_rx_packets           value:DERIVE:0:U
+if_tx_dropped           value:DERIVE:0:U
 if_tx_errors            value:DERIVE:0:U
 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_packets              rx:DERIVE:0:U, tx:DERIVE:0:U
+ipc                     value:GAUGE:0:U
 ipt_bytes               value:DERIVE:0:U
 ipt_packets             value:DERIVE:0:U
 irq                     value:DERIVE:0:U
 latency                 value:GAUGE:0:U
 links                   value:GAUGE:0:U
 load                    shortterm:GAUGE:0:5000, midterm:GAUGE:0:5000, longterm:GAUGE:0:5000
+memory_bandwidth        value:DERIVE:0:U
 md_disks                value:GAUGE:0:U
 memcached_command       value:DERIVE:0:U
 memcached_connections   value:GAUGE:0:U
index 020a577..32fa9bc 100644 (file)
@@ -257,17 +257,17 @@ static void *us_handle_client(void *arg) {
     }
 
     if (strcasecmp(fields[0], "getval") == 0) {
-      handle_getval(fhout, buffer);
+      cmd_handle_getval(fhout, buffer);
     } else if (strcasecmp(fields[0], "getthreshold") == 0) {
       handle_getthreshold(fhout, buffer);
     } else if (strcasecmp(fields[0], "putval") == 0) {
-      handle_putval(fhout, buffer);
+      cmd_handle_putval(fhout, buffer);
     } else if (strcasecmp(fields[0], "listval") == 0) {
-      handle_listval(fhout, buffer);
+      cmd_handle_listval(fhout, buffer);
     } else if (strcasecmp(fields[0], "putnotif") == 0) {
       handle_putnotif(fhout, buffer);
     } else if (strcasecmp(fields[0], "flush") == 0) {
-      handle_flush(fhout, buffer);
+      cmd_handle_flush(fhout, buffer);
     } else {
       if (fprintf(fhout, "-1 Unknown command: %s\n", fields[0]) < 0) {
         char errbuf[1024];
@@ -328,7 +328,7 @@ static void *us_server_thread(void __attribute__((unused)) * arg) {
     DEBUG("Spawning child to handle connection on fd #%i", *remote_fd);
 
     status = plugin_thread_create(&th, &th_attr, us_handle_client,
-                                  (void *)remote_fd);
+                                  (void *)remote_fd, "unixsock conn");
     if (status != 0) {
       char errbuf[1024];
       WARNING("unixsock plugin: pthread_create failed: %s",
@@ -395,7 +395,8 @@ static int us_init(void) {
 
   loop = 1;
 
-  status = plugin_thread_create(&listen_thread, NULL, us_server_thread, NULL);
+  status = plugin_thread_create(&listen_thread, NULL, us_server_thread, NULL,
+                                "unixsock listen");
   if (status != 0) {
     char errbuf[1024];
     ERROR("unixsock plugin: pthread_create failed: %s",
index e7479e8..96a227d 100644 (file)
@@ -60,16 +60,12 @@ static time_t boottime;
 extern kstat_ctl_t *kc;
 #endif /* #endif HAVE_LIBKSTAT */
 
-static void uptime_submit(gauge_t uptime) {
-  value_t values[1];
+static void uptime_submit(gauge_t value) {
   value_list_t vl = VALUE_LIST_INIT;
 
-  values[0].gauge = uptime;
-
-  vl.values = values;
+  vl.values = &(value_t){.gauge = value};
   vl.values_len = 1;
 
-  sstrncpy(vl.host, hostname_g, sizeof(vl.host));
   sstrncpy(vl.plugin, "uptime", sizeof(vl.plugin));
   sstrncpy(vl.type, "uptime", sizeof(vl.type));
 
index 1ae128e..ff8c047 100644 (file)
 #endif
 
 static void users_submit(gauge_t value) {
-  value_t values[1];
   value_list_t vl = VALUE_LIST_INIT;
 
-  values[0].gauge = value;
-
-  vl.values = values;
+  vl.values = &(value_t){.gauge = value};
   vl.values_len = 1;
-  sstrncpy(vl.host, hostname_g, sizeof(vl.host));
   sstrncpy(vl.plugin, "users", sizeof(vl.plugin));
   sstrncpy(vl.type, "users", sizeof(vl.plugin));
 
index 8eec975..a8de21a 100644 (file)
@@ -1,6 +1,6 @@
 /**
  * collectd - src/utils_cmd_flush.c
- * Copyright (C) 2008       Sebastian Harl
+ * Copyright (C) 2008, 2016 Sebastian Harl
  * Copyright (C) 2008       Florian Forster
  *
  * Permission is hereby granted, free of charge, to any person obtaining a
 #include "common.h"
 #include "plugin.h"
 #include "utils_cmd_flush.h"
-#include "utils_parse_option.h"
 
-int handle_flush(FILE *fh, char *buffer) {
-  int success = 0;
-  int error = 0;
-
-  double timeout = 0.0;
-  char **plugins = NULL;
-  size_t plugins_num = 0;
-  char **identifiers = NULL;
-  size_t identifiers_num = 0;
-
-#define PRINT_TO_SOCK(fh, ...)                                                 \
-  do {                                                                         \
-    if (fprintf(fh, __VA_ARGS__) < 0) {                                        \
-      char errbuf[1024];                                                       \
-      WARNING("handle_flush: failed to write to socket #%i: %s", fileno(fh),   \
-              sstrerror(errno, errbuf, sizeof(errbuf)));                       \
-      strarray_free(plugins, plugins_num);                                     \
-      strarray_free(identifiers, identifiers_num);                             \
-      return -1;                                                               \
-    }                                                                          \
-    fflush(fh);                                                                \
-  } while (0)
-
-  if ((fh == NULL) || (buffer == NULL))
-    return (-1);
-
-  DEBUG("utils_cmd_flush: handle_flush (fh = %p, buffer = %s);", (void *)fh,
-        buffer);
+cmd_status_t cmd_parse_flush(size_t argc, char **argv, cmd_flush_t *ret_flush,
+                             const cmd_options_t *opts,
+                             cmd_error_handler_t *err) {
 
-  if (strncasecmp("FLUSH", buffer, strlen("FLUSH")) != 0) {
-    PRINT_TO_SOCK(fh, "-1 Cannot parse command.\n");
-    return (-1);
+  if ((ret_flush == NULL) || (opts == NULL)) {
+    errno = EINVAL;
+    cmd_error(CMD_ERROR, err, "Invalid arguments to cmd_parse_flush.");
+    return (CMD_ERROR);
   }
-  buffer += strlen("FLUSH");
 
-  while (*buffer != 0) {
+  for (size_t i = 0; i < argc; i++) {
     char *opt_key;
     char *opt_value;
     int status;
 
     opt_key = NULL;
     opt_value = NULL;
-    status = parse_option(&buffer, &opt_key, &opt_value);
+    status = cmd_parse_option(argv[i], &opt_key, &opt_value, err);
     if (status != 0) {
-      PRINT_TO_SOCK(fh, "-1 Parsing options failed.\n");
-      strarray_free(plugins, plugins_num);
-      strarray_free(identifiers, identifiers_num);
-      return (-1);
+      if (status == CMD_NO_OPTION)
+        cmd_error(CMD_PARSE_ERROR, err, "Invalid option string `%s'.", argv[i]);
+      cmd_destroy_flush(ret_flush);
+      return (CMD_PARSE_ERROR);
     }
 
-    if (strcasecmp("plugin", opt_key) == 0)
-      strarray_add(&plugins, &plugins_num, opt_value);
-    else if (strcasecmp("identifier", opt_key) == 0)
-      strarray_add(&identifiers, &identifiers_num, opt_value);
-    else if (strcasecmp("timeout", opt_key) == 0) {
+    if (strcasecmp("plugin", opt_key) == 0) {
+      strarray_add(&ret_flush->plugins, &ret_flush->plugins_num, opt_value);
+    } else if (strcasecmp("identifier", opt_key) == 0) {
+      identifier_t *id =
+          realloc(ret_flush->identifiers,
+                  (ret_flush->identifiers_num + 1) * sizeof(*id));
+      if (id == NULL) {
+        cmd_error(CMD_ERROR, err, "realloc failed.");
+        cmd_destroy_flush(ret_flush);
+        return (CMD_ERROR);
+      }
+
+      ret_flush->identifiers = id;
+      id = ret_flush->identifiers + ret_flush->identifiers_num;
+      ret_flush->identifiers_num++;
+      if (parse_identifier(opt_value, &id->host, &id->plugin,
+                           &id->plugin_instance, &id->type, &id->type_instance,
+                           opts->identifier_default_host) != 0) {
+        cmd_error(CMD_PARSE_ERROR, err, "Invalid identifier `%s'.", opt_value);
+        cmd_destroy_flush(ret_flush);
+        return (CMD_PARSE_ERROR);
+      }
+    } else if (strcasecmp("timeout", opt_key) == 0) {
       char *endptr;
 
       errno = 0;
       endptr = NULL;
-      timeout = strtod(opt_value, &endptr);
-
-      if ((endptr == opt_value) || (errno != 0) || (!isfinite(timeout))) {
-        PRINT_TO_SOCK(fh, "-1 Invalid value for option `timeout': "
-                          "%s\n",
-                      opt_value);
-        strarray_free(plugins, plugins_num);
-        strarray_free(identifiers, identifiers_num);
-        return (-1);
-      } else if (timeout < 0.0) {
-        timeout = 0.0;
+      ret_flush->timeout = strtod(opt_value, &endptr);
+
+      if ((endptr == opt_value) || (errno != 0) ||
+          (!isfinite(ret_flush->timeout))) {
+        cmd_error(CMD_PARSE_ERROR, err,
+                  "Invalid value for option `timeout': %s", opt_value);
+        cmd_destroy_flush(ret_flush);
+        return (CMD_PARSE_ERROR);
+      } else if (ret_flush->timeout < 0.0) {
+        ret_flush->timeout = 0.0;
       }
     } else {
-      PRINT_TO_SOCK(fh, "-1 Cannot parse option %s\n", opt_key);
-      strarray_free(plugins, plugins_num);
-      strarray_free(identifiers, identifiers_num);
-      return (-1);
+      cmd_error(CMD_PARSE_ERROR, err, "Cannot parse option `%s'.", opt_key);
+      cmd_destroy_flush(ret_flush);
+      return (CMD_PARSE_ERROR);
     }
-  } /* while (*buffer != 0) */
+  }
+
+  return (CMD_OK);
+} /* cmd_status_t cmd_parse_flush */
+
+cmd_status_t cmd_handle_flush(FILE *fh, char *buffer) {
+  cmd_error_handler_t err = {cmd_error_fh, fh};
+  cmd_t cmd;
 
-  for (size_t i = 0; (i == 0) || (i < plugins_num); i++) {
+  int success = 0;
+  int error = 0;
+  int status;
+
+  if ((fh == NULL) || (buffer == NULL))
+    return (-1);
+
+  DEBUG("utils_cmd_flush: cmd_handle_flush (fh = %p, buffer = %s);", (void *)fh,
+        buffer);
+
+  if ((status = cmd_parse(buffer, &cmd, NULL, &err)) != CMD_OK)
+    return (status);
+  if (cmd.type != CMD_FLUSH) {
+    cmd_error(CMD_UNKNOWN_COMMAND, &err, "Unexpected command: `%s'.",
+              CMD_TO_STRING(cmd.type));
+    cmd_destroy(&cmd);
+    return (CMD_UNKNOWN_COMMAND);
+  }
+
+  for (size_t i = 0; (i == 0) || (i < cmd.cmd.flush.plugins_num); i++) {
     char *plugin = NULL;
 
-    if (plugins_num != 0)
-      plugin = plugins[i];
+    if (cmd.cmd.flush.plugins_num != 0)
+      plugin = cmd.cmd.flush.plugins[i];
 
-    for (size_t j = 0; (j == 0) || (j < identifiers_num); j++) {
+    for (size_t j = 0; (j == 0) || (j < cmd.cmd.flush.identifiers_num); j++) {
       char *identifier = NULL;
+      char buffer[1024];
       int status;
 
-      if (identifiers_num != 0)
-        identifier = identifiers[j];
+      if (cmd.cmd.flush.identifiers_num != 0) {
+        identifier_t *id = cmd.cmd.flush.identifiers + j;
+        if (format_name(buffer, sizeof(buffer), id->host, id->plugin,
+                        id->plugin_instance, id->type,
+                        id->type_instance) != 0) {
+          error++;
+          continue;
+        }
+        identifier = buffer;
+      }
 
-      status = plugin_flush(plugin, DOUBLE_TO_CDTIME_T(timeout), identifier);
+      status = plugin_flush(plugin, DOUBLE_TO_CDTIME_T(cmd.cmd.flush.timeout),
+                            identifier);
       if (status == 0)
         success++;
       else
@@ -133,12 +159,23 @@ int handle_flush(FILE *fh, char *buffer) {
     }
   }
 
-  PRINT_TO_SOCK(fh, "0 Done: %i successful, %i errors\n", success, error);
+  cmd_error(CMD_OK, &err, "Done: %i successful, %i errors", success, error);
 
-  strarray_free(plugins, plugins_num);
-  strarray_free(identifiers, identifiers_num);
+  cmd_destroy(&cmd);
   return (0);
 #undef PRINT_TO_SOCK
-} /* int handle_flush */
+} /* cmd_status_t cmd_handle_flush */
+
+void cmd_destroy_flush(cmd_flush_t *flush) {
+  if (flush == NULL)
+    return;
+
+  strarray_free(flush->plugins, flush->plugins_num);
+  flush->plugins = NULL;
+  flush->plugins_num = 0;
+
+  sfree(flush->identifiers);
+  flush->identifiers_num = 0;
+} /* void cmd_destroy_flush */
 
 /* vim: set sw=4 ts=4 tw=78 noexpandtab : */
index 031fcac..d9b4e1b 100644 (file)
@@ -1,6 +1,6 @@
 /**
  * collectd - src/utils_cmd_flush.h
- * Copyright (C) 2008       Sebastian Harl
+ * Copyright (C) 2008, 2016 Sebastian Harl
  *
  * Permission is hereby granted, free of charge, to any person obtaining a
  * copy of this software and associated documentation files (the "Software"),
 #ifndef UTILS_CMD_FLUSH_H
 #define UTILS_CMD_FLUSH_H 1
 
+#include "utils_cmds.h"
+
 #include <stdio.h>
 
-int handle_flush(FILE *fh, char *buffer);
+cmd_status_t cmd_parse_flush(size_t argc, char **argv, cmd_flush_t *ret_flush,
+                             const cmd_options_t *opts,
+                             cmd_error_handler_t *err);
+
+cmd_status_t cmd_handle_flush(FILE *fh, char *buffer);
+
+void cmd_destroy_flush(cmd_flush_t *flush);
 
 #endif /* UTILS_CMD_FLUSH_H */
 
index ac2578d..926375d 100644 (file)
@@ -95,7 +95,8 @@ int handle_getthreshold(FILE *fh, char *buffer) {
   identifier_copy = sstrdup(identifier);
 
   status = parse_identifier(identifier_copy, &host, &plugin, &plugin_instance,
-                            &type, &type_instance);
+                            &type, &type_instance,
+                            /* default_host = */ NULL);
   if (status != 0) {
     DEBUG("handle_getthreshold: Cannot parse identifier `%s'.", identifier);
     print_to_socket(fh, "-1 Cannot parse identifier `%s'.\n", identifier);
@@ -154,48 +155,31 @@ int handle_getthreshold(FILE *fh, char *buffer) {
   print_to_socket(fh, "%zu Threshold found\n", i);
 
   if (threshold.host[0] != 0)
-    print_to_socket(fh, "Host: %s\n", threshold.host) if (
-        threshold.plugin[0] !=
-        0) print_to_socket(fh, "Plugin: %s\n",
-                           threshold.plugin) if (threshold.plugin_instance[0] !=
-                                                 0)
-        print_to_socket(fh, "Plugin Instance: %s\n",
-                        threshold.plugin_instance) if (threshold.type[0] != 0)
-            print_to_socket(fh, "Type: %s\n", threshold.type) if (
-                threshold.type_instance[0] !=
-                0) print_to_socket(fh, "Type Instance: %s\n",
-                                   threshold
-                                       .type_instance) if (threshold.data_source
-                                                               [0] != 0)
-                print_to_socket(
-                    fh, "Data Source: %s\n",
-                    threshold.data_source) if (!isnan(threshold.warning_min))
-                    print_to_socket(
-                        fh, "Warning Min: %g\n",
-                        threshold
-                            .warning_min) if (!isnan(threshold.warning_max))
-                        print_to_socket(
-                            fh, "Warning Max: %g\n",
-                            threshold
-                                .warning_max) if (!isnan(threshold.failure_min))
-                            print_to_socket(
-                                fh, "Failure Min: %g\n",
-                                threshold
-                                    .failure_min) if (!isnan(threshold
-                                                                 .failure_max))
-                                print_to_socket(
-                                    fh, "Failure Max: %g\n",
-                                    threshold.failure_max) if (threshold
-                                                                   .hysteresis >
-                                                               0.0)
-                                    print_to_socket(
-                                        fh, "Hysteresis: %g\n",
-                                        threshold.hysteresis) if (threshold
-                                                                      .hits > 1)
-                                        print_to_socket(fh, "Hits: %i\n",
-                                                        threshold.hits)
-
-                                            return (0);
+    print_to_socket(fh, "Host: %s\n", threshold.host);
+  if (threshold.plugin[0] != 0)
+    print_to_socket(fh, "Plugin: %s\n", threshold.plugin);
+  if (threshold.plugin_instance[0] != 0)
+    print_to_socket(fh, "Plugin Instance: %s\n", threshold.plugin_instance);
+  if (threshold.type[0] != 0)
+    print_to_socket(fh, "Type: %s\n", threshold.type);
+  if (threshold.type_instance[0] != 0)
+    print_to_socket(fh, "Type Instance: %s\n", threshold.type_instance);
+  if (threshold.data_source[0] != 0)
+    print_to_socket(fh, "Data Source: %s\n", threshold.data_source);
+  if (!isnan(threshold.warning_min))
+    print_to_socket(fh, "Warning Min: %g\n", threshold.warning_min);
+  if (!isnan(threshold.warning_max))
+    print_to_socket(fh, "Warning Max: %g\n", threshold.warning_max);
+  if (!isnan(threshold.failure_min))
+    print_to_socket(fh, "Failure Min: %g\n", threshold.failure_min);
+  if (!isnan(threshold.failure_max))
+    print_to_socket(fh, "Failure Max: %g\n", threshold.failure_max);
+  if (threshold.hysteresis > 0.0)
+    print_to_socket(fh, "Hysteresis: %g\n", threshold.hysteresis);
+  if (threshold.hits > 1)
+    print_to_socket(fh, "Hits: %i\n", threshold.hits);
+
+  return (0);
 } /* int handle_getthreshold */
 
 /* vim: set sw=2 sts=2 ts=8 et : */
index 3df13b1..c23650b 100644 (file)
 #include "utils_cmd_getval.h"
 #include "utils_parse_option.h"
 
+cmd_status_t cmd_parse_getval(size_t argc, char **argv,
+                              cmd_getval_t *ret_getval,
+                              const cmd_options_t *opts,
+                              cmd_error_handler_t *err) {
+  char *identifier_copy;
+  int status;
+
+  if ((ret_getval == NULL) || (opts == NULL)) {
+    errno = EINVAL;
+    cmd_error(CMD_ERROR, err, "Invalid arguments to cmd_parse_getval.");
+    return (CMD_ERROR);
+  }
+
+  if (argc != 1) {
+    if (argc == 0)
+      cmd_error(CMD_PARSE_ERROR, err, "Missing identifier.");
+    else
+      cmd_error(CMD_PARSE_ERROR, err, "Garbage after identifier: `%s'.",
+                argv[1]);
+    return (CMD_PARSE_ERROR);
+  }
+
+  /* parse_identifier() modifies its first argument,
+   * returning pointers into it */
+  identifier_copy = sstrdup(argv[0]);
+
+  status = parse_identifier(
+      argv[0], &ret_getval->identifier.host, &ret_getval->identifier.plugin,
+      &ret_getval->identifier.plugin_instance, &ret_getval->identifier.type,
+      &ret_getval->identifier.type_instance, opts->identifier_default_host);
+  if (status != 0) {
+    DEBUG("cmd_parse_getval: Cannot parse identifier `%s'.", identifier_copy);
+    cmd_error(CMD_PARSE_ERROR, err, "Cannot parse identifier `%s'.",
+              identifier_copy);
+    sfree(identifier_copy);
+    return (CMD_PARSE_ERROR);
+  }
+
+  ret_getval->raw_identifier = identifier_copy;
+  return (CMD_OK);
+} /* cmd_status_t cmd_parse_getval */
+
 #define print_to_socket(fh, ...)                                               \
   do {                                                                         \
     if (fprintf(fh, __VA_ARGS__) < 0) {                                        \
       char errbuf[1024];                                                       \
-      WARNING("handle_getval: failed to write to socket #%i: %s", fileno(fh),  \
-              sstrerror(errno, errbuf, sizeof(errbuf)));                       \
+      WARNING("cmd_handle_getval: failed to write to socket #%i: %s",          \
+              fileno(fh), sstrerror(errno, errbuf, sizeof(errbuf)));           \
       return -1;                                                               \
     }                                                                          \
     fflush(fh);                                                                \
   } while (0)
 
-int handle_getval(FILE *fh, char *buffer) {
-  char *command;
-  char *identifier;
-  char *identifier_copy;
+cmd_status_t cmd_handle_getval(FILE *fh, char *buffer) {
+  cmd_error_handler_t err = {cmd_error_fh, fh};
+  cmd_status_t status;
+  cmd_t cmd;
 
-  char *hostname;
-  char *plugin;
-  char *plugin_instance;
-  char *type;
-  char *type_instance;
   gauge_t *values;
   size_t values_num;
 
   const data_set_t *ds;
 
-  int status;
-
   if ((fh == NULL) || (buffer == NULL))
     return (-1);
 
-  DEBUG("utils_cmd_getval: handle_getval (fh = %p, buffer = %s);", (void *)fh,
-        buffer);
+  DEBUG("utils_cmd_getval: cmd_handle_getval (fh = %p, buffer = %s);",
+        (void *)fh, buffer);
 
-  command = NULL;
-  status = parse_string(&buffer, &command);
-  if (status != 0) {
-    print_to_socket(fh, "-1 Cannot parse command.\n");
-    return (-1);
-  }
-  assert(command != NULL);
-
-  if (strcasecmp("GETVAL", command) != 0) {
-    print_to_socket(fh, "-1 Unexpected command: `%s'.\n", command);
-    return (-1);
+  if ((status = cmd_parse(buffer, &cmd, NULL, &err)) != CMD_OK)
+    return (status);
+  if (cmd.type != CMD_GETVAL) {
+    cmd_error(CMD_UNKNOWN_COMMAND, &err, "Unexpected command: `%s'.",
+              CMD_TO_STRING(cmd.type));
+    cmd_destroy(&cmd);
+    return (CMD_UNKNOWN_COMMAND);
   }
 
-  identifier = NULL;
-  status = parse_string(&buffer, &identifier);
-  if (status != 0) {
-    print_to_socket(fh, "-1 Cannot parse identifier.\n");
-    return (-1);
-  }
-  assert(identifier != NULL);
-
-  if (*buffer != 0) {
-    print_to_socket(fh, "-1 Garbage after end of command: %s\n", buffer);
-    return (-1);
-  }
-
-  /* parse_identifier() modifies its first argument,
-   * returning pointers into it */
-  identifier_copy = sstrdup(identifier);
-
-  status = parse_identifier(identifier_copy, &hostname, &plugin,
-                            &plugin_instance, &type, &type_instance);
-  if (status != 0) {
-    DEBUG("handle_getval: Cannot parse identifier `%s'.", identifier);
-    print_to_socket(fh, "-1 Cannot parse identifier `%s'.\n", identifier);
-    sfree(identifier_copy);
-    return (-1);
-  }
-
-  ds = plugin_get_ds(type);
+  ds = plugin_get_ds(cmd.cmd.getval.identifier.type);
   if (ds == NULL) {
-    DEBUG("handle_getval: plugin_get_ds (%s) == NULL;", type);
-    print_to_socket(fh, "-1 Type `%s' is unknown.\n", type);
-    sfree(identifier_copy);
+    DEBUG("cmd_handle_getval: plugin_get_ds (%s) == NULL;",
+          cmd.cmd.getval.identifier.type);
+    cmd_error(CMD_ERROR, &err, "Type `%s' is unknown.\n",
+              cmd.cmd.getval.identifier.type);
+    cmd_destroy(&cmd);
     return (-1);
   }
 
   values = NULL;
   values_num = 0;
-  status = uc_get_rate_by_name(identifier, &values, &values_num);
+  status =
+      uc_get_rate_by_name(cmd.cmd.getval.raw_identifier, &values, &values_num);
   if (status != 0) {
-    print_to_socket(fh, "-1 No such value\n");
-    sfree(identifier_copy);
-    return (-1);
+    cmd_error(CMD_ERROR, &err, "No such value.");
+    cmd_destroy(&cmd);
+    return (CMD_ERROR);
   }
 
   if (ds->ds_num != values_num) {
     ERROR("ds[%s]->ds_num = %zu, "
           "but uc_get_rate_by_name returned %zu values.",
           ds->type, ds->ds_num, values_num);
-    print_to_socket(fh, "-1 Error reading value from cache.\n");
+    cmd_error(CMD_ERROR, &err, "Error reading value from cache.");
     sfree(values);
-    sfree(identifier_copy);
-    return (-1);
+    cmd_destroy(&cmd);
+    return (CMD_ERROR);
   }
 
   print_to_socket(fh, "%zu Value%s found\n", values_num,
@@ -145,9 +153,16 @@ int handle_getval(FILE *fh, char *buffer) {
   }
 
   sfree(values);
-  sfree(identifier_copy);
+  cmd_destroy(&cmd);
+
+  return (CMD_OK);
+} /* cmd_status_t cmd_handle_getval */
+
+void cmd_destroy_getval(cmd_getval_t *getval) {
+  if (getval == NULL)
+    return;
 
-  return (0);
-} /* int handle_getval */
+  sfree(getval->raw_identifier);
+} /* void cmd_destroy_getval */
 
 /* vim: set sw=2 sts=2 ts=8 : */
index 011c63e..8d488b3 100644 (file)
 
 #include <stdio.h>
 
-int handle_getval(FILE *fh, char *buffer);
+#include "utils_cmds.h"
+
+cmd_status_t cmd_parse_getval(size_t argc, char **argv,
+                              cmd_getval_t *ret_getval,
+                              const cmd_options_t *opts,
+                              cmd_error_handler_t *err);
+
+cmd_status_t cmd_handle_getval(FILE *fh, char *buffer);
+
+void cmd_destroy_getval(cmd_getval_t *getval);
 
 #endif /* UTILS_CMD_GETVAL_H */
 
index 638f7fa..f863889 100644 (file)
 #include "utils_cmd_listval.h"
 #include "utils_parse_option.h"
 
+cmd_status_t cmd_parse_listval(size_t argc, char **argv,
+                               cmd_listval_t *ret_listval
+                               __attribute__((unused)),
+                               const cmd_options_t *opts
+                               __attribute__((unused)),
+                               cmd_error_handler_t *err) {
+  if (argc != 0) {
+    cmd_error(CMD_PARSE_ERROR, err, "Garbage after end of command: `%s'.",
+              argv[0]);
+    return (CMD_PARSE_ERROR);
+  }
+
+  return (CMD_OK);
+} /* cmd_status_t cmd_parse_listval */
+
 #define free_everything_and_return(status)                                     \
   do {                                                                         \
     for (size_t j = 0; j < number; j++) {                                      \
       char errbuf[1024];                                                       \
       WARNING("handle_listval: failed to write to socket #%i: %s", fileno(fh), \
               sstrerror(errno, errbuf, sizeof(errbuf)));                       \
-      free_everything_and_return(-1);                                          \
+      free_everything_and_return(CMD_ERROR);                                   \
     }                                                                          \
     fflush(fh);                                                                \
   } while (0)
 
-int handle_listval(FILE *fh, char *buffer) {
-  char *command;
+cmd_status_t cmd_handle_listval(FILE *fh, char *buffer) {
+  cmd_error_handler_t err = {cmd_error_fh, fh};
+  cmd_status_t status;
+  cmd_t cmd;
+
   char **names = NULL;
   cdtime_t *times = NULL;
   size_t number = 0;
-  int status;
 
   DEBUG("utils_cmd_listval: handle_listval (fh = %p, buffer = %s);", (void *)fh,
         buffer);
 
-  command = NULL;
-  status = parse_string(&buffer, &command);
-  if (status != 0) {
-    print_to_socket(fh, "-1 Cannot parse command.\n");
-    free_everything_and_return(-1);
-  }
-  assert(command != NULL);
-
-  if (strcasecmp("LISTVAL", command) != 0) {
-    print_to_socket(fh, "-1 Unexpected command: `%s'.\n", command);
-    free_everything_and_return(-1);
-  }
-
-  if (*buffer != 0) {
-    print_to_socket(fh, "-1 Garbage after end of command: %s\n", buffer);
-    free_everything_and_return(-1);
+  if ((status = cmd_parse(buffer, &cmd, NULL, &err)) != CMD_OK)
+    return (status);
+  if (cmd.type != CMD_LISTVAL) {
+    cmd_error(CMD_UNKNOWN_COMMAND, &err, "Unexpected command: `%s'.",
+              CMD_TO_STRING(cmd.type));
+    free_everything_and_return(CMD_UNKNOWN_COMMAND);
   }
 
   status = uc_get_names(&names, &times, &number);
   if (status != 0) {
     DEBUG("command listval: uc_get_names failed with status %i", status);
-    print_to_socket(fh, "-1 uc_get_names failed.\n");
-    free_everything_and_return(-1);
+    cmd_error(CMD_ERROR, &err, "uc_get_names failed.");
+    free_everything_and_return(CMD_ERROR);
   }
 
   print_to_socket(fh, "%i Value%s found\n", (int)number,
@@ -95,7 +102,11 @@ int handle_listval(FILE *fh, char *buffer) {
   for (size_t i = 0; i < number; i++)
     print_to_socket(fh, "%.3f %s\n", CDTIME_T_TO_DOUBLE(times[i]), names[i]);
 
-  free_everything_and_return(0);
-} /* int handle_listval */
+  free_everything_and_return(CMD_OK);
+} /* cmd_status_t cmd_handle_listval */
+
+void cmd_destroy_listval(cmd_listval_t *listval __attribute__((unused))) {
+  /* nothing to do */
+} /* void cmd_destroy_listval */
 
 /* vim: set sw=2 sts=2 ts=8 : */
index f4250fa..8a4e8c8 100644 (file)
 
 #include <stdio.h>
 
-int handle_listval(FILE *fh, char *buffer);
+#include "utils_cmds.h"
+
+cmd_status_t cmd_parse_listval(size_t argc, char **argv,
+                               cmd_listval_t *ret_listval,
+                               const cmd_options_t *opts,
+                               cmd_error_handler_t *err);
+
+cmd_status_t cmd_handle_listval(FILE *fh, char *buffer);
+
+void cmd_destroy_listval(cmd_listval_t *listval);
 
 #endif /* UTILS_CMD_LISTVAL_H */
 
index 66b502a..f1fe6d7 100644 (file)
@@ -1,6 +1,7 @@
 /**
  * collectd - src/utils_cmd_putval.c
  * Copyright (C) 2007-2009  Florian octo Forster
+ * Copyright (C) 2016       Sebastian tokkee Harl
  *
  * Permission is hereby granted, free of charge, to any person obtaining a
  * copy of this software and associated documentation files (the "Software"),
  *
  * Authors:
  *   Florian octo Forster <octo at collectd.org>
+ *   Sebastian tokkee Harl <sh at tokkee.org>
  **/
 
 #include "collectd.h"
 
 #include "common.h"
-#include "plugin.h"
-
 #include "utils_cmd_putval.h"
-#include "utils_parse_option.h"
-
-#define print_to_socket(fh, ...)                                               \
-  do {                                                                         \
-    if (fprintf(fh, __VA_ARGS__) < 0) {                                        \
-      char errbuf[1024];                                                       \
-      WARNING("handle_putval: failed to write to socket #%i: %s", fileno(fh),  \
-              sstrerror(errno, errbuf, sizeof(errbuf)));                       \
-      sfree(vl.values);                                                        \
-      return -1;                                                               \
-    }                                                                          \
-    fflush(fh);                                                                \
-  } while (0)
+
+/*
+ * private helper functions
+ */
 
 static int set_option(value_list_t *vl, const char *key, const char *value) {
   if ((vl == NULL) || (key == NULL) || (value == NULL))
@@ -62,10 +53,18 @@ static int set_option(value_list_t *vl, const char *key, const char *value) {
     return (1);
 
   return (0);
-} /* int parse_option */
+} /* int set_option */
+
+/*
+ * public API
+ */
+
+cmd_status_t cmd_parse_putval(size_t argc, char **argv,
+                              cmd_putval_t *ret_putval,
+                              const cmd_options_t *opts,
+                              cmd_error_handler_t *err) {
+  cmd_status_t result;
 
-int handle_putval(FILE *fh, char *buffer) {
-  char *command;
   char *identifier;
   char *hostname;
   char *plugin;
@@ -73,49 +72,38 @@ int handle_putval(FILE *fh, char *buffer) {
   char *type;
   char *type_instance;
   int status;
-  int values_submitted;
 
   char *identifier_copy;
 
   const data_set_t *ds;
   value_list_t vl = VALUE_LIST_INIT;
-  vl.values = NULL;
-
-  DEBUG("utils_cmd_putval: handle_putval (fh = %p, buffer = %s);", (void *)fh,
-        buffer);
 
-  command = NULL;
-  status = parse_string(&buffer, &command);
-  if (status != 0) {
-    print_to_socket(fh, "-1 Cannot parse command.\n");
-    return (-1);
+  if ((ret_putval == NULL) || (opts == NULL)) {
+    errno = EINVAL;
+    cmd_error(CMD_ERROR, err, "Invalid arguments to cmd_parse_putval.");
+    return (CMD_ERROR);
   }
-  assert(command != NULL);
 
-  if (strcasecmp("PUTVAL", command) != 0) {
-    print_to_socket(fh, "-1 Unexpected command: `%s'.\n", command);
-    return (-1);
+  if (argc < 2) {
+    cmd_error(CMD_PARSE_ERROR, err, "Missing identifier and/or value-list.");
+    return (CMD_PARSE_ERROR);
   }
 
-  identifier = NULL;
-  status = parse_string(&buffer, &identifier);
-  if (status != 0) {
-    print_to_socket(fh, "-1 Cannot parse identifier.\n");
-    return (-1);
-  }
-  assert(identifier != NULL);
+  identifier = argv[0];
 
-  /* parse_identifier() modifies its first argument,
-   * returning pointers into it */
+  /* parse_identifier() modifies its first argument, returning pointers into
+   * it; retain the old value for later. */
   identifier_copy = sstrdup(identifier);
 
-  status = parse_identifier(identifier_copy, &hostname, &plugin,
-                            &plugin_instance, &type, &type_instance);
+  status =
+      parse_identifier(identifier, &hostname, &plugin, &plugin_instance, &type,
+                       &type_instance, opts->identifier_default_host);
   if (status != 0) {
-    DEBUG("handle_putval: Cannot parse identifier `%s'.", identifier);
-    print_to_socket(fh, "-1 Cannot parse identifier `%s'.\n", identifier);
+    DEBUG("cmd_handle_putval: Cannot parse identifier `%s'.", identifier_copy);
+    cmd_error(CMD_PARSE_ERROR, err, "Cannot parse identifier `%s'.",
+              identifier_copy);
     sfree(identifier_copy);
-    return (-1);
+    return (CMD_PARSE_ERROR);
   }
 
   if ((strlen(hostname) >= sizeof(vl.host)) ||
@@ -124,9 +112,9 @@ int handle_putval(FILE *fh, char *buffer) {
        (strlen(plugin_instance) >= sizeof(vl.plugin_instance))) ||
       ((type_instance != NULL) &&
        (strlen(type_instance) >= sizeof(vl.type_instance)))) {
-    print_to_socket(fh, "-1 Identifier too long.\n");
+    cmd_error(CMD_PARSE_ERROR, err, "Identifier too long.");
     sfree(identifier_copy);
-    return (-1);
+    return (CMD_PARSE_ERROR);
   }
 
   sstrncpy(vl.host, hostname, sizeof(vl.host));
@@ -139,78 +127,140 @@ int handle_putval(FILE *fh, char *buffer) {
 
   ds = plugin_get_ds(type);
   if (ds == NULL) {
-    print_to_socket(fh, "-1 Type `%s' isn't defined.\n", type);
+    cmd_error(CMD_PARSE_ERROR, err, "1 Type `%s' isn't defined.", type);
     sfree(identifier_copy);
-    return (-1);
+    return (CMD_PARSE_ERROR);
   }
 
-  /* Free identifier_copy */
   hostname = NULL;
   plugin = NULL;
   plugin_instance = NULL;
   type = NULL;
   type_instance = NULL;
-  sfree(identifier_copy);
 
-  vl.values_len = ds->ds_num;
-  vl.values = malloc(vl.values_len * sizeof(*vl.values));
-  if (vl.values == NULL) {
-    print_to_socket(fh, "-1 malloc failed.\n");
-    return (-1);
+  ret_putval->raw_identifier = identifier_copy;
+  if (ret_putval->raw_identifier == NULL) {
+    cmd_error(CMD_ERROR, err, "malloc failed.");
+    cmd_destroy_putval(ret_putval);
+    sfree(vl.values);
+    return (CMD_ERROR);
   }
 
-  /* All the remaining fields are part of the optionlist. */
-  values_submitted = 0;
-  while (*buffer != 0) {
-    char *string = NULL;
+  /* All the remaining fields are part of the option list. */
+  result = CMD_OK;
+  for (size_t i = 1; i < argc; ++i) {
+    value_list_t *tmp;
+
+    char *key = NULL;
     char *value = NULL;
 
-    status = parse_option(&buffer, &string, &value);
-    if (status < 0) {
-      /* parse_option failed, buffer has been modified.
-       * => we need to abort */
-      print_to_socket(fh, "-1 Misformatted option.\n");
-      sfree(vl.values);
-      return (-1);
-    } else if (status == 0) {
-      assert(string != NULL);
+    status = cmd_parse_option(argv[i], &key, &value, err);
+    if (status == CMD_OK) {
+      assert(key != NULL);
       assert(value != NULL);
-      set_option(&vl, string, value);
+      set_option(&vl, key, value);
       continue;
+    } else if (status != CMD_NO_OPTION) {
+      /* parse_option failed, buffer has been modified.
+       * => we need to abort */
+      result = status;
+      break;
+    }
+    /* else: cmd_parse_option did not find an option; treat this as a
+     * value list. */
+
+    vl.values_len = ds->ds_num;
+    vl.values = calloc(vl.values_len, sizeof(*vl.values));
+    if (vl.values == NULL) {
+      cmd_error(CMD_ERROR, err, "malloc failed.");
+      result = CMD_ERROR;
+      break;
     }
-    /* else: parse_option but buffer has not been modified. This is
-     * the default if no `=' is found.. */
 
-    status = parse_string(&buffer, &string);
+    status = parse_values(argv[i], &vl, ds);
     if (status != 0) {
-      print_to_socket(fh, "-1 Misformatted value.\n");
+      cmd_error(CMD_PARSE_ERROR, err, "Parsing the values string failed.");
+      result = CMD_PARSE_ERROR;
+      vl.values_len = 0;
       sfree(vl.values);
-      return (-1);
+      break;
     }
-    assert(string != NULL);
 
-    status = parse_values(string, &vl, ds);
-    if (status != 0) {
-      print_to_socket(fh, "-1 Parsing the values string failed.\n");
+    tmp = realloc(ret_putval->vl,
+                  (ret_putval->vl_num + 1) * sizeof(*ret_putval->vl));
+    if (tmp == NULL) {
+      cmd_error(CMD_ERROR, err, "realloc failed.");
+      cmd_destroy_putval(ret_putval);
+      result = CMD_ERROR;
+      vl.values_len = 0;
       sfree(vl.values);
-      return (-1);
+      break;
     }
 
-    plugin_dispatch_values(&vl);
-    values_submitted++;
+    ret_putval->vl = tmp;
+    ret_putval->vl_num++;
+    memcpy(&ret_putval->vl[ret_putval->vl_num - 1], &vl, sizeof(vl));
+
+    /* pointer is now owned by ret_putval->vl[] */
+    vl.values_len = 0;
+    vl.values = NULL;
   } /* while (*buffer != 0) */
   /* Done parsing the options. */
 
+  if (result != CMD_OK)
+    cmd_destroy_putval(ret_putval);
+
+  return (result);
+} /* cmd_status_t cmd_parse_putval */
+
+void cmd_destroy_putval(cmd_putval_t *putval) {
+  if (putval == NULL)
+    return;
+
+  sfree(putval->raw_identifier);
+
+  for (size_t i = 0; i < putval->vl_num; ++i) {
+    sfree(putval->vl[i].values);
+    meta_data_destroy(putval->vl[i].meta);
+    putval->vl[i].meta = NULL;
+  }
+  sfree(putval->vl);
+  putval->vl = NULL;
+  putval->vl_num = 0;
+} /* void cmd_destroy_putval */
+
+cmd_status_t cmd_handle_putval(FILE *fh, char *buffer) {
+  cmd_error_handler_t err = {cmd_error_fh, fh};
+  cmd_t cmd;
+
+  int status;
+
+  DEBUG("utils_cmd_putval: cmd_handle_putval (fh = %p, buffer = %s);",
+        (void *)fh, buffer);
+
+  if ((status = cmd_parse(buffer, &cmd, NULL, &err)) != CMD_OK)
+    return (status);
+  if (cmd.type != CMD_PUTVAL) {
+    cmd_error(CMD_UNKNOWN_COMMAND, &err, "Unexpected command: `%s'.",
+              CMD_TO_STRING(cmd.type));
+    cmd_destroy(&cmd);
+    return (CMD_UNKNOWN_COMMAND);
+  }
+
+  for (size_t i = 0; i < cmd.cmd.putval.vl_num; ++i)
+    plugin_dispatch_values(&cmd.cmd.putval.vl[i]);
+
   if (fh != stdout)
-    print_to_socket(fh, "0 Success: %i %s been dispatched.\n", values_submitted,
-                    (values_submitted == 1) ? "value has" : "values have");
+    cmd_error(CMD_OK, &err, "Success: %i %s been dispatched.",
+              (int)cmd.cmd.putval.vl_num,
+              (cmd.cmd.putval.vl_num == 1) ? "value has" : "values have");
 
-  sfree(vl.values);
-  return (0);
-} /* int handle_putval */
+  cmd_destroy(&cmd);
+  return (CMD_OK);
+} /* int cmd_handle_putval */
 
-int create_putval(char *ret, size_t ret_len, /* {{{ */
-                  const data_set_t *ds, const value_list_t *vl) {
+int cmd_create_putval(char *ret, size_t ret_len, /* {{{ */
+                      const data_set_t *ds, const value_list_t *vl) {
   char buffer_ident[6 * DATA_MAX_NAME_LEN];
   char buffer_values[1024];
   int status;
@@ -232,4 +282,4 @@ int create_putval(char *ret, size_t ret_len, /* {{{ */
             buffer_values);
 
   return (0);
-} /* }}} int create_putval */
+} /* }}} int cmd_create_putval */
index 9f60ca0..bc6e193 100644 (file)
 #ifndef UTILS_CMD_PUTVAL_H
 #define UTILS_CMD_PUTVAL_H 1
 
+#include "plugin.h"
+#include "utils_cmds.h"
+
 #include <stdio.h>
 
-#include "plugin.h"
+cmd_status_t cmd_parse_putval(size_t argc, char **argv,
+                              cmd_putval_t *ret_putval,
+                              const cmd_options_t *opts,
+                              cmd_error_handler_t *err);
+
+cmd_status_t cmd_handle_putval(FILE *fh, char *buffer);
 
-int handle_putval(FILE *fh, char *buffer);
+void cmd_destroy_putval(cmd_putval_t *putval);
 
-int create_putval(char *ret, size_t ret_len, const data_set_t *ds,
-                  const value_list_t *vl);
+int cmd_create_putval(char *ret, size_t ret_len, const data_set_t *ds,
+                      const value_list_t *vl);
 
 #endif /* UTILS_CMD_PUTVAL_H */
diff --git a/src/utils_cmds.c b/src/utils_cmds.c
new file mode 100644 (file)
index 0000000..3ea7c9d
--- /dev/null
@@ -0,0 +1,313 @@
+/**
+ * collectd - src/utils_cmds.c
+ * Copyright (C) 2008       Florian Forster
+ * Copyright (C) 2016       Sebastian 'tokkee' Harl
+ *
+ * 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 <octo at collectd.org>
+ *   Sebastian 'tokkee' Harl <sh at tokkee.org>
+ **/
+
+#include "daemon/common.h"
+#include "utils_cmd_flush.h"
+#include "utils_cmd_getval.h"
+#include "utils_cmd_listval.h"
+#include "utils_cmd_putval.h"
+#include "utils_cmds.h"
+#include "utils_parse_option.h"
+
+#include <stdbool.h>
+#include <string.h>
+
+static cmd_options_t default_options = {
+    /* identifier_default_host = */ NULL,
+};
+
+/*
+ * private helper functions
+ */
+
+static cmd_status_t cmd_split(char *buffer, size_t *ret_len, char ***ret_fields,
+                              cmd_error_handler_t *err) {
+  char *field;
+  bool in_field, in_quotes;
+
+  size_t estimate, len;
+  char **fields;
+
+  estimate = 0;
+  in_field = false;
+  for (char *string = buffer; *string != '\0'; ++string) {
+    /* Make a quick worst-case estimate of the number of fields by
+     * counting spaces and ignoring quotation marks. */
+    if (!isspace((int)*string)) {
+      if (!in_field) {
+        estimate++;
+        in_field = true;
+      }
+    } else {
+      in_field = false;
+    }
+  }
+
+  /* fields will be NULL-terminated */
+  fields = malloc((estimate + 1) * sizeof(*fields));
+  if (fields == NULL) {
+    cmd_error(CMD_ERROR, err, "malloc failed.");
+    return (CMD_ERROR);
+  }
+
+#define END_FIELD()                                                            \
+  do {                                                                         \
+    *field = '\0';                                                             \
+    field = NULL;                                                              \
+    in_field = false;                                                          \
+  } while (0)
+#define NEW_FIELD()                                                            \
+  do {                                                                         \
+    field = string;                                                            \
+    in_field = true;                                                           \
+    assert(len < estimate);                                                    \
+    fields[len] = field;                                                       \
+    field++;                                                                   \
+    len++;                                                                     \
+  } while (0)
+
+  len = 0;
+  field = NULL;
+  in_field = false;
+  in_quotes = false;
+  for (char *string = buffer; *string != '\0'; string++) {
+    if (isspace((int)string[0])) {
+      if (!in_quotes) {
+        if (in_field)
+          END_FIELD();
+
+        /* skip space */
+        continue;
+      }
+    } else if (string[0] == '"') {
+      /* Note: Two consecutive quoted fields not separated by space are
+       * treated as different fields. This is the collectd 5.x behavior
+       * around splitting fields. */
+
+      if (in_quotes) {
+        /* end of quoted field */
+        if (!in_field) /* empty quoted string */
+          NEW_FIELD();
+        END_FIELD();
+        in_quotes = false;
+        continue;
+      }
+
+      in_quotes = true;
+      /* if (! in_field): add new field on next iteration
+       * else: quoted string following an unquoted string (one field)
+       * in either case: skip quotation mark */
+      continue;
+    } else if ((string[0] == '\\') && in_quotes) {
+      /* Outside of quotes, a backslash is a regular character (mostly
+       * for backward compatibility). */
+
+      if (string[1] == '\0') {
+        free(fields);
+        cmd_error(CMD_PARSE_ERROR, err, "Backslash at end of string.");
+        return (CMD_PARSE_ERROR);
+      }
+
+      /* un-escape the next character; skip backslash */
+      string++;
+    }
+
+    if (!in_field)
+      NEW_FIELD();
+    else {
+      *field = string[0];
+      field++;
+    }
+  }
+
+  if (in_quotes) {
+    free(fields);
+    cmd_error(CMD_PARSE_ERROR, err, "Unterminated quoted string.");
+    return (CMD_PARSE_ERROR);
+  }
+
+#undef NEW_FIELD
+#undef END_FIELD
+
+  fields[len] = NULL;
+  if (ret_len != NULL)
+    *ret_len = len;
+  if (ret_fields != NULL)
+    *ret_fields = fields;
+  else
+    free(fields);
+  return (CMD_OK);
+} /* int cmd_split */
+
+/*
+ * public API
+ */
+
+void cmd_error(cmd_status_t status, cmd_error_handler_t *err,
+               const char *format, ...) {
+  va_list ap;
+
+  if ((err == NULL) || (err->cb == NULL))
+    return;
+
+  va_start(ap, format);
+  err->cb(err->ud, status, format, ap);
+  va_end(ap);
+} /* void cmd_error */
+
+cmd_status_t cmd_parsev(size_t argc, char **argv, cmd_t *ret_cmd,
+                        const cmd_options_t *opts, cmd_error_handler_t *err) {
+  char *command = NULL;
+  cmd_status_t status;
+
+  if ((argc < 1) || (argv == NULL) || (ret_cmd == NULL)) {
+    errno = EINVAL;
+    cmd_error(CMD_ERROR, err, "Missing command.");
+    return CMD_ERROR;
+  }
+
+  if (opts == NULL)
+    opts = &default_options;
+
+  memset(ret_cmd, 0, sizeof(*ret_cmd));
+  command = argv[0];
+  if (strcasecmp("FLUSH", command) == 0) {
+    ret_cmd->type = CMD_FLUSH;
+    status =
+        cmd_parse_flush(argc - 1, argv + 1, &ret_cmd->cmd.flush, opts, err);
+  } else if (strcasecmp("GETVAL", command) == 0) {
+    ret_cmd->type = CMD_GETVAL;
+    status =
+        cmd_parse_getval(argc - 1, argv + 1, &ret_cmd->cmd.getval, opts, err);
+  } else if (strcasecmp("LISTVAL", command) == 0) {
+    ret_cmd->type = CMD_LISTVAL;
+    status =
+        cmd_parse_listval(argc - 1, argv + 1, &ret_cmd->cmd.listval, opts, err);
+  } else if (strcasecmp("PUTVAL", command) == 0) {
+    ret_cmd->type = CMD_PUTVAL;
+    status =
+        cmd_parse_putval(argc - 1, argv + 1, &ret_cmd->cmd.putval, opts, err);
+  } else {
+    ret_cmd->type = CMD_UNKNOWN;
+    cmd_error(CMD_UNKNOWN_COMMAND, err, "Unknown command `%s'.", command);
+    return (CMD_UNKNOWN_COMMAND);
+  }
+
+  if (status != CMD_OK)
+    ret_cmd->type = CMD_UNKNOWN;
+  return (status);
+} /* cmd_status_t cmd_parsev */
+
+cmd_status_t cmd_parse(char *buffer, cmd_t *ret_cmd, const cmd_options_t *opts,
+                       cmd_error_handler_t *err) {
+  char **fields = NULL;
+  size_t fields_num = 0;
+  cmd_status_t status;
+
+  if ((status = cmd_split(buffer, &fields_num, &fields, err)) != CMD_OK)
+    return status;
+
+  status = cmd_parsev(fields_num, fields, ret_cmd, opts, err);
+  free(fields);
+  return (status);
+} /* cmd_status_t cmd_parse */
+
+void cmd_destroy(cmd_t *cmd) {
+  if (cmd == NULL)
+    return;
+
+  switch (cmd->type) {
+  case CMD_UNKNOWN:
+    /* nothing to do */
+    break;
+  case CMD_FLUSH:
+    cmd_destroy_flush(&cmd->cmd.flush);
+    break;
+  case CMD_GETVAL:
+    cmd_destroy_getval(&cmd->cmd.getval);
+    break;
+  case CMD_LISTVAL:
+    cmd_destroy_listval(&cmd->cmd.listval);
+    break;
+  case CMD_PUTVAL:
+    cmd_destroy_putval(&cmd->cmd.putval);
+    break;
+  }
+} /* void cmd_destroy */
+
+cmd_status_t cmd_parse_option(char *field, char **ret_key, char **ret_value,
+                              cmd_error_handler_t *err) {
+  char *key, *value;
+
+  if (field == NULL) {
+    errno = EINVAL;
+    cmd_error(CMD_ERROR, err, "Invalid argument to cmd_parse_option.");
+    return (CMD_ERROR);
+  }
+  key = value = field;
+
+  /* Look for the equal sign. */
+  while (isalnum((int)value[0]) || (value[0] == '_') || (value[0] == ':'))
+    value++;
+  if ((value[0] != '=') || (value == key)) {
+    /* Whether this is a fatal error is up to the caller. */
+    return (CMD_NO_OPTION);
+  }
+  *value = '\0';
+  value++;
+
+  if (ret_key != NULL)
+    *ret_key = key;
+  if (ret_value != NULL)
+    *ret_value = value;
+
+  return (CMD_OK);
+} /* cmd_status_t cmd_parse_option */
+
+void cmd_error_fh(void *ud, cmd_status_t status, const char *format,
+                  va_list ap) {
+  FILE *fh = ud;
+  int code = -1;
+  char buf[1024];
+
+  if (status == CMD_OK)
+    code = 0;
+
+  vsnprintf(buf, sizeof(buf), format, ap);
+  buf[sizeof(buf) - 1] = '\0';
+  if (fprintf(fh, "%i %s\n", code, buf) < 0) {
+    char errbuf[1024];
+    WARNING("utils_cmds: failed to write to file-handle #%i: %s", fileno(fh),
+            sstrerror(errno, errbuf, sizeof(errbuf)));
+    return;
+  }
+
+  fflush(fh);
+} /* void cmd_error_fh */
+
+/* vim: set sw=4 ts=4 tw=78 noexpandtab : */
diff --git a/src/utils_cmds.h b/src/utils_cmds.h
new file mode 100644 (file)
index 0000000..26d5338
--- /dev/null
@@ -0,0 +1,213 @@
+/**
+ * collectd - src/utils_cmds.h
+ * Copyright (C) 2016 Sebastian 'tokkee' Harl
+ *
+ * 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:
+ *   Sebastian 'tokkee' Harl <sh at tokkee.org>
+ **/
+
+#ifndef UTILS_CMDS_H
+#define UTILS_CMDS_H 1
+
+#include "plugin.h"
+
+#include <stdarg.h>
+
+typedef enum {
+  CMD_UNKNOWN = 0,
+  CMD_FLUSH = 1,
+  CMD_GETVAL = 2,
+  CMD_LISTVAL = 3,
+  CMD_PUTVAL = 4,
+} cmd_type_t;
+#define CMD_TO_STRING(type)                                                    \
+  ((type) == CMD_FLUSH) ? "FLUSH" : ((type) == CMD_GETVAL)                     \
+                                        ? "GETVAL"                             \
+                                        : ((type) == CMD_LISTVAL)              \
+                                              ? "LISTVAL"                      \
+                                              : ((type) == CMD_PUTVAL)         \
+                                                    ? "PUTVAL"                 \
+                                                    : "UNKNOWN"
+
+typedef struct {
+  double timeout;
+
+  char **plugins;
+  size_t plugins_num;
+  identifier_t *identifiers;
+  size_t identifiers_num;
+} cmd_flush_t;
+
+typedef struct {
+  char *raw_identifier;
+  identifier_t identifier;
+} cmd_getval_t;
+
+typedef struct {
+} cmd_listval_t;
+
+typedef struct {
+  /* The raw identifier as provided by the user. */
+  char *raw_identifier;
+
+  /* An array of the fully parsed identifier and all value lists, and their
+   * options as provided by the user. */
+  value_list_t *vl;
+  size_t vl_num;
+} cmd_putval_t;
+
+/*
+ * NAME
+ *   cmd_t
+ *
+ * DESCRIPTION
+ *   The representation of a fully parsed command.
+ */
+typedef struct {
+  cmd_type_t type;
+  union {
+    cmd_flush_t flush;
+    cmd_getval_t getval;
+    cmd_listval_t listval;
+    cmd_putval_t putval;
+  } cmd;
+} cmd_t;
+
+/*
+ * NAME
+ *   cmd_options_t
+ *
+ * DESCRIPTIONS
+ *   Optional settings for tuning the parser behavior.
+ */
+typedef struct {
+  /* identifier_default_host: If non-NULL, the hostname is optional and will
+   * default to the specified value. */
+  char *identifier_default_host;
+} cmd_options_t;
+
+/*
+ * NAME
+ *   cmd_status_t
+ *
+ * DESCRIPTION
+ *   Status codes describing the parse result.
+ */
+typedef enum {
+  CMD_OK = 0,
+  CMD_ERROR = -1,
+  CMD_PARSE_ERROR = -2,
+  CMD_UNKNOWN_COMMAND = -3,
+
+  /* Not necessarily fatal errors. */
+  CMD_NO_OPTION = 1,
+} cmd_status_t;
+
+/*
+ * NAME
+ *   cmd_error_handler_t
+ *
+ * DESCRIPTION
+ *   An error handler describes a callback to be invoked when the parser
+ *   encounters an error. The user data pointer will be passed to the callback
+ *   as the first argument.
+ */
+typedef struct {
+  void (*cb)(void *, cmd_status_t, const char *, va_list);
+  void *ud;
+} cmd_error_handler_t;
+
+/*
+ * NAME:
+ *   cmd_error
+ *
+ * DESCRIPTION
+ *   Reports an error via the specified error handler (if set).
+ */
+void cmd_error(cmd_status_t status, cmd_error_handler_t *err,
+               const char *format, ...);
+
+/*
+ * NAME
+ *   cmd_parse
+ *
+ * DESCRIPTION
+ *   Parse a command string and populate a command object.
+ *
+ * PARAMETERS
+ *   `buffer'  The command string to be parsed.
+ *   `ret_cmd' The parse result will be stored at this location.
+ *   `opts'    Parser options. If NULL, defaults will be used.
+ *   `err'     An optional error handler to invoke on error.
+ *
+ * RETURN VALUE
+ *   CMD_OK on success or the respective error code otherwise.
+ */
+cmd_status_t cmd_parse(char *buffer, cmd_t *ret_cmd, const cmd_options_t *opts,
+                       cmd_error_handler_t *err);
+
+cmd_status_t cmd_parsev(size_t argc, char **argv, cmd_t *ret_cmd,
+                        const cmd_options_t *opts, cmd_error_handler_t *err);
+
+void cmd_destroy(cmd_t *cmd);
+
+/*
+ * NAME
+ *   cmd_parse_option
+ *
+ * DESCRIPTION
+ *   Parses a command option which must be of the form:
+ *     name=value with \ and spaces
+ *
+ * PARAMETERS
+ *   `field'     The parsed input field with any quotes removed and special
+ *               characters unescaped.
+ *   `ret_key'   The parsed key will be stored at this location.
+ *   `ret_value' The parsed value will be stored at this location.
+ *
+ * RETURN VALUE
+ *   CMD_OK on success or an error code otherwise.
+ *   CMD_NO_OPTION if `field' does not represent an option at all (missing
+ *   equal sign).
+ */
+cmd_status_t cmd_parse_option(char *field, char **ret_key, char **ret_value,
+                              cmd_error_handler_t *err);
+
+/*
+ * NAME
+ *   cmd_error_fh
+ *
+ * DESCRIPTION
+ *   An error callback writing the message to an open file handle using the
+ *   format expected by the unixsock or exec plugins.
+ *
+ * PARAMETERS
+ *   `ud'     Error handler user-data pointer. This must be an open
+ *            file-handle (FILE *).
+ *   `status' The error status code.
+ *   `format' Printf-style format string.
+ *   `ap'     Variable argument list providing the arguments for the format
+ *            string.
+ */
+void cmd_error_fh(void *ud, cmd_status_t status, const char *format,
+                  va_list ap);
+
+#endif /* UTILS_CMDS_H */
diff --git a/src/utils_cmds_test.c b/src/utils_cmds_test.c
new file mode 100644 (file)
index 0000000..b540146
--- /dev/null
@@ -0,0 +1,224 @@
+/**
+ * collectd - src/tests/utils_cmds_test.c
+ * Copyright (C) 2016       Sebastian 'tokkee' Harl
+ *
+ * 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:
+ *   Sebastian 'tokkee' Harl <sh at tokkee.org>
+ **/
+
+#include "common.h"
+#include "testing.h"
+#include "utils_cmds.h"
+
+static void error_cb(void *ud, cmd_status_t status, const char *format,
+                     va_list ap) {
+  if (status == CMD_OK)
+    return;
+
+  printf("ERROR[%d]: ", status);
+  vprintf(format, ap);
+  printf("\n");
+  fflush(stdout);
+} /* void error_cb */
+
+static cmd_options_t default_host_opts = {
+    /* identifier_default_host = */ "dummy-host",
+};
+
+static struct {
+  char *input;
+  cmd_options_t *opts;
+  cmd_status_t expected_status;
+  cmd_type_t expected_type;
+} parse_data[] = {
+    /* Valid FLUSH commands. */
+    {
+        "FLUSH", NULL, CMD_OK, CMD_FLUSH,
+    },
+    {
+        "FLUSH identifier=myhost/magic/MAGIC", NULL, CMD_OK, CMD_FLUSH,
+    },
+    {
+        "FLUSH identifier=magic/MAGIC", &default_host_opts, CMD_OK, CMD_FLUSH,
+    },
+    {
+        "FLUSH timeout=123 plugin=\"A\"", NULL, CMD_OK, CMD_FLUSH,
+    },
+    /* Invalid FLUSH commands. */
+    {
+        /* Missing hostname; no default. */
+        "FLUSH identifier=magic/MAGIC", NULL, CMD_PARSE_ERROR, CMD_UNKNOWN,
+    },
+    {
+        /* Missing 'identifier' key. */
+        "FLUSH myhost/magic/MAGIC", NULL, CMD_PARSE_ERROR, CMD_UNKNOWN,
+    },
+    {
+        /* Invalid timeout. */
+        "FLUSH timeout=A", NULL, CMD_PARSE_ERROR, CMD_UNKNOWN,
+    },
+    {
+        /* Invalid identifier. */
+        "FLUSH identifier=invalid", NULL, CMD_PARSE_ERROR, CMD_UNKNOWN,
+    },
+    {
+        /* Invalid option. */
+        "FLUSH invalid=option", NULL, CMD_PARSE_ERROR, CMD_UNKNOWN,
+    },
+
+    /* Valid GETVAL commands. */
+    {
+        "GETVAL myhost/magic/MAGIC", NULL, CMD_OK, CMD_GETVAL,
+    },
+    {
+        "GETVAL magic/MAGIC", &default_host_opts, CMD_OK, CMD_GETVAL,
+    },
+
+    /* Invalid GETVAL commands. */
+    {
+        "GETVAL magic/MAGIC", NULL, CMD_PARSE_ERROR, CMD_UNKNOWN,
+    },
+    {
+        "GETVAL", NULL, CMD_PARSE_ERROR, CMD_UNKNOWN,
+    },
+    {
+        "GETVAL invalid", NULL, CMD_PARSE_ERROR, CMD_UNKNOWN,
+    },
+
+    /* Valid LISTVAL commands. */
+    {
+        "LISTVAL", NULL, CMD_OK, CMD_LISTVAL,
+    },
+
+    /* Invalid LISTVAL commands. */
+    {
+        "LISTVAL invalid", NULL, CMD_PARSE_ERROR, CMD_UNKNOWN,
+    },
+
+    /* Valid PUTVAL commands. */
+    {
+        "PUTVAL magic/MAGIC N:42", &default_host_opts, CMD_OK, CMD_PUTVAL,
+    },
+    {
+        "PUTVAL myhost/magic/MAGIC N:42", NULL, CMD_OK, CMD_PUTVAL,
+    },
+    {
+        "PUTVAL myhost/magic/MAGIC 1234:42", NULL, CMD_OK, CMD_PUTVAL,
+    },
+    {
+        "PUTVAL myhost/magic/MAGIC 1234:42 2345:23", NULL, CMD_OK, CMD_PUTVAL,
+    },
+    {
+        "PUTVAL myhost/magic/MAGIC interval=2 1234:42", NULL, CMD_OK,
+        CMD_PUTVAL,
+    },
+    {
+        "PUTVAL myhost/magic/MAGIC interval=2 1234:42 interval=5 2345:23", NULL,
+        CMD_OK, CMD_PUTVAL,
+    },
+
+    /* Invalid PUTVAL commands. */
+    {
+        "PUTVAL magic/MAGIC N:42", NULL, CMD_PARSE_ERROR, CMD_UNKNOWN,
+    },
+    {
+        "PUTVAL", NULL, CMD_PARSE_ERROR, CMD_UNKNOWN,
+    },
+    {
+        "PUTVAL invalid N:42", NULL, CMD_PARSE_ERROR, CMD_UNKNOWN,
+    },
+    {
+        "PUTVAL myhost/magic/MAGIC A:42", NULL, CMD_PARSE_ERROR, CMD_UNKNOWN,
+    },
+    {
+        "PUTVAL myhost/magic/MAGIC 1234:A", NULL, CMD_PARSE_ERROR, CMD_UNKNOWN,
+    },
+    {
+        "PUTVAL myhost/magic/MAGIC", NULL, CMD_PARSE_ERROR, CMD_UNKNOWN,
+    },
+    {
+        "PUTVAL 1234:A", NULL, CMD_PARSE_ERROR, CMD_UNKNOWN,
+    },
+    {
+        "PUTVAL myhost/magic/UNKNOWN 1234:42", NULL, CMD_PARSE_ERROR,
+        CMD_UNKNOWN,
+    },
+    /*
+     * As of collectd 5.x, PUTVAL accepts invalid options.
+    {
+            "PUTVAL myhost/magic/MAGIC invalid=2 1234:42",
+            NULL,
+            CMD_PARSE_ERROR,
+            CMD_UNKNOWN,
+    },
+    */
+
+    /* Invalid commands. */
+    {
+        "INVALID", NULL, CMD_UNKNOWN_COMMAND, CMD_UNKNOWN,
+    },
+    {
+        "INVALID interval=2", NULL, CMD_UNKNOWN_COMMAND, CMD_UNKNOWN,
+    },
+};
+
+DEF_TEST(parse) {
+  cmd_error_handler_t err = {error_cb, NULL};
+  int test_result = 0;
+
+  for (size_t i = 0; i < STATIC_ARRAY_SIZE(parse_data); i++) {
+    char *input = strdup(parse_data[i].input);
+
+    char description[1024];
+    cmd_status_t status;
+    cmd_t cmd;
+
+    _Bool result;
+
+    memset(&cmd, 0, sizeof(cmd));
+
+    status = cmd_parse(input, &cmd, parse_data[i].opts, &err);
+    snprintf(description, sizeof(description), "cmd_parse (\"%s\", opts=%p) = "
+                                               "%d (type=%d [%s]); want %d "
+                                               "(type=%d [%s])",
+             parse_data[i].input, parse_data[i].opts, status, cmd.type,
+             CMD_TO_STRING(cmd.type), parse_data[i].expected_status,
+             parse_data[i].expected_type,
+             CMD_TO_STRING(parse_data[i].expected_type));
+    result = (status == parse_data[i].expected_status) &&
+             (cmd.type == parse_data[i].expected_type);
+    LOG(result, description);
+
+    /* Run all tests before failing. */
+    if (!result)
+      test_result = -1;
+
+    cmd_destroy(&cmd);
+    free(input);
+  }
+
+  return (test_result);
+}
+
+int main(int argc, char **argv) {
+  RUN_TEST(parse);
+  END_TEST;
+}
index 40bfff5..9c7d96f 100644 (file)
@@ -77,7 +77,7 @@ static int gr_format_values(char *ret, size_t ret_len, int ds_num,
 }
 
 static void gr_copy_escape_part(char *dst, const char *src, size_t dst_len,
-                                char escape_char) {
+                                char escape_char, _Bool preserve_separator) {
   memset(dst, 0, dst_len);
 
   if (src == NULL)
@@ -89,7 +89,8 @@ static void gr_copy_escape_part(char *dst, const char *src, size_t dst_len,
       break;
     }
 
-    if ((src[i] == '.') || isspace((int)src[i]) || iscntrl((int)src[i]))
+    if ((!preserve_separator && (src[i] == '.')) || isspace((int)src[i]) ||
+        iscntrl((int)src[i]))
       dst[i] = escape_char;
     else
       dst[i] = src[i];
@@ -115,13 +116,19 @@ static int gr_format_name(char *ret, int ret_len, value_list_t const *vl,
   if (postfix == NULL)
     postfix = "";
 
-  gr_copy_escape_part(n_host, vl->host, sizeof(n_host), escape_char);
-  gr_copy_escape_part(n_plugin, vl->plugin, sizeof(n_plugin), escape_char);
+  _Bool preserve_separator = (flags & GRAPHITE_PRESERVE_SEPARATOR) ? 1 : 0;
+
+  gr_copy_escape_part(n_host, vl->host, sizeof(n_host), escape_char,
+                      preserve_separator);
+  gr_copy_escape_part(n_plugin, vl->plugin, sizeof(n_plugin), escape_char,
+                      preserve_separator);
   gr_copy_escape_part(n_plugin_instance, vl->plugin_instance,
-                      sizeof(n_plugin_instance), escape_char);
-  gr_copy_escape_part(n_type, vl->type, sizeof(n_type), escape_char);
+                      sizeof(n_plugin_instance), escape_char,
+                      preserve_separator);
+  gr_copy_escape_part(n_type, vl->type, sizeof(n_type), escape_char,
+                      preserve_separator);
   gr_copy_escape_part(n_type_instance, vl->type_instance,
-                      sizeof(n_type_instance), escape_char);
+                      sizeof(n_type_instance), escape_char, preserve_separator);
 
   if (n_plugin_instance[0] != '\0')
     ssnprintf(tmp_plugin, sizeof(tmp_plugin), "%s%c%s", n_plugin,
@@ -130,19 +137,27 @@ static int gr_format_name(char *ret, int ret_len, value_list_t const *vl,
   else
     sstrncpy(tmp_plugin, n_plugin, sizeof(tmp_plugin));
 
-  if (n_type_instance[0] != '\0')
-    ssnprintf(tmp_type, sizeof(tmp_type), "%s%c%s", n_type,
-              (flags & GRAPHITE_SEPARATE_INSTANCES) ? '.' : '-',
-              n_type_instance);
-  else
+  if (n_type_instance[0] != '\0') {
+    if ((flags & GRAPHITE_DROP_DUPE_FIELDS) && strcmp(n_plugin, n_type) == 0)
+      sstrncpy(tmp_type, n_type_instance, sizeof(tmp_type));
+    else
+      ssnprintf(tmp_type, sizeof(tmp_type), "%s%c%s", n_type,
+                (flags & GRAPHITE_SEPARATE_INSTANCES) ? '.' : '-',
+                n_type_instance);
+  } else
     sstrncpy(tmp_type, n_type, sizeof(tmp_type));
 
   /* Assert always_append_ds -> ds_name */
   assert(!(flags & GRAPHITE_ALWAYS_APPEND_DS) || (ds_name != NULL));
-  if (ds_name != NULL)
-    ssnprintf(ret, ret_len, "%s%s%s.%s.%s.%s", prefix, n_host, postfix,
-              tmp_plugin, tmp_type, ds_name);
-  else
+  if (ds_name != NULL) {
+    if ((flags & GRAPHITE_DROP_DUPE_FIELDS) &&
+        strcmp(tmp_plugin, tmp_type) == 0)
+      ssnprintf(ret, ret_len, "%s%s%s.%s.%s", prefix, n_host, postfix,
+                tmp_plugin, ds_name);
+    else
+      ssnprintf(ret, ret_len, "%s%s%s.%s.%s.%s", prefix, n_host, postfix,
+                tmp_plugin, tmp_type, ds_name);
+  } else
     ssnprintf(ret, ret_len, "%s%s%s.%s.%s", prefix, n_host, postfix, tmp_plugin,
               tmp_type);
 
index b82da9f..de90c44 100644 (file)
@@ -29,6 +29,8 @@
 #define GRAPHITE_STORE_RATES 0x01
 #define GRAPHITE_SEPARATE_INSTANCES 0x02
 #define GRAPHITE_ALWAYS_APPEND_DS 0x04
+#define GRAPHITE_DROP_DUPE_FIELDS 0x08
+#define GRAPHITE_PRESERVE_SEPARATOR 0x10
 
 int format_graphite(char *buffer, size_t buffer_size, const data_set_t *ds,
                     const value_list_t *vl, const char *prefix,
diff --git a/src/utils_format_graphite_test.c b/src/utils_format_graphite_test.c
new file mode 100644 (file)
index 0000000..415a283
--- /dev/null
@@ -0,0 +1,164 @@
+/**
+ * collectd - src/utils_format_graphite_test.c
+ * Copyright (C) 2016       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 <octo at collectd.org>
+ */
+
+#include "collectd.h"
+
+#include "common.h" /* for STATIC_ARRAY_SIZE */
+#include "testing.h"
+#include "utils_format_graphite.h"
+
+static data_set_t ds_single = {
+    .type = "single",
+    .ds_num = 1,
+    .ds = &(data_source_t){"value", DS_TYPE_GAUGE, NAN, NAN},
+};
+
+/*
+static data_set_t ds_double = {
+    .type = "double",
+    .ds_num = 2,
+    .ds =
+        (data_source_t[]){
+            {"one", DS_TYPE_DERIVE, 0, NAN}, {"two", DS_TYPE_DERIVE, 0, NAN},
+        },
+};
+*/
+
+DEF_TEST(metric_name) {
+  struct {
+    char *plugin_instance;
+    char *type_instance;
+    char *prefix;
+    char *suffix;
+    unsigned int flags;
+    char *want_name;
+  } cases[] = {
+      {
+          .want_name = "example@com.test.single",
+      },
+      /* plugin and type instances */
+      {
+          .plugin_instance = "foo",
+          .type_instance = "bar",
+          .want_name = "example@com.test-foo.single-bar",
+      },
+      {
+          .plugin_instance = NULL,
+          .type_instance = "bar",
+          .want_name = "example@com.test.single-bar",
+      },
+      {
+          .plugin_instance = "foo",
+          .type_instance = NULL,
+          .want_name = "example@com.test-foo.single",
+      },
+      /* special chars */
+      {
+          .plugin_instance = "foo (test)",
+          .type_instance = "test: \"hello\"",
+          .want_name = "example@com.test-foo@@test@.single-test@@@hello@",
+      },
+      /* flag GRAPHITE_SEPARATE_INSTANCES */
+      {
+          .plugin_instance = "foo",
+          .type_instance = "bar",
+          .flags = GRAPHITE_SEPARATE_INSTANCES,
+          .want_name = "example@com.test.foo.single.bar",
+      },
+      /* flag GRAPHITE_ALWAYS_APPEND_DS */
+      {
+          .plugin_instance = "foo",
+          .type_instance = "bar",
+          .flags = GRAPHITE_ALWAYS_APPEND_DS,
+          .want_name = "example@com.test-foo.single-bar.value",
+      },
+      /* flag GRAPHITE_PRESERVE_SEPARATOR */
+      {
+          .plugin_instance = "f.o.o",
+          .type_instance = "b.a.r",
+          .flags = 0,
+          .want_name = "example@com.test-f@o@o.single-b@a@r",
+      },
+      {
+          .plugin_instance = "f.o.o",
+          .type_instance = "b.a.r",
+          .flags = GRAPHITE_PRESERVE_SEPARATOR,
+          .want_name = "example.com.test-f.o.o.single-b.a.r",
+      },
+      /* prefix and suffix */
+      {
+          .prefix = "foo.",
+          .suffix = ".bar",
+          .want_name = "foo.example@com.bar.test.single",
+      },
+      {
+          .prefix = NULL,
+          .suffix = ".bar",
+          .want_name = "example@com.bar.test.single",
+      },
+      {
+          .prefix = "foo.",
+          .suffix = NULL,
+          .want_name = "foo.example@com.test.single",
+      },
+  };
+
+  for (size_t i = 0; i < STATIC_ARRAY_SIZE(cases); i++) {
+    value_list_t vl = {
+        .values = &(value_t){.gauge = 42},
+        .values_len = 1,
+        .time = TIME_T_TO_CDTIME_T_STATIC(1480063672),
+        .interval = TIME_T_TO_CDTIME_T_STATIC(10),
+        .host = "example.com",
+        .plugin = "test",
+        .type = "single",
+    };
+
+    char want[1024];
+    snprintf(want, sizeof(want), "%s 42 1480063672\r\n", cases[i].want_name);
+
+    if (cases[i].plugin_instance != NULL)
+      sstrncpy(vl.plugin_instance, cases[i].plugin_instance,
+               sizeof(vl.plugin_instance));
+    if (cases[i].type_instance != NULL)
+      sstrncpy(vl.type_instance, cases[i].type_instance,
+               sizeof(vl.type_instance));
+
+    char got[1024];
+    EXPECT_EQ_INT(0, format_graphite(got, sizeof(got), &ds_single, &vl,
+                                     cases[i].prefix, cases[i].suffix, '@',
+                                     cases[i].flags));
+    EXPECT_EQ_STR(want, got);
+  }
+
+  return 0;
+}
+
+int main(void) {
+  RUN_TEST(metric_name);
+
+  END_TEST;
+}
index 651547a..f0311e8 100644 (file)
 #define LLONG_MAX 9223372036854775807LL
 #endif
 
-#ifndef HISTOGRAM_NUM_BINS
-#define HISTOGRAM_NUM_BINS 1000
-#endif
-
 #ifndef HISTOGRAM_DEFAULT_BIN_WIDTH
 /* 1048576 = 2^20 ^= 1/1024 s */
 #define HISTOGRAM_DEFAULT_BIN_WIDTH 1048576
@@ -152,7 +148,7 @@ void latency_counter_add(latency_counter_t *lc, cdtime_t latency) /* {{{ */
   if (lc->max < latency)
     lc->max = latency;
 
-  /* A latency of _exactly_ 1.0 ms should be stored in the buffer 0, so
+  /* A latency of _exactly_ 1.0 ms is stored in the buffer 0, so
    * subtract one from the cdtime_t value so that exactly 1.0 ms get sorted
    * accordingly. */
   bin = (latency - 1) / lc->bin_width;
@@ -287,4 +283,64 @@ cdtime_t latency_counter_get_percentile(latency_counter_t *lc, /* {{{ */
   return (latency_interpolated);
 } /* }}} cdtime_t latency_counter_get_percentile */
 
+double latency_counter_get_rate(const latency_counter_t *lc, /* {{{ */
+                                cdtime_t lower, cdtime_t upper,
+                                const cdtime_t now) {
+  if ((lc == NULL) || (lc->num == 0))
+    return (NAN);
+
+  if (upper && (upper < lower))
+    return (NAN);
+  if (lower == upper)
+    return (0);
+
+  /* Buckets have an exclusive lower bound and an inclusive upper bound. That
+   * means that the first bucket, index 0, represents (0-bin_width]. That means
+   * that latency==bin_width needs to result in bin=0, that's why we need to
+   * subtract one before dividing by bin_width. */
+  cdtime_t lower_bin = 0;
+  if (lower)
+    /* lower is *exclusive* => determine bucket for lower+1 */
+    lower_bin = ((lower + 1) - 1) / lc->bin_width;
+
+  /* lower is greater than the longest latency observed => rate is zero. */
+  if (lower_bin >= HISTOGRAM_NUM_BINS)
+    return (0);
+
+  cdtime_t upper_bin = HISTOGRAM_NUM_BINS - 1;
+  if (upper)
+    upper_bin = (upper - 1) / lc->bin_width;
+
+  if (upper_bin >= HISTOGRAM_NUM_BINS) {
+    upper_bin = HISTOGRAM_NUM_BINS - 1;
+    upper = 0;
+  }
+
+  double sum = 0;
+  for (size_t i = lower_bin; i <= upper_bin; i++)
+    sum += lc->histogram[i];
+
+  if (lower) {
+    /* Approximate ratio of requests in lower_bin, that fall between
+     * lower_bin_boundary and lower. This ratio is then subtracted from sum to
+     * increase accuracy. */
+    cdtime_t lower_bin_boundary = lower_bin * lc->bin_width;
+    assert(lower >= lower_bin_boundary);
+    double lower_ratio =
+        (double)(lower - lower_bin_boundary) / ((double)lc->bin_width);
+    sum -= lower_ratio * lc->histogram[lower_bin];
+  }
+
+  if (upper) {
+    /* As above: approximate ratio of requests in upper_bin, that fall between
+     * upper and upper_bin_boundary. */
+    cdtime_t upper_bin_boundary = (upper_bin + 1) * lc->bin_width;
+    assert(upper <= upper_bin_boundary);
+    double ratio = (double)(upper_bin_boundary - upper) / (double)lc->bin_width;
+    sum -= ratio * lc->histogram[upper_bin];
+  }
+
+  return sum / (CDTIME_T_TO_DOUBLE(now - lc->start_time));
+} /* }}} double latency_counter_get_rate */
+
 /* vim: set sw=2 sts=2 et fdm=marker : */
index 7c339c6..85493e1 100644 (file)
 
 #include "utils_time.h"
 
+#ifndef HISTOGRAM_NUM_BINS
+#define HISTOGRAM_NUM_BINS 1000
+#endif
+
 struct latency_counter_s;
 typedef struct latency_counter_s latency_counter_t;
 
@@ -44,4 +48,18 @@ size_t latency_counter_get_num(latency_counter_t *lc);
 cdtime_t latency_counter_get_average(latency_counter_t *lc);
 cdtime_t latency_counter_get_percentile(latency_counter_t *lc, double percent);
 
+/*
+ * NAME
+ *  latency_counter_get_rate(counter,lower,upper,now)
+ *
+ * DESCRIPTION
+ *   Calculates rate of latency values fall within requested interval.
+ *   Interval specified as (lower,upper], i.e. the lower boundary is exclusive,
+ *   the upper boundary is inclusive.
+ *   When lower is zero, then the interval is (0, upper].
+ *   When upper is zero, then the interval is (lower, infinity).
+ */
+double latency_counter_get_rate(const latency_counter_t *lc, cdtime_t lower,
+                                cdtime_t upper, const cdtime_t now);
+
 /* vim: set sw=2 sts=2 et : */
diff --git a/src/utils_latency_config.c b/src/utils_latency_config.c
new file mode 100644 (file)
index 0000000..674e275
--- /dev/null
@@ -0,0 +1,150 @@
+/**
+ * collectd - src/utils_latency_config.c
+ * Copyright (C) 2013-2016   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 <octo at collectd.org>
+ *   Pavel Rochnyack <pavel2000 at ngs.ru>
+ */
+
+#include "common.h"
+#include "utils_latency_config.h"
+#include "collectd.h"
+
+static int latency_config_add_percentile(latency_config_t *conf,
+                                         oconfig_item_t *ci,
+                                         const char *plugin) {
+  double percent;
+  int status = cf_util_get_double(ci, &percent);
+  if (status != 0)
+    return status;
+
+  if ((percent <= 0.0) || (percent >= 100)) {
+    ERROR("%s plugin: The value for \"%s\" must be between 0 and 100, "
+          "exclusively.",
+          plugin, ci->key);
+    return ERANGE;
+  }
+
+  double *tmp = realloc(conf->percentile,
+                        sizeof(*conf->percentile) * (conf->percentile_num + 1));
+  if (tmp == NULL) {
+    ERROR("%s plugin: realloc failed.", plugin);
+    return ENOMEM;
+  }
+  conf->percentile = tmp;
+  conf->percentile[conf->percentile_num] = percent;
+  conf->percentile_num++;
+
+  return 0;
+} /* int latency_config_add_percentile */
+
+static int latency_config_add_bucket(latency_config_t *conf, oconfig_item_t *ci,
+                                     const char *plugin) {
+  if ((ci->values_num != 2) || (ci->values[0].type != OCONFIG_TYPE_NUMBER) ||
+      (ci->values[1].type != OCONFIG_TYPE_NUMBER)) {
+    ERROR("%s plugin: \"%s\" requires exactly two numeric arguments.", plugin,
+          ci->key);
+    return EINVAL;
+  }
+
+  if (ci->values[1].value.number &&
+      ci->values[1].value.number <= ci->values[0].value.number) {
+    ERROR("%s plugin: MIN must be less than MAX in \"%s\".", plugin, ci->key);
+    return ERANGE;
+  }
+
+  if (ci->values[0].value.number < 0) {
+    ERROR("%s plugin: MIN must be greater then or equal to zero in \"%s\".",
+          plugin, ci->key);
+    return ERANGE;
+  }
+
+  latency_bucket_t *tmp =
+      realloc(conf->buckets, sizeof(*conf->buckets) * (conf->buckets_num + 1));
+  if (tmp == NULL) {
+    ERROR("%s plugin: realloc failed.", plugin);
+    return ENOMEM;
+  }
+  conf->buckets = tmp;
+  conf->buckets[conf->buckets_num].lower_bound =
+      DOUBLE_TO_CDTIME_T(ci->values[0].value.number);
+  conf->buckets[conf->buckets_num].upper_bound =
+      DOUBLE_TO_CDTIME_T(ci->values[1].value.number);
+  conf->buckets_num++;
+
+  return (0);
+} /* int latency_config_add_bucket */
+
+int latency_config(latency_config_t *conf, oconfig_item_t *ci,
+                   char const *plugin) {
+  int status = 0;
+
+  for (int i = 0; i < ci->children_num; i++) {
+    oconfig_item_t *child = ci->children + i;
+
+    if (strcasecmp("Percentile", child->key) == 0)
+      status = latency_config_add_percentile(conf, child, plugin);
+    else if (strcasecmp("Bucket", child->key) == 0)
+      status = latency_config_add_bucket(conf, child, plugin);
+    else
+      WARNING("%s plugin: \"%s\" is not a valid option within a \"%s\" block.",
+              plugin, child->key, ci->key);
+
+    if (status != 0)
+      return status;
+  }
+
+  if ((status == 0) && (conf->percentile_num == 0) &&
+      (conf->buckets_num == 0)) {
+    ERROR("%s plugin: The \"%s\" block must contain at least one "
+          "\"Percentile\" or \"Bucket\" option.",
+          plugin, ci->key);
+    return EINVAL;
+  }
+
+  return 0;
+}
+
+int latency_config_copy(latency_config_t *dst, const latency_config_t src) {
+  *dst = (latency_config_t){
+      .percentile_num = src.percentile_num, .buckets_num = src.buckets_num,
+  };
+
+  dst->percentile = calloc(dst->percentile_num, sizeof(*dst->percentile));
+  dst->buckets = calloc(dst->buckets_num, sizeof(*dst->buckets));
+
+  if ((dst->percentile == NULL) || (dst->buckets == 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));
+
+  return 0;
+} /* int latency_config_copy */
+
+void latency_config_free(latency_config_t conf) {
+  sfree(conf.percentile);
+  sfree(conf.buckets);
+} /* void latency_config_free */
diff --git a/src/utils_latency_config.h b/src/utils_latency_config.h
new file mode 100644 (file)
index 0000000..9a7a11a
--- /dev/null
@@ -0,0 +1,62 @@
+/**
+ * collectd - src/utils_latency_config.c
+ * Copyright (C) 2013-2016   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 <octo at collectd.org>
+ *   Pavel Rochnyack <pavel2000 at ngs.ru>
+ */
+
+#ifndef UTILS_LATENCY_CONFIG_H
+#define UTILS_LATENCY_CONFIG_H 1
+
+#include "collectd.h"
+
+#include "liboconfig/oconfig.h"
+#include "utils_time.h"
+
+typedef struct {
+  cdtime_t lower_bound;
+  cdtime_t upper_bound;
+} latency_bucket_t;
+
+typedef struct {
+  double *percentile;
+  size_t percentile_num;
+
+  latency_bucket_t *buckets;
+  size_t buckets_num;
+
+  /*
+  _Bool lower;
+  _Bool upper;
+  _Bool avg;
+  */
+} latency_config_t;
+
+int latency_config(latency_config_t *conf, oconfig_item_t *ci,
+                   char const *plugin);
+
+int latency_config_copy(latency_config_t *dst, const latency_config_t src);
+
+void latency_config_free(latency_config_t conf);
+
+#endif /* UTILS_LATENCY_CONFIG_H */
index 7d16039..ef29d8b 100644 (file)
@@ -24,7 +24,7 @@
  *   Florian octo Forster <octo at collectd.org>
  */
 
-#define DBL_PRECISION 1e-9
+#define DBL_PRECISION 1e-6
 
 #include "common.h" /* for STATIC_ARRAY_SIZE */
 #include "collectd.h"
@@ -101,9 +101,134 @@ DEF_TEST(percentile) {
   return 0;
 }
 
+DEF_TEST(get_rate) {
+  /* We re-declare the struct here so we can inspect its content. */
+  struct {
+    cdtime_t start_time;
+    cdtime_t sum;
+    size_t num;
+    cdtime_t min;
+    cdtime_t max;
+    cdtime_t bin_width;
+    int histogram[HISTOGRAM_NUM_BINS];
+  } * peek;
+  latency_counter_t *l;
+
+  CHECK_NOT_NULL(l = latency_counter_create());
+  peek = (void *)l;
+
+  for (time_t i = 1; i <= 125; i++) {
+    latency_counter_add(l, TIME_T_TO_CDTIME_T(i));
+  }
+
+  /* We expect a bucket width of 125ms. */
+  EXPECT_EQ_UINT64(DOUBLE_TO_CDTIME_T(0.125), peek->bin_width);
+
+  struct {
+    size_t index;
+    int want;
+  } bucket_cases[] = {
+      {0, 0},  /* (0.000-0.125] */
+      {1, 0},  /* (0.125-0.250] */
+      {2, 0},  /* (0.250-0.375] */
+      {3, 0},  /* (0.375-0.500] */
+      {4, 0},  /* (0.500-0.625] */
+      {5, 0},  /* (0.625-0.750] */
+      {6, 0},  /* (0.750-0.875] */
+      {7, 1},  /* (0.875-1.000] */
+      {8, 0},  /* (1.000-1.125] */
+      {9, 0},  /* (1.125-1.250] */
+      {10, 0}, /* (1.250-1.375] */
+      {11, 0}, /* (1.375-1.500] */
+      {12, 0}, /* (1.500-1.625] */
+      {13, 0}, /* (1.625-1.750] */
+      {14, 0}, /* (1.750-1.875] */
+      {15, 1}, /* (1.875-2.000] */
+      {16, 0}, /* (2.000-2.125] */
+  };
+
+  for (size_t i = 0; i < STATIC_ARRAY_SIZE(bucket_cases); i++) {
+    size_t index = bucket_cases[i].index;
+    EXPECT_EQ_INT(bucket_cases[i].want, peek->histogram[index]);
+  }
+
+  struct {
+    cdtime_t lower_bound;
+    cdtime_t upper_bound;
+    double want;
+  } cases[] = {
+      {
+          // bucket 6 is zero
+          DOUBLE_TO_CDTIME_T_STATIC(0.750), DOUBLE_TO_CDTIME_T_STATIC(0.875),
+          0.00,
+      },
+      {
+          // bucket 7 contains the t=1 update
+          DOUBLE_TO_CDTIME_T_STATIC(0.875), DOUBLE_TO_CDTIME_T_STATIC(1.000),
+          1.00,
+      },
+      {
+          // range: bucket 7 - bucket 15; contains the t=1 and t=2 updates
+          DOUBLE_TO_CDTIME_T_STATIC(0.875), DOUBLE_TO_CDTIME_T_STATIC(2.000),
+          2.00,
+      },
+      {
+          // lower bucket is only partially applied
+          DOUBLE_TO_CDTIME_T_STATIC(0.875 + (0.125 / 4)),
+          DOUBLE_TO_CDTIME_T_STATIC(2.000), 1.75,
+      },
+      {
+          // upper bucket is only partially applied
+          DOUBLE_TO_CDTIME_T_STATIC(0.875),
+          DOUBLE_TO_CDTIME_T_STATIC(2.000 - (0.125 / 4)), 1.75,
+      },
+      {
+          // both buckets are only partially applied
+          DOUBLE_TO_CDTIME_T_STATIC(0.875 + (0.125 / 4)),
+          DOUBLE_TO_CDTIME_T_STATIC(2.000 - (0.125 / 4)), 1.50,
+      },
+      {
+          // lower bound is unspecified
+          0, DOUBLE_TO_CDTIME_T_STATIC(2.000), 2.00,
+      },
+      {
+          // upper bound is unspecified
+          DOUBLE_TO_CDTIME_T_STATIC(125.000 - 0.125), 0, 1.00,
+      },
+      {
+          // overflow test: upper >> longest latency
+          DOUBLE_TO_CDTIME_T_STATIC(1.000), DOUBLE_TO_CDTIME_T_STATIC(999999),
+          124.00,
+      },
+      {
+          // overflow test: lower > longest latency
+          DOUBLE_TO_CDTIME_T_STATIC(130), 0, 0.00,
+      },
+      {
+          // lower > upper => error
+          DOUBLE_TO_CDTIME_T_STATIC(10), DOUBLE_TO_CDTIME_T_STATIC(9), NAN,
+      },
+      {
+          // lower == upper => zero
+          DOUBLE_TO_CDTIME_T_STATIC(9), DOUBLE_TO_CDTIME_T_STATIC(9), 0.00,
+      },
+  };
+
+  for (size_t i = 0; i < STATIC_ARRAY_SIZE(cases); i++) {
+    cdtime_t now = peek->start_time + TIME_T_TO_CDTIME_T(1);
+    EXPECT_EQ_DOUBLE(cases[i].want,
+                     latency_counter_get_rate(l, cases[i].lower_bound,
+                                              cases[i].upper_bound, now));
+  }
+
+  latency_counter_destroy(l);
+  return 0;
+}
+
 int main(void) {
   RUN_TEST(simple);
   RUN_TEST(percentile);
+  RUN_TEST(get_rate);
 
   END_TEST;
 }
diff --git a/src/utils_match.c b/src/utils_match.c
new file mode 100644 (file)
index 0000000..11b3f74
--- /dev/null
@@ -0,0 +1,378 @@
+/**
+ * collectd - src/utils_match.c
+ * Copyright (C) 2008-2014  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 <octo at collectd.org>
+ **/
+
+#include "collectd.h"
+
+#include "common.h"
+#include "plugin.h"
+
+#include "utils_match.h"
+
+#include <regex.h>
+
+#define UTILS_MATCH_FLAGS_EXCLUDE_REGEX 0x02
+#define UTILS_MATCH_FLAGS_REGEX 0x04
+
+struct cu_match_s {
+  regex_t regex;
+  regex_t excluderegex;
+  int flags;
+
+  int (*callback)(const char *str, char *const *matches, size_t matches_num,
+                  void *user_data);
+  void *user_data;
+  void (*free)(void *user_data);
+};
+
+/*
+ * Private functions
+ */
+static char *match_substr(const char *str, int begin, int end) {
+  char *ret;
+  size_t ret_len;
+
+  if ((begin < 0) || (end < 0) || (begin >= end))
+    return (NULL);
+  if ((size_t)end > (strlen(str) + 1)) {
+    ERROR("utils_match: match_substr: `end' points after end of string.");
+    return (NULL);
+  }
+
+  ret_len = end - begin;
+  ret = malloc(ret_len + 1);
+  if (ret == NULL) {
+    ERROR("utils_match: match_substr: malloc failed.");
+    return (NULL);
+  }
+
+  sstrncpy(ret, str + begin, ret_len + 1);
+  return (ret);
+} /* char *match_substr */
+
+static int default_callback(const char __attribute__((unused)) * str,
+                            char *const *matches, size_t matches_num,
+                            void *user_data) {
+  cu_match_value_t *data = (cu_match_value_t *)user_data;
+
+  if (data->ds_type & UTILS_MATCH_DS_TYPE_GAUGE) {
+    gauge_t value;
+    char *endptr = NULL;
+
+    if (data->ds_type & UTILS_MATCH_CF_GAUGE_INC) {
+      data->value.gauge = isnan(data->value.gauge) ? 1 : data->value.gauge + 1;
+      data->values_num++;
+      return (0);
+    }
+
+    if (matches_num < 2)
+      return (-1);
+
+    value = (gauge_t)strtod(matches[1], &endptr);
+    if (matches[1] == endptr)
+      return (-1);
+
+    if (data->ds_type & UTILS_MATCH_CF_GAUGE_DIST) {
+      latency_counter_add(data->latency, DOUBLE_TO_CDTIME_T(value));
+      data->values_num++;
+      return (0);
+    }
+
+    if ((data->values_num == 0) ||
+        (data->ds_type & UTILS_MATCH_CF_GAUGE_LAST) ||
+        (data->ds_type & UTILS_MATCH_CF_GAUGE_PERSIST)) {
+      data->value.gauge = value;
+    } else if (data->ds_type & UTILS_MATCH_CF_GAUGE_AVERAGE) {
+      double f = ((double)data->values_num) / ((double)(data->values_num + 1));
+      data->value.gauge = (data->value.gauge * f) + (value * (1.0 - f));
+    } else if (data->ds_type & UTILS_MATCH_CF_GAUGE_MIN) {
+      if (data->value.gauge > value)
+        data->value.gauge = value;
+    } else if (data->ds_type & UTILS_MATCH_CF_GAUGE_MAX) {
+      if (data->value.gauge < value)
+        data->value.gauge = value;
+    } else if (data->ds_type & UTILS_MATCH_CF_GAUGE_ADD) {
+      data->value.gauge += value;
+    } else {
+      ERROR("utils_match: default_callback: obj->ds_type is invalid!");
+      return (-1);
+    }
+
+    data->values_num++;
+  } else if (data->ds_type & UTILS_MATCH_DS_TYPE_COUNTER) {
+    counter_t value;
+    char *endptr = NULL;
+
+    if (data->ds_type & UTILS_MATCH_CF_COUNTER_INC) {
+      data->value.counter++;
+      data->values_num++;
+      return (0);
+    }
+
+    if (matches_num < 2)
+      return (-1);
+
+    value = (counter_t)strtoull(matches[1], &endptr, 0);
+    if (matches[1] == endptr)
+      return (-1);
+
+    if (data->ds_type & UTILS_MATCH_CF_COUNTER_SET)
+      data->value.counter = value;
+    else if (data->ds_type & UTILS_MATCH_CF_COUNTER_ADD)
+      data->value.counter += value;
+    else {
+      ERROR("utils_match: default_callback: obj->ds_type is invalid!");
+      return (-1);
+    }
+
+    data->values_num++;
+  } else if (data->ds_type & UTILS_MATCH_DS_TYPE_DERIVE) {
+    derive_t value;
+    char *endptr = NULL;
+
+    if (data->ds_type & UTILS_MATCH_CF_DERIVE_INC) {
+      data->value.derive++;
+      data->values_num++;
+      return (0);
+    }
+
+    if (matches_num < 2)
+      return (-1);
+
+    value = (derive_t)strtoll(matches[1], &endptr, 0);
+    if (matches[1] == endptr)
+      return (-1);
+
+    if (data->ds_type & UTILS_MATCH_CF_DERIVE_SET)
+      data->value.derive = value;
+    else if (data->ds_type & UTILS_MATCH_CF_DERIVE_ADD)
+      data->value.derive += value;
+    else {
+      ERROR("utils_match: default_callback: obj->ds_type is invalid!");
+      return (-1);
+    }
+
+    data->values_num++;
+  } else if (data->ds_type & UTILS_MATCH_DS_TYPE_ABSOLUTE) {
+    absolute_t value;
+    char *endptr = NULL;
+
+    if (matches_num < 2)
+      return (-1);
+
+    value = (absolute_t)strtoull(matches[1], &endptr, 0);
+    if (matches[1] == endptr)
+      return (-1);
+
+    if (data->ds_type & UTILS_MATCH_CF_ABSOLUTE_SET)
+      data->value.absolute = value;
+    else {
+      ERROR("utils_match: default_callback: obj->ds_type is invalid!");
+      return (-1);
+    }
+
+    data->values_num++;
+  } else {
+    ERROR("utils_match: default_callback: obj->ds_type is invalid!");
+    return (-1);
+  }
+
+  return (0);
+} /* int default_callback */
+
+static void match_simple_free(void *data) {
+  cu_match_value_t *user_data = (cu_match_value_t *)data;
+  if (user_data->latency)
+    latency_counter_destroy(user_data->latency);
+
+  free(data);
+} /* void match_simple_free */
+
+/*
+ * Public functions
+ */
+cu_match_t *
+match_create_callback(const char *regex, const char *excluderegex,
+                      int (*callback)(const char *str, char *const *matches,
+                                      size_t matches_num, void *user_data),
+                      void *user_data,
+                      void (*free_user_data)(void *user_data)) {
+  cu_match_t *obj;
+  int status;
+
+  DEBUG("utils_match: match_create_callback: regex = %s, excluderegex = %s",
+        regex, excluderegex);
+
+  obj = calloc(1, sizeof(*obj));
+  if (obj == NULL)
+    return (NULL);
+
+  status = regcomp(&obj->regex, regex, REG_EXTENDED | REG_NEWLINE);
+  if (status != 0) {
+    ERROR("Compiling the regular expression \"%s\" failed.", regex);
+    sfree(obj);
+    return (NULL);
+  }
+  obj->flags |= UTILS_MATCH_FLAGS_REGEX;
+
+  if (excluderegex && strcmp(excluderegex, "") != 0) {
+    status = regcomp(&obj->excluderegex, excluderegex, REG_EXTENDED);
+    if (status != 0) {
+      ERROR("Compiling the excluding regular expression \"%s\" failed.",
+            excluderegex);
+      sfree(obj);
+      return (NULL);
+    }
+    obj->flags |= UTILS_MATCH_FLAGS_EXCLUDE_REGEX;
+  }
+
+  obj->callback = callback;
+  obj->user_data = user_data;
+  obj->free = free_user_data;
+
+  return (obj);
+} /* cu_match_t *match_create_callback */
+
+cu_match_t *match_create_simple(const char *regex, const char *excluderegex,
+                                int match_ds_type) {
+  cu_match_value_t *user_data;
+  cu_match_t *obj;
+
+  user_data = calloc(1, sizeof(*user_data));
+  if (user_data == NULL)
+    return (NULL);
+  user_data->ds_type = match_ds_type;
+
+  if ((match_ds_type & UTILS_MATCH_DS_TYPE_GAUGE) &&
+      (match_ds_type & UTILS_MATCH_CF_GAUGE_DIST)) {
+    user_data->latency = latency_counter_create();
+    if (user_data->latency == NULL) {
+      ERROR("match_create_simple(): latency_counter_create() failed.");
+      free(user_data);
+      return (NULL);
+    }
+  }
+
+  obj = match_create_callback(regex, excluderegex, default_callback, user_data,
+                              match_simple_free);
+  if (obj == NULL) {
+    if (user_data->latency)
+      latency_counter_destroy(user_data->latency);
+
+    sfree(user_data);
+    return (NULL);
+  }
+  return (obj);
+} /* cu_match_t *match_create_simple */
+
+void match_value_reset(cu_match_value_t *mv) {
+  if (mv == NULL)
+    return;
+
+  /* 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->values_num = 0;
+  }
+} /* }}} void match_value_reset */
+
+void match_destroy(cu_match_t *obj) {
+  if (obj == NULL)
+    return;
+
+  if (obj->flags & UTILS_MATCH_FLAGS_REGEX)
+    regfree(&obj->regex);
+  if (obj->flags & UTILS_MATCH_FLAGS_EXCLUDE_REGEX)
+    regfree(&obj->excluderegex);
+  if ((obj->user_data != NULL) && (obj->free != NULL))
+    (*obj->free)(obj->user_data);
+
+  sfree(obj);
+} /* void match_destroy */
+
+int match_apply(cu_match_t *obj, const char *str) {
+  int status;
+  regmatch_t re_match[32];
+  char *matches[32] = {0};
+  size_t matches_num;
+
+  if ((obj == NULL) || (str == NULL))
+    return (-1);
+
+  if (obj->flags & UTILS_MATCH_FLAGS_EXCLUDE_REGEX) {
+    status =
+        regexec(&obj->excluderegex, str, STATIC_ARRAY_SIZE(re_match), re_match,
+                /* eflags = */ 0);
+    /* Regex did match, so exclude this line */
+    if (status == 0) {
+      DEBUG("ExludeRegex matched, don't count that line\n");
+      return (0);
+    }
+  }
+
+  status = regexec(&obj->regex, str, STATIC_ARRAY_SIZE(re_match), re_match,
+                   /* eflags = */ 0);
+
+  /* Regex did not match */
+  if (status != 0)
+    return (0);
+
+  for (matches_num = 0; matches_num < STATIC_ARRAY_SIZE(matches);
+       matches_num++) {
+    if ((re_match[matches_num].rm_so < 0) || (re_match[matches_num].rm_eo < 0))
+      break;
+
+    matches[matches_num] = match_substr(str, re_match[matches_num].rm_so,
+                                        re_match[matches_num].rm_eo);
+    if (matches[matches_num] == NULL) {
+      status = -1;
+      break;
+    }
+  }
+
+  if (status != 0) {
+    ERROR("utils_match: match_apply: match_substr failed.");
+  } else {
+    status = obj->callback(str, matches, matches_num, obj->user_data);
+    if (status != 0) {
+      ERROR("utils_match: match_apply: callback failed.");
+    }
+  }
+
+  for (size_t i = 0; i < matches_num; i++) {
+    sfree(matches[i]);
+  }
+
+  return (status);
+} /* int match_apply */
+
+void *match_get_user_data(cu_match_t *obj) {
+  if (obj == NULL)
+    return (NULL);
+  return (obj->user_data);
+} /* void *match_get_user_data */
+
+/* vim: set sw=2 sts=2 ts=8 : */
diff --git a/src/utils_match.h b/src/utils_match.h
new file mode 100644 (file)
index 0000000..1383530
--- /dev/null
@@ -0,0 +1,181 @@
+/**
+ * collectd - src/utils_match.h
+ * Copyright (C) 2008-2014  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 <octo at collectd.org>
+ **/
+
+#ifndef UTILS_MATCH_H
+#define UTILS_MATCH_H 1
+
+#include "plugin.h"
+#include "utils_latency.h"
+
+/*
+ * Each type may have 12 sub-types
+ * 0x1000 = 1000000000000
+ *          ^             <- Type bit
+ *           ^^^^^^^^^^^^ <- Subtype bits
+ */
+#define UTILS_MATCH_DS_TYPE_GAUGE 0x1000
+#define UTILS_MATCH_DS_TYPE_COUNTER 0x2000
+#define UTILS_MATCH_DS_TYPE_DERIVE 0x4000
+#define UTILS_MATCH_DS_TYPE_ABSOLUTE 0x8000
+
+#define UTILS_MATCH_CF_GAUGE_AVERAGE 0x01
+#define UTILS_MATCH_CF_GAUGE_MIN 0x02
+#define UTILS_MATCH_CF_GAUGE_MAX 0x04
+#define UTILS_MATCH_CF_GAUGE_LAST 0x08
+#define UTILS_MATCH_CF_GAUGE_INC 0x10
+#define UTILS_MATCH_CF_GAUGE_ADD 0x20
+#define UTILS_MATCH_CF_GAUGE_PERSIST 0x40
+#define UTILS_MATCH_CF_GAUGE_DIST 0x80
+
+#define UTILS_MATCH_CF_COUNTER_SET 0x01
+#define UTILS_MATCH_CF_COUNTER_ADD 0x02
+#define UTILS_MATCH_CF_COUNTER_INC 0x04
+
+#define UTILS_MATCH_CF_DERIVE_SET 0x01
+#define UTILS_MATCH_CF_DERIVE_ADD 0x02
+#define UTILS_MATCH_CF_DERIVE_INC 0x04
+
+#define UTILS_MATCH_CF_ABSOLUTE_SET 0x01
+#define UTILS_MATCH_CF_ABSOLUTE_ADD 0x02
+#define UTILS_MATCH_CF_ABSOLUTE_INC 0x04
+
+/*
+ * Data types
+ */
+struct cu_match_s;
+typedef struct cu_match_s cu_match_t;
+
+struct cu_match_value_s {
+  int ds_type;
+  value_t value;
+  unsigned int values_num;
+  latency_counter_t *latency;
+};
+typedef struct cu_match_value_s cu_match_value_t;
+
+/*
+ * Prototypes
+ */
+/*
+ * NAME
+ *  match_create_callback
+ *
+ * DESCRIPTION
+ *  Creates a new `cu_match_t' object which will use the regular expression
+ *  `regex' to match lines, see the `match_apply' method below. If the line
+ *  matches, the callback passed in `callback' will be called along with the
+ *  pointer `user_pointer'.
+ *  The string that's passed to the callback depends on the regular expression:
+ *  If the regular expression includes a sub-match, i. e. something like
+ *    "value=([0-9][0-9]*)"
+ *  then only the submatch (the part in the parenthesis) will be passed to the
+ *  callback. If there is no submatch, then the entire string is passed to the
+ *  callback.
+ *  The optional `excluderegex' allows to exclude the line from the match, if
+ *  the excluderegex matches.
+ *  When `match_destroy' is called the `user_data' pointer is freed using
+ *  the `free_user_data' callback - if it is not NULL.
+ */
+cu_match_t *
+match_create_callback(const char *regex, const char *excluderegex,
+                      int (*callback)(const char *str, char *const *matches,
+                                      size_t matches_num, void *user_data),
+                      void *user_data, void (*free_user_data)(void *user_data));
+
+/*
+ * NAME
+ *  match_create_simple
+ *
+ * DESCRIPTION
+ *  Creates a new `cu_match_t' with a default callback. The user data for that
+ *  default callback will be a `cu_match_value_t' structure, with
+ *  `ds_type' copied to the structure. The default callback will handle the
+ *  string as containing a number (see strtoll(3) and strtod(3)) and store that
+ *  number in the `value' member. How that is done depends on `ds_type':
+ *
+ *  UTILS_MATCH_DS_TYPE_GAUGE
+ *    The function will search for a floating point number in the string and
+ *    store it in value.gauge.
+ *  UTILS_MATCH_DS_TYPE_COUNTER_SET
+ *    The function will search for an integer in the string and store it in
+ *    value.counter.
+ *  UTILS_MATCH_DS_TYPE_COUNTER_ADD
+ *    The function will search for an integer in the string and add it to the
+ *    value in value.counter.
+ *  UTILS_MATCH_DS_TYPE_COUNTER_INC
+ *    The function will not search for anything in the string and increase
+ *    value.counter by one.
+ */
+cu_match_t *match_create_simple(const char *regex, const char *excluderegex,
+                                int ds_type);
+
+/*
+ * NAME
+ *  match_value_reset
+ *
+ * DESCRIPTION
+ *   Resets the internal state, if applicable. This function must be called
+ *   after each iteration for "simple" matches, usually after dispatching the
+ *   metrics.
+ */
+void match_value_reset(cu_match_value_t *mv);
+
+/*
+ * NAME
+ *  match_destroy
+ *
+ * DESCRIPTION
+ *  Destroys the object and frees all internal resources.
+ */
+void match_destroy(cu_match_t *obj);
+
+/*
+ * NAME
+ *  match_apply
+ *
+ * DESCRIPTION
+ *  Tries to match the string `str' with the regular expression of `obj'. If
+ *  the string matches, calls the callback in `obj' with the (sub-)match.
+ *
+ *  The user_data pointer passed to `match_create_callback' is NOT freed
+ *  automatically. The `cu_match_value_t' structure allocated by
+ *  `match_create_callback' is freed automatically.
+ */
+int match_apply(cu_match_t *obj, const char *str);
+
+/*
+ * NAME
+ *  match_get_user_data
+ *
+ * DESCRIPTION
+ *  Returns the pointer passed to `match_create_callback' or a pointer to the
+ *  `cu_match_value_t' structure allocated by `match_create_simple'.
+ */
+void *match_get_user_data(cu_match_t *obj);
+
+#endif /* UTILS_MATCH_H */
+
+/* vim: set sw=2 sts=2 ts=8 : */
index 660e085..87f26df 100644 (file)
@@ -527,7 +527,7 @@ static cu_mount_t *cu_mount_gen_getmntent(void) {
 
   return (first);
 } /* static cu_mount_t *cu_mount_gen_getmntent (void) */
-  /* #endif HAVE_TWO_GETMNTENT || HAVE_GEN_GETMNTENT || HAVE_SUN_GETMNTENT */
+/* #endif HAVE_TWO_GETMNTENT || HAVE_GEN_GETMNTENT || HAVE_SUN_GETMNTENT */
 
 #elif HAVE_SEQ_GETMNTENT
 #warn "This version of `getmntent' hat not yet been implemented!"
diff --git a/src/utils_tail.c b/src/utils_tail.c
new file mode 100644 (file)
index 0000000..565a224
--- /dev/null
@@ -0,0 +1,235 @@
+/**
+ * collectd - src/utils_tail.c
+ * Copyright (C) 2007-2008  C-Ware, Inc.
+ * Copyright (C) 2008       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.
+ *
+ * Author:
+ *   Luke Heberling <lukeh at c-ware.com>
+ *   Florian Forster <octo at collectd.org>
+ *
+ * Description:
+ *   Encapsulates useful code for plugins which must watch for appends to
+ *   the end of a file.
+ **/
+
+#include "collectd.h"
+
+#include "common.h"
+#include "utils_tail.h"
+
+struct cu_tail_s {
+  char *file;
+  FILE *fh;
+  struct stat stat;
+};
+
+static int cu_tail_reopen(cu_tail_t *obj) {
+  int seek_end = 0;
+  FILE *fh;
+  struct stat stat_buf = {0};
+  int status;
+
+  status = stat(obj->file, &stat_buf);
+  if (status != 0) {
+    char errbuf[1024];
+    ERROR("utils_tail: stat (%s) failed: %s", obj->file,
+          sstrerror(errno, errbuf, sizeof(errbuf)));
+    return (-1);
+  }
+
+  /* The file is already open.. */
+  if ((obj->fh != NULL) && (stat_buf.st_ino == obj->stat.st_ino)) {
+    /* Seek to the beginning if file was truncated */
+    if (stat_buf.st_size < obj->stat.st_size) {
+      INFO("utils_tail: File `%s' was truncated.", obj->file);
+      status = fseek(obj->fh, 0, SEEK_SET);
+      if (status != 0) {
+        char errbuf[1024];
+        ERROR("utils_tail: fseek (%s) failed: %s", obj->file,
+              sstrerror(errno, errbuf, sizeof(errbuf)));
+        fclose(obj->fh);
+        obj->fh = NULL;
+        return (-1);
+      }
+    }
+    memcpy(&obj->stat, &stat_buf, sizeof(struct stat));
+    return (1);
+  }
+
+  /* Seek to the end if we re-open the same file again or the file opened
+   * is the first at all or the first after an error */
+  if ((obj->stat.st_ino == 0) || (obj->stat.st_ino == stat_buf.st_ino))
+    seek_end = 1;
+
+  fh = fopen(obj->file, "r");
+  if (fh == NULL) {
+    char errbuf[1024];
+    ERROR("utils_tail: fopen (%s) failed: %s", obj->file,
+          sstrerror(errno, errbuf, sizeof(errbuf)));
+    return (-1);
+  }
+
+  if (seek_end != 0) {
+    status = fseek(fh, 0, SEEK_END);
+    if (status != 0) {
+      char errbuf[1024];
+      ERROR("utils_tail: fseek (%s) failed: %s", obj->file,
+            sstrerror(errno, errbuf, sizeof(errbuf)));
+      fclose(fh);
+      return (-1);
+    }
+  }
+
+  if (obj->fh != NULL)
+    fclose(obj->fh);
+  obj->fh = fh;
+  memcpy(&obj->stat, &stat_buf, sizeof(struct stat));
+
+  return (0);
+} /* int cu_tail_reopen */
+
+cu_tail_t *cu_tail_create(const char *file) {
+  cu_tail_t *obj;
+
+  obj = calloc(1, sizeof(*obj));
+  if (obj == NULL)
+    return (NULL);
+
+  obj->file = strdup(file);
+  if (obj->file == NULL) {
+    free(obj);
+    return (NULL);
+  }
+
+  obj->fh = NULL;
+
+  return (obj);
+} /* cu_tail_t *cu_tail_create */
+
+int cu_tail_destroy(cu_tail_t *obj) {
+  if (obj->fh != NULL)
+    fclose(obj->fh);
+  free(obj->file);
+  free(obj);
+
+  return (0);
+} /* int cu_tail_destroy */
+
+int cu_tail_readline(cu_tail_t *obj, char *buf, int buflen) {
+  int status;
+
+  if (buflen < 1) {
+    ERROR("utils_tail: cu_tail_readline: buflen too small: %i bytes.", buflen);
+    return (-1);
+  }
+
+  if (obj->fh == NULL) {
+    status = cu_tail_reopen(obj);
+    if (status < 0)
+      return (status);
+  }
+  assert(obj->fh != NULL);
+
+  /* Try to read from the filehandle. If that succeeds, everything appears to
+   * be fine and we can return. */
+  clearerr(obj->fh);
+  if (fgets(buf, buflen, obj->fh) != NULL) {
+    buf[buflen - 1] = 0;
+    return (0);
+  }
+
+  /* Check if we encountered an error */
+  if (ferror(obj->fh) != 0) {
+    /* Jupp, error. Force `cu_tail_reopen' to reopen the file.. */
+    fclose(obj->fh);
+    obj->fh = NULL;
+  }
+  /* else: eof -> check if the file was moved away and reopen the new file if
+   * so.. */
+
+  status = cu_tail_reopen(obj);
+  /* error -> return with error */
+  if (status < 0)
+    return (status);
+  /* file end reached and file not reopened -> nothing more to read */
+  else if (status > 0) {
+    buf[0] = 0;
+    return (0);
+  }
+
+  /* If we get here: file was re-opened and there may be more to read.. Let's
+   * try again. */
+  if (fgets(buf, buflen, obj->fh) != NULL) {
+    buf[buflen - 1] = 0;
+    return (0);
+  }
+
+  if (ferror(obj->fh) != 0) {
+    char errbuf[1024];
+    WARNING("utils_tail: fgets (%s) returned an error: %s", obj->file,
+            sstrerror(errno, errbuf, sizeof(errbuf)));
+    fclose(obj->fh);
+    obj->fh = NULL;
+    return (-1);
+  }
+
+  /* EOf, well, apparently the new file is empty.. */
+  buf[0] = 0;
+  return (0);
+} /* int cu_tail_readline */
+
+int cu_tail_read(cu_tail_t *obj, char *buf, int buflen, tailfunc_t *callback,
+                 void *data) {
+  int status;
+
+  while (42) {
+    size_t len;
+
+    status = cu_tail_readline(obj, buf, buflen);
+    if (status != 0) {
+      ERROR("utils_tail: cu_tail_read: cu_tail_readline "
+            "failed.");
+      break;
+    }
+
+    /* check for EOF */
+    if (buf[0] == 0)
+      break;
+
+    len = strlen(buf);
+    while (len > 0) {
+      if (buf[len - 1] != '\n')
+        break;
+      buf[len - 1] = '\0';
+      len--;
+    }
+
+    status = callback(data, buf, buflen);
+    if (status != 0) {
+      ERROR("utils_tail: cu_tail_read: callback returned "
+            "status %i.",
+            status);
+      break;
+    }
+  }
+
+  return status;
+} /* int cu_tail_read */
diff --git a/src/utils_tail.h b/src/utils_tail.h
new file mode 100644 (file)
index 0000000..73a6de2
--- /dev/null
@@ -0,0 +1,88 @@
+/**
+ * collectd - src/utils_tail.h
+ * Copyright (C) 2007-2008  C-Ware, Inc.
+ *
+ * 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.
+ *
+ * Author:
+ *   Luke Heberling <lukeh at c-ware.com>
+ *
+ * DESCRIPTION
+ *   Facilitates reading information that is appended to a file, taking into
+ *   account that the file may be rotated and a new file created under the
+ *   same name.
+ **/
+
+#ifndef UTILS_TAIL_H
+#define UTILS_TAIL_H 1
+
+struct cu_tail_s;
+typedef struct cu_tail_s cu_tail_t;
+
+typedef int tailfunc_t(void *data, char *buf, int buflen);
+
+/*
+ * NAME
+ *   cu_tail_create
+ *
+ * DESCRIPTION
+ *   Allocates a new tail object..
+ *
+ * PARAMETERS
+ *   `file'       The name of the file to be tailed.
+ */
+cu_tail_t *cu_tail_create(const char *file);
+
+/*
+ * cu_tail_destroy
+ *
+ * Takes a tail object returned by `cu_tail_create' and destroys it, freeing
+ * all internal memory.
+ *
+ * Returns 0 when successful and non-zero otherwise.
+ */
+int cu_tail_destroy(cu_tail_t *obj);
+
+/*
+ * cu_tail_readline
+ *
+ * Reads from the file until `buflen' characters are read, a newline
+ * character is read, or an eof condition is encountered. `buf' is
+ * always null-terminated on successful return and isn't touched when non-zero
+ * is returned.
+ *
+ * You can check if the EOF condition is reached by looking at the buffer: If
+ * the length of the string stored in the buffer is zero, EOF occurred.
+ * Otherwise at least the newline character will be in the buffer.
+ *
+ * Returns 0 when successful and non-zero otherwise.
+ */
+int cu_tail_readline(cu_tail_t *obj, char *buf, int buflen);
+
+/*
+ * cu_tail_readline
+ *
+ * Reads from the file until eof condition or an error is encountered.
+ *
+ * Returns 0 when successful and non-zero otherwise.
+ */
+int cu_tail_read(cu_tail_t *obj, char *buf, int buflen, tailfunc_t *callback,
+                 void *data);
+
+#endif /* UTILS_TAIL_H */
diff --git a/src/utils_tail_match.c b/src/utils_tail_match.c
new file mode 100644 (file)
index 0000000..505c693
--- /dev/null
@@ -0,0 +1,340 @@
+/*
+ * collectd - src/utils_tail_match.c
+ * Copyright (C) 2007-2008  C-Ware, Inc.
+ * Copyright (C) 2008       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.
+ *
+ * Author:
+ *   Luke Heberling <lukeh at c-ware.com>
+ *   Florian Forster <octo at collectd.org>
+ *
+ * Description:
+ *   Encapsulates useful code to plugins which must parse a log file.
+ */
+
+#include "collectd.h"
+
+#include "common.h"
+#include "plugin.h"
+#include "utils_latency_config.h"
+#include "utils_match.h"
+#include "utils_tail.h"
+#include "utils_tail_match.h"
+
+struct cu_tail_match_simple_s {
+  char plugin[DATA_MAX_NAME_LEN];
+  char plugin_instance[DATA_MAX_NAME_LEN];
+  char type[DATA_MAX_NAME_LEN];
+  char type_instance[DATA_MAX_NAME_LEN];
+  cdtime_t interval;
+  latency_config_t latency_config;
+};
+typedef struct cu_tail_match_simple_s cu_tail_match_simple_t;
+
+struct cu_tail_match_match_s {
+  cu_match_t *match;
+  void *user_data;
+  int (*submit)(cu_match_t *match, void *user_data);
+  void (*free)(void *user_data);
+};
+typedef struct cu_tail_match_match_s cu_tail_match_match_t;
+
+struct cu_tail_match_s {
+  int flags;
+  cu_tail_t *tail;
+
+  cdtime_t interval;
+  cu_tail_match_match_t *matches;
+  size_t matches_num;
+};
+
+/*
+ * Private functions
+ */
+static int simple_submit_match(cu_match_t *match, void *user_data) {
+  cu_tail_match_simple_t *data = (cu_tail_match_simple_t *)user_data;
+  cu_match_value_t *match_value;
+  value_list_t vl = VALUE_LIST_INIT;
+  value_t values[1];
+
+  match_value = (cu_match_value_t *)match_get_user_data(match);
+  if (match_value == NULL)
+    return (-1);
+
+  if ((match_value->ds_type & UTILS_MATCH_DS_TYPE_GAUGE) &&
+      (match_value->values_num == 0))
+    values[0].gauge = NAN;
+  else
+    values[0] = match_value->value;
+
+  vl.values = values;
+  vl.values_len = 1;
+  sstrncpy(vl.plugin, data->plugin, sizeof(vl.plugin));
+  sstrncpy(vl.plugin_instance, data->plugin_instance,
+           sizeof(vl.plugin_instance));
+  sstrncpy(vl.type, data->type, sizeof(vl.type));
+  sstrncpy(vl.type_instance, data->type_instance, sizeof(vl.type_instance));
+
+  vl.interval = data->interval;
+  plugin_dispatch_values(&vl);
+
+  match_value_reset(match_value);
+  return (0);
+} /* int simple_submit_match */
+
+static int latency_submit_match(cu_match_t *match, void *user_data) {
+  cu_tail_match_simple_t *data = (cu_tail_match_simple_t *)user_data;
+  cu_match_value_t *match_value;
+  value_list_t vl = VALUE_LIST_INIT;
+
+  match_value = (cu_match_value_t *)match_get_user_data(match);
+  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));
+  vl.interval = data->interval;
+  vl.time = cdtime();
+
+  /* Submit percentiles */
+  sstrncpy(vl.type, data->type, sizeof(vl.type));
+  for (size_t i = 0; i < data->latency_config.percentile_num; i++) {
+    if (strlen(data->type_instance) != 0)
+      ssnprintf(vl.type_instance, sizeof(vl.type_instance), "%s-%.0f",
+                data->type_instance, data->latency_config.percentile[i]);
+    else
+      ssnprintf(vl.type_instance, sizeof(vl.type_instance), "%.0f",
+                data->latency_config.percentile[i]);
+
+    vl.values = &(value_t){
+        .gauge =
+            (match_value->values_num != 0)
+                ? CDTIME_T_TO_DOUBLE(latency_counter_get_percentile(
+                      match_value->latency, data->latency_config.percentile[i]))
+                : NAN,
+    };
+    vl.values_len = 1;
+
+    plugin_dispatch_values(&vl);
+  }
+
+  /* Submit buckets */
+  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];
+
+    double lower_bound = CDTIME_T_TO_DOUBLE(bucket.lower_bound);
+    double upper_bound =
+        bucket.upper_bound ? CDTIME_T_TO_DOUBLE(bucket.upper_bound) : INFINITY;
+
+    if (strlen(data->type_instance) != 0)
+      ssnprintf(vl.type_instance, sizeof(vl.type_instance), "%s-%s-%g_%g",
+                data->type, data->type_instance, lower_bound, upper_bound);
+    else
+      ssnprintf(vl.type_instance, sizeof(vl.type_instance), "%s-%g_%g",
+                data->type, lower_bound, upper_bound);
+
+    vl.values = &(value_t){
+        .gauge =
+            latency_counter_get_rate(match_value->latency, bucket.lower_bound,
+                                     bucket.upper_bound, vl.time),
+    };
+    vl.values_len = 1;
+
+    plugin_dispatch_values(&vl);
+  }
+
+  match_value->value.gauge = NAN;
+  match_value->values_num = 0;
+  latency_counter_reset(match_value->latency);
+
+  return (0);
+} /* int latency_submit_match */
+
+static int tail_callback(void *data, char *buf,
+                         int __attribute__((unused)) buflen) {
+  cu_tail_match_t *obj = (cu_tail_match_t *)data;
+
+  for (size_t i = 0; i < obj->matches_num; i++)
+    match_apply(obj->matches[i].match, buf);
+
+  return (0);
+} /* int tail_callback */
+
+static void tail_match_simple_free(void *data) {
+  cu_tail_match_simple_t *user_data = (cu_tail_match_simple_t *)data;
+  latency_config_free(user_data->latency_config);
+  sfree(user_data);
+} /* void tail_match_simple_free */
+
+/*
+ * Public functions
+ */
+cu_tail_match_t *tail_match_create(const char *filename) {
+  cu_tail_match_t *obj;
+
+  obj = calloc(1, sizeof(*obj));
+  if (obj == NULL)
+    return (NULL);
+
+  obj->tail = cu_tail_create(filename);
+  if (obj->tail == NULL) {
+    sfree(obj);
+    return (NULL);
+  }
+
+  return (obj);
+} /* cu_tail_match_t *tail_match_create */
+
+void tail_match_destroy(cu_tail_match_t *obj) {
+  if (obj == NULL)
+    return;
+
+  if (obj->tail != NULL) {
+    cu_tail_destroy(obj->tail);
+    obj->tail = NULL;
+  }
+
+  for (size_t i = 0; i < obj->matches_num; i++) {
+    cu_tail_match_match_t *match = obj->matches + i;
+    if (match->match != NULL) {
+      match_destroy(match->match);
+      match->match = NULL;
+    }
+
+    if ((match->user_data != NULL) && (match->free != NULL))
+      (*match->free)(match->user_data);
+    match->user_data = NULL;
+  }
+
+  sfree(obj->matches);
+  sfree(obj);
+} /* void tail_match_destroy */
+
+int tail_match_add_match(cu_tail_match_t *obj, cu_match_t *match,
+                         int (*submit_match)(cu_match_t *match,
+                                             void *user_data),
+                         void *user_data,
+                         void (*free_user_data)(void *user_data)) {
+  cu_tail_match_match_t *temp;
+
+  temp = realloc(obj->matches,
+                 sizeof(cu_tail_match_match_t) * (obj->matches_num + 1));
+  if (temp == NULL)
+    return (-1);
+
+  obj->matches = temp;
+  obj->matches_num++;
+
+  DEBUG("tail_match_add_match interval %lf",
+        CDTIME_T_TO_DOUBLE(((cu_tail_match_simple_t *)user_data)->interval));
+  temp = obj->matches + (obj->matches_num - 1);
+
+  temp->match = match;
+  temp->user_data = user_data;
+  temp->submit = submit_match;
+  temp->free = free_user_data;
+
+  return (0);
+} /* int tail_match_add_match */
+
+int tail_match_add_match_simple(cu_tail_match_t *obj, const char *regex,
+                                const char *excluderegex, int ds_type,
+                                const char *plugin, const char *plugin_instance,
+                                const char *type, const char *type_instance,
+                                const latency_config_t latency_cfg,
+                                const cdtime_t interval) {
+  cu_match_t *match;
+  cu_tail_match_simple_t *user_data;
+  int status;
+
+  match = match_create_simple(regex, excluderegex, ds_type);
+  if (match == NULL)
+    return (-1);
+
+  user_data = calloc(1, sizeof(*user_data));
+  if (user_data == NULL) {
+    match_destroy(match);
+    return (-1);
+  }
+
+  sstrncpy(user_data->plugin, plugin, sizeof(user_data->plugin));
+  if (plugin_instance != NULL)
+    sstrncpy(user_data->plugin_instance, plugin_instance,
+             sizeof(user_data->plugin_instance));
+
+  sstrncpy(user_data->type, type, sizeof(user_data->type));
+  if (type_instance != NULL)
+    sstrncpy(user_data->type_instance, type_instance,
+             sizeof(user_data->type_instance));
+
+  user_data->interval = interval;
+
+  if ((ds_type & UTILS_MATCH_DS_TYPE_GAUGE) &&
+      (ds_type & UTILS_MATCH_CF_GAUGE_DIST)) {
+    status = latency_config_copy(&user_data->latency_config, latency_cfg);
+    if (status != 0) {
+      ERROR("tail_match_add_match_simple: latency_config_copy() failed.");
+      status = -1;
+      goto out;
+    }
+
+    status = tail_match_add_match(obj, match, latency_submit_match, user_data,
+                                  tail_match_simple_free);
+  } else {
+    status =
+        tail_match_add_match(obj, match, simple_submit_match, user_data, free);
+  }
+
+out:
+  if (status != 0) {
+    tail_match_simple_free(user_data);
+    match_destroy(match);
+  }
+
+  return (status);
+} /* int tail_match_add_match_simple */
+
+int tail_match_read(cu_tail_match_t *obj) {
+  char buffer[4096];
+  int status;
+
+  status = cu_tail_read(obj->tail, buffer, sizeof(buffer), tail_callback,
+                        (void *)obj);
+  if (status != 0) {
+    ERROR("tail_match: cu_tail_read failed.");
+    return (status);
+  }
+
+  for (size_t i = 0; i < obj->matches_num; i++) {
+    cu_tail_match_match_t *lt_match = obj->matches + i;
+
+    if (lt_match->submit == NULL)
+      continue;
+
+    (*lt_match->submit)(lt_match->match, lt_match->user_data);
+  }
+
+  return (0);
+} /* int tail_match_read */
+
+/* vim: set sw=2 sts=2 ts=8 : */
diff --git a/src/utils_tail_match.h b/src/utils_tail_match.h
new file mode 100644 (file)
index 0000000..09e3d40
--- /dev/null
@@ -0,0 +1,141 @@
+/*
+ * collectd - src/utils_tail_match.h
+ * Copyright (C) 2007-2008  C-Ware, Inc.
+ * Copyright (C) 2008       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:
+ *   Luke Heberling <lukeh at c-ware.com>
+ *   Florian Forster <octo at collectd.org>
+ *
+ * Description:
+ *   `tail_match' uses `utils_tail' and `utils_match' to tail a file and try to
+ *   match it using several regular expressions. Matches are then passed to
+ *   user-provided callback functions or default handlers. This should keep all
+ *   of the parsing logic out of the actual plugin, which only operate with
+ *   regular expressions.
+ */
+
+#include "utils_latency_config.h"
+#include "utils_match.h"
+
+struct cu_tail_match_s;
+typedef struct cu_tail_match_s cu_tail_match_t;
+
+/*
+ * NAME
+ *   tail_match_create
+ *
+ * DESCRIPTION
+ *   Allocates, initializes and returns a new `cu_tail_match_t' object.
+ *
+ * PARAMETERS
+ *   `filename'  The name to read data from.
+ *
+ * RETURN VALUE
+ *   Returns NULL upon failure, non-NULL otherwise.
+ */
+cu_tail_match_t *tail_match_create(const char *filename);
+
+/*
+ * NAME
+ *   tail_match_destroy
+ *
+ * DESCRIPTION
+ *   Releases resources used by the `cu_tail_match_t' object.
+ *
+ * PARAMETERS
+ *   The object to destroy.
+ */
+void tail_match_destroy(cu_tail_match_t *obj);
+
+/*
+ * NAME
+ *   tail_match_add_match
+ *
+ * DESCRIPTION
+ *   Adds a match, in form of a `cu_match_t' object, to the object.
+ *   After data has been read from the logfile (using utils_tail) the callback
+ *   function `submit_match' is called with the match object and the user
+ *   supplied data.
+ *   Please note that his function is called regardless whether this match
+ *   matched any lines recently or not.
+ *   When `tail_match_destroy' is called the `user_data' pointer is freed using
+ *   the `free_user_data' callback - if it is not NULL.
+ *   When using this interface the `tail_match' module doesn't dispatch any
+ * values
+ *   itself - all that has to happen in either the match-callbacks or the
+ *   submit_match callback.
+ *
+ * RETURN VALUE
+ *   Zero upon success, non-zero otherwise.
+ */
+int tail_match_add_match(cu_tail_match_t *obj, cu_match_t *match,
+                         int (*submit_match)(cu_match_t *match,
+                                             void *user_data),
+                         void *user_data,
+                         void (*free_user_data)(void *user_data));
+
+/*
+ * NAME
+ *  tail_match_add_match_simple
+ *
+ * DESCRIPTION
+ *  A simplified version of `tail_match_add_match'. The regular expressen
+ * `regex'
+ *  must match a number, which is then dispatched according to `ds_type'. See
+ *  the `match_create_simple' function in utils_match.h for a description how
+ *  this flag effects calculation of a new value.
+ *  The values gathered are dispatched by the tail_match module in this case.
+ * The
+ *  passed `plugin', `plugin_instance', `type', and `type_instance' are
+ *  directly used when submitting these values.
+ *  With excluderegex it is possible to exlude lines from the match.
+ *  The `latency_cfg' specifies configuration for submitting latency.
+ *
+ * RETURN VALUE
+ *   Zero upon success, non-zero otherwise.
+ */
+int tail_match_add_match_simple(cu_tail_match_t *obj, const char *regex,
+                                const char *excluderegex, int ds_type,
+                                const char *plugin, const char *plugin_instance,
+                                const char *type, const char *type_instance,
+                                const latency_config_t latency_cfg,
+                                const cdtime_t interval);
+
+/*
+ * NAME
+ *   tail_match_read
+ *
+ * DESCRIPTION
+ *   This function should be called periodically by plugins. It reads new lines
+ *   from the logfile using `utils_tail' and tries to match them using all
+ *   added `utils_match' objects.
+ *   After all lines have been read and processed, the submit_match callback is
+ *   called or, in case of tail_match_add_match_simple, the data is dispatched
+ * to
+ *   the daemon directly.
+ *
+ * RETURN VALUE
+ *   Zero on success, nonzero on failure.
+*/
+int tail_match_read(cu_tail_match_t *obj);
+
+/* vim: set sw=2 sts=2 ts=8 : */
index 9a08507..8bcaf40 100644 (file)
@@ -81,7 +81,7 @@ struct user_obj_s;
 typedef struct user_obj_s user_obj_t;
 struct user_obj_s {
   void *user_obj;
-  identifier_t ident;
+  lookup_identifier_t ident;
 
   user_obj_t *next;
 };
@@ -162,7 +162,7 @@ static int lu_copy_ident_to_match_part(part_match_t *match_part, /* {{{ */
 } /* }}} int lu_copy_ident_to_match_part */
 
 static int lu_copy_ident_to_match(identifier_match_t *match, /* {{{ */
-                                  identifier_t const *ident,
+                                  lookup_identifier_t const *ident,
                                   unsigned int group_by) {
   memset(match, 0, sizeof(*match));
 
@@ -567,7 +567,7 @@ void lookup_destroy(lookup_t *obj) /* {{{ */
 } /* }}} void lookup_destroy */
 
 int lookup_add(lookup_t *obj, /* {{{ */
-               identifier_t const *ident, unsigned int group_by,
+               lookup_identifier_t const *ident, unsigned int group_by,
                void *user_class) {
   by_type_entry_t *by_type = NULL;
   user_class_list_t *user_class_obj;
index 381f0cd..90a4ee5 100644 (file)
@@ -54,14 +54,14 @@ typedef void (*lookup_free_class_callback_t)(void *user_class);
  * freed. */
 typedef void (*lookup_free_obj_callback_t)(void *user_obj);
 
-struct identifier_s {
+struct lookup_identifier_s {
   char host[DATA_MAX_NAME_LEN];
   char plugin[DATA_MAX_NAME_LEN];
   char plugin_instance[DATA_MAX_NAME_LEN];
   char type[DATA_MAX_NAME_LEN];
   char type_instance[DATA_MAX_NAME_LEN];
 };
-typedef struct identifier_s identifier_t;
+typedef struct lookup_identifier_s lookup_identifier_t;
 
 #define LU_GROUP_BY_HOST 0x01
 #define LU_GROUP_BY_PLUGIN 0x02
@@ -78,8 +78,8 @@ lookup_t *lookup_create(lookup_class_callback_t, lookup_obj_callback_t,
                         lookup_free_obj_callback_t);
 void lookup_destroy(lookup_t *obj);
 
-int lookup_add(lookup_t *obj, identifier_t const *ident, unsigned int group_by,
-               void *user_class);
+int lookup_add(lookup_t *obj, lookup_identifier_t const *ident,
+               unsigned int group_by, void *user_class);
 
 /* TODO(octo): Pass lookup_obj_callback_t to lookup_search()? */
 int lookup_search(lookup_t *obj, data_set_t const *ds, value_list_t const *vl);
index 2f87fdb..776a520 100644 (file)
@@ -32,8 +32,8 @@
 static _Bool expect_new_obj = 0;
 static _Bool have_new_obj = 0;
 
-static identifier_t last_class_ident;
-static identifier_t last_obj_ident;
+static lookup_identifier_t last_class_ident;
+static lookup_identifier_t last_obj_ident;
 
 static data_source_t dsrc_test = {"value", DS_TYPE_DERIVE, 0.0, NAN};
 static data_set_t const ds_test = {"test", 1, &dsrc_test};
@@ -43,8 +43,8 @@ static data_set_t const ds_unknown = {"unknown", 1, &dsrc_unknown};
 
 static int lookup_obj_callback(data_set_t const *ds, value_list_t const *vl,
                                void *user_class, void *user_obj) {
-  identifier_t *class = user_class;
-  identifier_t *obj = user_obj;
+  lookup_identifier_t *class = user_class;
+  lookup_identifier_t *obj = user_obj;
 
   OK1(expect_new_obj == have_new_obj,
       (expect_new_obj ? "New obj is created." : "Updating existing obj."));
@@ -60,8 +60,8 @@ static int lookup_obj_callback(data_set_t const *ds, value_list_t const *vl,
 
 static void *lookup_class_callback(data_set_t const *ds, value_list_t const *vl,
                                    void *user_class) {
-  identifier_t *class = user_class;
-  identifier_t *obj;
+  lookup_identifier_t *class = user_class;
+  lookup_identifier_t *obj;
 
   assert(expect_new_obj);
 
@@ -85,7 +85,7 @@ static int checked_lookup_add(lookup_t *obj, /* {{{ */
                               char const *plugin_instance, char const *type,
                               char const *type_instance,
                               unsigned int group_by) {
-  identifier_t ident;
+  lookup_identifier_t ident;
   void *user_class;
 
   strncpy(ident.host, host, sizeof(ident.host));
@@ -107,7 +107,7 @@ static int checked_lookup_search(lookup_t *obj, char const *host,
                                  char const *plugin_instance, char const *type,
                                  char const *type_instance, _Bool expect_new) {
   int status;
-  value_list_t vl = VALUE_LIST_STATIC;
+  value_list_t vl = VALUE_LIST_INIT;
   data_set_t const *ds = &ds_unknown;
 
   strncpy(vl.host, host, sizeof(vl.host));
index 28ea677..7a23817 100644 (file)
@@ -3,6 +3,6 @@
    Memcheck:Addr4
    fun:parse_value
    fun:parse_values
-   fun:test_parse_values
+   ...
    fun:main
 }
index 99485a1..41b60d8 100644 (file)
@@ -94,13 +94,10 @@ static int varnish_submit(const char *plugin_instance, /* {{{ */
   vl.values = &value;
   vl.values_len = 1;
 
-  sstrncpy(vl.host, hostname_g, sizeof(vl.host));
-
   sstrncpy(vl.plugin, "varnish", sizeof(vl.plugin));
 
   if (plugin_instance == NULL)
     plugin_instance = "default";
-
   ssnprintf(vl.plugin_instance, sizeof(vl.plugin_instance), "%s-%s",
             plugin_instance, category);
 
@@ -116,24 +113,16 @@ static int varnish_submit_gauge(const char *plugin_instance, /* {{{ */
                                 const char *category, const char *type,
                                 const char *type_instance,
                                 uint64_t gauge_value) {
-  value_t value;
-
-  value.gauge = (gauge_t)gauge_value;
-
-  return (
-      varnish_submit(plugin_instance, category, type, type_instance, value));
+  return (varnish_submit(plugin_instance, category, type, type_instance,
+                         (value_t){.gauge = (gauge_t)gauge_value}));
 } /* }}} int varnish_submit_gauge */
 
 static int varnish_submit_derive(const char *plugin_instance, /* {{{ */
                                  const char *category, const char *type,
                                  const char *type_instance,
                                  uint64_t derive_value) {
-  value_t value;
-
-  value.derive = (derive_t)derive_value;
-
-  return (
-      varnish_submit(plugin_instance, category, type, type_instance, value));
+  return (varnish_submit(plugin_instance, category, type, type_instance,
+                         (value_t){.derive = (derive_t)derive_value}));
 } /* }}} int varnish_submit_derive */
 
 #if HAVE_VARNISH_V3 || HAVE_VARNISH_V4
@@ -1140,13 +1129,13 @@ static int varnish_init(void) /* {{{ */
 
   varnish_config_apply_default(conf);
 
-  user_data_t ud = {.data = conf, .free_func = varnish_config_free};
-
-  plugin_register_complex_read(/* group = */ "varnish",
-                               /* name      = */ "varnish/localhost",
-                               /* callback  = */ varnish_read,
-                               /* interval  = */ 0,
-                               /* user data = */ &ud);
+  plugin_register_complex_read(
+      /* group = */ "varnish",
+      /* name      = */ "varnish/localhost",
+      /* callback  = */ varnish_read,
+      /* interval  = */ 0, &(user_data_t){
+                               .data = conf, .free_func = varnish_config_free,
+                           });
 
   return (0);
 } /* }}} int varnish_init */
@@ -1154,7 +1143,6 @@ static int varnish_init(void) /* {{{ */
 static int varnish_config_instance(const oconfig_item_t *ci) /* {{{ */
 {
   user_config_t *conf;
-  user_data_t ud;
   char callback_name[DATA_MAX_NAME_LEN];
 
   conf = calloc(1, sizeof(*conf));
@@ -1308,14 +1296,13 @@ static int varnish_config_instance(const oconfig_item_t *ci) /* {{{ */
   ssnprintf(callback_name, sizeof(callback_name), "varnish/%s",
             (conf->instance == NULL) ? "localhost" : conf->instance);
 
-  ud.data = conf;
-  ud.free_func = varnish_config_free;
-
-  plugin_register_complex_read(/* group = */ "varnish",
-                               /* name      = */ callback_name,
-                               /* callback  = */ varnish_read,
-                               /* interval  = */ 0,
-                               /* user data = */ &ud);
+  plugin_register_complex_read(
+      /* group = */ "varnish",
+      /* name      = */ callback_name,
+      /* callback  = */ varnish_read,
+      /* interval  = */ 0, &(user_data_t){
+                               .data = conf, .free_func = varnish_config_free,
+                           });
 
   have_instance = 1;
 
index 26e207d..c3c07a0 100644 (file)
@@ -26,6 +26,7 @@
 #include "utils_complain.h"
 #include "utils_ignorelist.h"
 
+#include <libgen.h> /* for basename(3) */
 #include <libvirt/libvirt.h>
 #include <libvirt/virterror.h>
 #include <libxml/parser.h>
@@ -41,6 +42,8 @@ static const char *config_keys[] = {"Connection",
 
                                     "Domain",
                                     "BlockDevice",
+                                    "BlockDeviceFormat",
+                                    "BlockDeviceFormatBasename",
                                     "InterfaceDevice",
                                     "IgnoreSelected",
 
@@ -119,9 +122,15 @@ enum plginst_field { plginst_none = 0, plginst_name, plginst_uuid };
 static enum plginst_field plugin_instance_format[PLGINST_MAX_FIELDS] = {
     plginst_none};
 
+/* BlockDeviceFormat */
+enum bd_field { target, source };
+
 /* InterfaceFormat. */
 enum if_field { if_address, if_name, if_number };
 
+/* BlockDeviceFormatBasename */
+_Bool blockdevice_format_basename = 0;
+static enum bd_field blockdevice_format = target;
 static enum if_field interface_format = if_name;
 
 /* Time that we last refreshed. */
@@ -210,95 +219,61 @@ static void init_value_list(value_list_t *vl, virDomainPtr dom) {
 
 } /* void init_value_list */
 
-static void memory_submit(gauge_t memory, virDomainPtr dom) {
-  value_t values[1];
+static void submit(virDomainPtr dom, char const *type,
+                   char const *type_instance, value_t *values,
+                   size_t values_len) {
   value_list_t vl = VALUE_LIST_INIT;
-
   init_value_list(&vl, dom);
 
-  values[0].gauge = memory;
-
   vl.values = values;
-  vl.values_len = 1;
+  vl.values_len = values_len;
 
-  sstrncpy(vl.type, "memory", sizeof(vl.type));
-  sstrncpy(vl.type_instance, "total", sizeof(vl.type_instance));
+  sstrncpy(vl.type, type, sizeof(vl.type));
+  if (type_instance != NULL)
+    sstrncpy(vl.type_instance, type_instance, sizeof(vl.type_instance));
 
   plugin_dispatch_values(&vl);
 }
 
-static void memory_stats_submit(gauge_t memory, virDomainPtr dom,
+static void memory_submit(gauge_t value, virDomainPtr dom) {
+  submit(dom, "memory", "total", &(value_t){.gauge = value}, 1);
+}
+
+static void memory_stats_submit(gauge_t value, virDomainPtr dom,
                                 int tag_index) {
   static const char *tags[] = {"swap_in",        "swap_out", "major_fault",
                                "minor_fault",    "unused",   "available",
                                "actual_balloon", "rss"};
 
-  value_t values[1];
-  value_list_t vl = VALUE_LIST_INIT;
-
-  init_value_list(&vl, dom);
-
-  values[0].gauge = memory;
-
-  vl.values = values;
-  vl.values_len = 1;
-
-  sstrncpy(vl.type, "memory", sizeof(vl.type));
-  sstrncpy(vl.type_instance, tags[tag_index], sizeof(vl.type_instance));
+  if ((tag_index < 0) || (tag_index >= STATIC_ARRAY_SIZE(tags))) {
+    ERROR("virt plugin: Array index out of bounds: tag_index = %d", tag_index);
+    return;
+  }
 
-  plugin_dispatch_values(&vl);
+  submit(dom, "memory", tags[tag_index], &(value_t){.gauge = value}, 1);
 }
 
-static void cpu_submit(unsigned long long cpu_time, virDomainPtr dom,
+static void cpu_submit(unsigned long long value, virDomainPtr dom,
                        const char *type) {
-  value_t values[1];
-  value_list_t vl = VALUE_LIST_INIT;
-
-  init_value_list(&vl, dom);
-
-  values[0].derive = cpu_time;
-
-  vl.values = values;
-  vl.values_len = 1;
-
-  sstrncpy(vl.type, type, sizeof(vl.type));
-
-  plugin_dispatch_values(&vl);
+  submit(dom, type, NULL, &(value_t){.derive = (derive_t)value}, 1);
 }
 
-static void vcpu_submit(derive_t cpu_time, virDomainPtr dom, int vcpu_nr,
+static void vcpu_submit(derive_t value, virDomainPtr dom, int vcpu_nr,
                         const char *type) {
-  value_t values[1];
-  value_list_t vl = VALUE_LIST_INIT;
+  char type_instance[DATA_MAX_NAME_LEN];
 
-  init_value_list(&vl, dom);
-
-  values[0].derive = cpu_time;
-  vl.values = values;
-  vl.values_len = 1;
+  ssnprintf(type_instance, sizeof(type_instance), "%d", vcpu_nr);
 
-  sstrncpy(vl.type, type, sizeof(vl.type));
-  ssnprintf(vl.type_instance, sizeof(vl.type_instance), "%d", vcpu_nr);
-
-  plugin_dispatch_values(&vl);
+  submit(dom, type, type_instance, &(value_t){.derive = value}, 1);
 }
 
 static void submit_derive2(const char *type, derive_t v0, derive_t v1,
                            virDomainPtr dom, const char *devname) {
-  value_t values[2];
-  value_list_t vl = VALUE_LIST_INIT;
-
-  init_value_list(&vl, dom);
-
-  values[0].derive = v0;
-  values[1].derive = v1;
-  vl.values = values;
-  vl.values_len = 2;
-
-  sstrncpy(vl.type, type, sizeof(vl.type));
-  sstrncpy(vl.type_instance, devname, sizeof(vl.type_instance));
+  value_t values[] = {
+      {.derive = v0}, {.derive = v1},
+  };
 
-  plugin_dispatch_values(&vl);
+  submit(dom, type, devname, values, STATIC_ARRAY_SIZE(values));
 } /* void submit_derive2 */
 
 static int lv_init(void) {
@@ -348,6 +323,22 @@ static int lv_config(const char *key, const char *value) {
       return 1;
     return 0;
   }
+
+  if (strcasecmp(key, "BlockDeviceFormat") == 0) {
+    if (strcasecmp(value, "target") == 0)
+      blockdevice_format = target;
+    else if (strcasecmp(value, "source") == 0)
+      blockdevice_format = source;
+    else {
+      ERROR(PLUGIN_NAME " plugin: unknown BlockDeviceFormat: %s", value);
+      return -1;
+    }
+    return 0;
+  }
+  if (strcasecmp(key, "BlockDeviceFormatBasename") == 0) {
+    blockdevice_format_basename = IS_TRUE(value);
+    return 0;
+  }
   if (strcasecmp(key, "InterfaceDevice") == 0) {
     if (ignorelist_add(il_interface_devices, value))
       return 1;
@@ -583,14 +574,22 @@ static int lv_read(void) {
                             sizeof stats) != 0)
       continue;
 
+    char *type_instance = NULL;
+    if (blockdevice_format_basename && blockdevice_format == source)
+      type_instance = strdup(basename(block_devices[i].path));
+    else
+      type_instance = strdup(block_devices[i].path);
+
     if ((stats.rd_req != -1) && (stats.wr_req != -1))
       submit_derive2("disk_ops", (derive_t)stats.rd_req, (derive_t)stats.wr_req,
-                     block_devices[i].dom, block_devices[i].path);
+                     block_devices[i].dom, type_instance);
 
     if ((stats.rd_bytes != -1) && (stats.wr_bytes != -1))
       submit_derive2("disk_octets", (derive_t)stats.rd_bytes,
                      (derive_t)stats.wr_bytes, block_devices[i].dom,
-                     block_devices[i].path);
+                     type_instance);
+
+    sfree(type_instance);
   } /* for (nr_block_devices) */
 
   /* Get interface stats for each domain. */
@@ -716,8 +715,11 @@ static int refresh_lists(void) {
       xpath_ctx = xmlXPathNewContext(xml_doc);
 
       /* Block devices. */
-      xpath_obj = xmlXPathEval((xmlChar *)"/domain/devices/disk/target[@dev]",
-                               xpath_ctx);
+      char *bd_xmlpath = "/domain/devices/disk/target[@dev]";
+      if (blockdevice_format == source)
+        bd_xmlpath = "/domain/devices/disk/source[@dev]";
+      xpath_obj = xmlXPathEval((xmlChar *)bd_xmlpath, xpath_ctx);
+
       if (xpath_obj == NULL || xpath_obj->type != XPATH_NODESET ||
           xpath_obj->nodesetval == NULL)
         goto cont;
index 60ea382..cd9e7d2 100644 (file)
@@ -47,7 +47,6 @@ static void submit(const char *plugin_instance, const char *type,
   vl.values = values;
   vl.values_len = values_len;
 
-  sstrncpy(vl.host, hostname_g, sizeof(vl.host));
   sstrncpy(vl.plugin, "vmem", sizeof(vl.plugin));
   if (plugin_instance != NULL)
     sstrncpy(vl.plugin_instance, plugin_instance, sizeof(vl.plugin_instance));
@@ -60,12 +59,12 @@ static void submit(const char *plugin_instance, const char *type,
 
 static void submit_two(const char *plugin_instance, const char *type,
                        const char *type_instance, derive_t c0, derive_t c1) {
-  value_t values[2];
+  value_t values[] = {
+      {.derive = c0}, {.derive = c1},
+  };
 
-  values[0].derive = c0;
-  values[1].derive = c1;
-
-  submit(plugin_instance, type, type_instance, values, 2);
+  submit(plugin_instance, type, type_instance, values,
+         STATIC_ARRAY_SIZE(values));
 } /* void submit_one */
 
 static void submit_one(const char *plugin_instance, const char *type,
index 94da860..c5a7fb3 100644 (file)
@@ -55,15 +55,13 @@ static int vserver_init(void) {
 static void traffic_submit(const char *plugin_instance,
                            const char *type_instance, derive_t rx,
                            derive_t tx) {
-  value_t values[2];
   value_list_t vl = VALUE_LIST_INIT;
-
-  values[0].derive = rx;
-  values[1].derive = tx;
+  value_t values[] = {
+      {.derive = rx}, {.derive = tx},
+  };
 
   vl.values = values;
   vl.values_len = STATIC_ARRAY_SIZE(values);
-  sstrncpy(vl.host, hostname_g, sizeof(vl.host));
   sstrncpy(vl.plugin, "vserver", sizeof(vl.plugin));
   sstrncpy(vl.plugin_instance, plugin_instance, sizeof(vl.plugin_instance));
   sstrncpy(vl.type, "if_octets", sizeof(vl.type));
@@ -74,16 +72,13 @@ static void traffic_submit(const char *plugin_instance,
 
 static void load_submit(const char *plugin_instance, gauge_t snum, gauge_t mnum,
                         gauge_t lnum) {
-  value_t values[3];
   value_list_t vl = VALUE_LIST_INIT;
-
-  values[0].gauge = snum;
-  values[1].gauge = mnum;
-  values[2].gauge = lnum;
+  value_t values[] = {
+      {.gauge = snum}, {.gauge = mnum}, {.gauge = lnum},
+  };
 
   vl.values = values;
   vl.values_len = STATIC_ARRAY_SIZE(values);
-  sstrncpy(vl.host, hostname_g, sizeof(vl.host));
   sstrncpy(vl.plugin, "vserver", sizeof(vl.plugin));
   sstrncpy(vl.plugin_instance, plugin_instance, sizeof(vl.plugin_instance));
   sstrncpy(vl.type, "load", sizeof(vl.type));
@@ -95,14 +90,10 @@ static void submit_gauge(const char *plugin_instance, const char *type,
                          const char *type_instance, gauge_t value)
 
 {
-  value_t values[1];
   value_list_t vl = VALUE_LIST_INIT;
 
-  values[0].gauge = value;
-
-  vl.values = values;
-  vl.values_len = STATIC_ARRAY_SIZE(values);
-  sstrncpy(vl.host, hostname_g, sizeof(vl.host));
+  vl.values = &(value_t){.gauge = value};
+  vl.values_len = 1;
   sstrncpy(vl.plugin, "vserver", sizeof(vl.plugin));
   sstrncpy(vl.plugin_instance, plugin_instance, sizeof(vl.plugin_instance));
   sstrncpy(vl.type, type, sizeof(vl.type));
index 80cd994..ae77ded 100644 (file)
@@ -53,14 +53,10 @@ static double wireless_dbm_to_watt (double dbm)
 
 static void wireless_submit(const char *plugin_instance, const char *type,
                             double value) {
-  value_t values[1];
   value_list_t vl = VALUE_LIST_INIT;
 
-  values[0].gauge = value;
-
-  vl.values = values;
+  vl.values = &(value_t){.gauge = value};
   vl.values_len = 1;
-  sstrncpy(vl.host, hostname_g, sizeof(vl.host));
   sstrncpy(vl.plugin, "wireless", sizeof(vl.plugin));
   sstrncpy(vl.plugin_instance, plugin_instance, sizeof(vl.plugin_instance));
   sstrncpy(vl.type, type, sizeof(vl.type));
index 828d22d..b85ab47 100644 (file)
@@ -521,6 +521,10 @@ static int wg_config_node(oconfig_item_t *ci) {
       cf_util_get_flag(child, &cb->format_flags, GRAPHITE_SEPARATE_INSTANCES);
     else if (strcasecmp("AlwaysAppendDS", child->key) == 0)
       cf_util_get_flag(child, &cb->format_flags, GRAPHITE_ALWAYS_APPEND_DS);
+    else if (strcasecmp("PreserveSeparator", child->key) == 0)
+      cf_util_get_flag(child, &cb->format_flags, GRAPHITE_PRESERVE_SEPARATOR);
+    else if (strcasecmp("DropDuplicateFields", child->key) == 0)
+      cf_util_get_flag(child, &cb->format_flags, GRAPHITE_DROP_DUPE_FIELDS);
     else if (strcasecmp("EscapeCharacter", child->key) == 0)
       config_set_char(&cb->escape_char, child);
     else {
@@ -547,12 +551,12 @@ static int wg_config_node(oconfig_item_t *ci) {
     ssnprintf(callback_name, sizeof(callback_name), "write_graphite/%s",
               cb->name);
 
-  user_data_t ud = {.data = cb, .free_func = wg_callback_free};
+  plugin_register_write(callback_name, wg_write,
+                        &(user_data_t){
+                            .data = cb, .free_func = wg_callback_free,
+                        });
 
-  plugin_register_write(callback_name, wg_write, &ud);
-
-  ud.free_func = NULL;
-  plugin_register_flush(callback_name, wg_flush, &ud);
+  plugin_register_flush(callback_name, wg_flush, &(user_data_t){.data = cb});
 
   return (0);
 }
index 4a92ac7..4688928 100644 (file)
@@ -390,6 +390,9 @@ static int wh_write_command(const data_set_t *ds,
   }
   assert(command_len < cb->send_buffer_free);
 
+  /* Make scan-build happy. */
+  assert(cb->send_buffer != NULL);
+
   /* `command_len + 1' because `command_len' does not include the
    * trailing null byte. Neither does `send_buffer_fill'. */
   memcpy(cb->send_buffer + cb->send_buffer_fill, command, command_len + 1);
index 1712a47..306d92c 100644 (file)
@@ -186,9 +186,10 @@ static int kafka_write(const data_set_t *ds, /* {{{ */
 
   switch (ctx->format) {
   case KAFKA_FORMAT_COMMAND:
-    status = create_putval(buffer, sizeof(buffer), ds, vl);
+    status = cmd_create_putval(buffer, sizeof(buffer), ds, vl);
     if (status != 0) {
-      ERROR("write_kafka plugin: create_putval failed with status %i.", status);
+      ERROR("write_kafka plugin: cmd_create_putval failed with status %i.",
+            status);
       return status;
     }
     blen = strlen(buffer);
@@ -370,6 +371,10 @@ static void kafka_config_topic(rd_kafka_conf_t *conf,
       status = cf_util_get_flag(child, &tctx->graphite_flags,
                                 GRAPHITE_ALWAYS_APPEND_DS);
 
+    } else if (strcasecmp("GraphitePreserveSeparator", child->key) == 0) {
+      status = cf_util_get_flag(child, &tctx->graphite_flags,
+                                GRAPHITE_PRESERVE_SEPARATOR);
+
     } else if (strcasecmp("GraphitePrefix", child->key) == 0) {
       status = cf_util_get_string(child, &tctx->prefix);
     } else if (strcasecmp("GraphitePostfix", child->key) == 0) {
@@ -396,9 +401,11 @@ static void kafka_config_topic(rd_kafka_conf_t *conf,
   ssnprintf(callback_name, sizeof(callback_name), "write_kafka/%s",
             tctx->topic_name);
 
-  user_data_t ud = {.data = tctx, .free_func = kafka_topic_context_free};
-
-  status = plugin_register_write(callback_name, kafka_write, &ud);
+  status = plugin_register_write(
+      callback_name, kafka_write,
+      &(user_data_t){
+          .data = tctx, .free_func = kafka_topic_context_free,
+      });
   if (status != 0) {
     WARNING("write_kafka plugin: plugin_register_write (\"%s\") "
             "failed with status %i.",
index 35c9118..2d34b21 100644 (file)
 #include "plugin.h"
 
 #include "utils_format_graphite.h"
+#include "utils_format_json.h"
 
 #include <netdb.h>
 
-#define WL_BUF_SIZE 8192
+#define WL_BUF_SIZE 16384
 
-static int wl_write_messages(const data_set_t *ds, const value_list_t *vl) {
+#define WL_FORMAT_GRAPHITE 1
+#define WL_FORMAT_JSON 2
+
+/* Plugin:WriteLog has to also operate without a config, so use a global. */
+int wl_format = WL_FORMAT_GRAPHITE;
+
+static int wl_write_graphite(const data_set_t *ds, const value_list_t *vl) {
   char buffer[WL_BUF_SIZE] = {0};
   int status;
 
   if (0 != strcmp(ds->type, vl->type)) {
-    ERROR("write_log plugin: DS type does not match "
-          "value list type");
+    ERROR("write_log plugin: DS type does not match value list type");
     return -1;
   }
 
@@ -53,18 +59,81 @@ static int wl_write_messages(const data_set_t *ds, const value_list_t *vl) {
   INFO("write_log values:\n%s", buffer);
 
   return (0);
-} /* int wl_write_messages */
+} /* int wl_write_graphite */
+
+static int wl_write_json(const data_set_t *ds, const value_list_t *vl) {
+  char buffer[WL_BUF_SIZE] = {0};
+  size_t bfree = sizeof(buffer);
+  size_t bfill = 0;
+
+  if (0 != strcmp(ds->type, vl->type)) {
+    ERROR("write_log plugin: DS type does not match value list type");
+    return -1;
+  }
+
+  format_json_initialize(buffer, &bfill, &bfree);
+  format_json_value_list(buffer, &bfill, &bfree, ds, vl,
+                         /* store rates = */ 0);
+  format_json_finalize(buffer, &bfill, &bfree);
+
+  INFO("write_log values:\n%s", buffer);
+
+  return (0);
+} /* int wl_write_json */
 
 static int wl_write(const data_set_t *ds, const value_list_t *vl,
                     __attribute__((unused)) user_data_t *user_data) {
-  int status;
+  int status = 0;
 
-  status = wl_write_messages(ds, vl);
+  if (wl_format == WL_FORMAT_GRAPHITE) {
+    status = wl_write_graphite(ds, vl);
+  } else if (wl_format == WL_FORMAT_JSON) {
+    status = wl_write_json(ds, vl);
+  }
 
   return (status);
 }
 
+static int wl_config(oconfig_item_t *ci) /* {{{ */
+{
+  _Bool format_seen = 0;
+
+  for (int i = 0; i < ci->children_num; i++) {
+    oconfig_item_t *child = ci->children + i;
+
+    if (strcasecmp("Format", child->key) == 0) {
+      char str[16];
+
+      if (cf_util_get_string_buffer(child, str, sizeof(str)) != 0)
+        continue;
+
+      if (format_seen) {
+        WARNING("write_log plugin: Redefining option `%s'.", child->key);
+      }
+      format_seen = 1;
+
+      if (strcasecmp("Graphite", str) == 0)
+        wl_format = WL_FORMAT_GRAPHITE;
+      else if (strcasecmp("JSON", str) == 0)
+        wl_format = WL_FORMAT_JSON;
+      else {
+        ERROR("write_log plugin: Unknown format `%s' for option `%s'.", str,
+              child->key);
+        return (-EINVAL);
+      }
+    } else {
+      ERROR("write_log plugin: Invalid configuration option: `%s'.",
+            child->key);
+      return (-EINVAL);
+    }
+  }
+
+  return (0);
+} /* }}} int wl_config */
+
 void module_register(void) {
+  plugin_register_complex_config("write_log", wl_config);
+  /* If config is supplied, the global wl_format will be set. */
   plugin_register_write("write_log", wl_write, NULL);
 }
 
index 39ff281..e175e1f 100644 (file)
@@ -315,9 +315,10 @@ static int wm_config_node(oconfig_item_t *ci) /* {{{ */
 
     ssnprintf(cb_name, sizeof(cb_name), "write_mongodb/%s", node->name);
 
-    user_data_t ud = {.data = node, .free_func = wm_config_free};
-
-    status = plugin_register_write(cb_name, wm_write, &ud);
+    status = plugin_register_write(
+        cb_name, wm_write, &(user_data_t){
+                               .data = node, .free_func = wm_config_free,
+                           });
     INFO("write_mongodb plugin: registered write plugin %s %d", cb_name,
          status);
   }
diff --git a/src/write_prometheus.c b/src/write_prometheus.c
new file mode 100644 (file)
index 0000000..de1c389
--- /dev/null
@@ -0,0 +1,879 @@
+/**
+ * collectd - src/write_prometheus.c
+ * Copyright (C) 2016       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 <octo at collectd.org>
+ */
+
+#include "collectd.h"
+
+#include "common.h"
+#include "plugin.h"
+#include "utils_avltree.h"
+#include "utils_complain.h"
+#include "utils_time.h"
+
+#include "prometheus.pb-c.h"
+
+#include <microhttpd.h>
+
+#ifndef PROMETHEUS_DEFAULT_STALENESS_DELTA
+#define PROMETHEUS_DEFAULT_STALENESS_DELTA TIME_T_TO_CDTIME_T_STATIC(300)
+#endif
+
+#define VARINT_UINT32_BYTES 5
+
+#define CONTENT_TYPE_PROTO                                                     \
+  "application/vnd.google.protobuf; proto=io.prometheus.client.MetricFamily; " \
+  "encoding=delimited"
+#define CONTENT_TYPE_TEXT "text/plain; version=0.0.4"
+
+static c_avl_tree_t *metrics;
+static pthread_mutex_t metrics_lock = PTHREAD_MUTEX_INITIALIZER;
+
+static unsigned short httpd_port = 9103;
+static struct MHD_Daemon *httpd;
+
+static cdtime_t staleness_delta = PROMETHEUS_DEFAULT_STALENESS_DELTA;
+
+/* Unfortunately, protoc-c doesn't export it's implementation of varint, so we
+ * need to implement our own. */
+static size_t varint(uint8_t buffer[static VARINT_UINT32_BYTES],
+                     uint32_t value) {
+  for (size_t i = 0; i < VARINT_UINT32_BYTES; i++) {
+    buffer[i] = (uint8_t)(value & 0x7f);
+    value >>= 7;
+
+    if (value == 0)
+      return i + 1;
+
+    buffer[i] |= 0x80;
+  }
+
+  return 0;
+}
+
+/* format_protobuf iterates over all metric families in "metrics" and adds them
+ * to a buffer in ProtoBuf format. It prefixes each protobuf with its encoded
+ * size, the so called "delimited" format. */
+static void format_protobuf(ProtobufCBuffer *buffer) {
+  pthread_mutex_lock(&metrics_lock);
+
+  char *unused_name;
+  Io__Prometheus__Client__MetricFamily *fam;
+  c_avl_iterator_t *iter = c_avl_get_iterator(metrics);
+  while (c_avl_iterator_next(iter, (void *)&unused_name, (void *)&fam) == 0) {
+    /* Prometheus uses a message length prefix to determine where one
+     * MetricFamily ends and the next begins. This delimiter is encoded as a
+     * "varint", which is common in Protobufs. */
+    uint8_t delim[VARINT_UINT32_BYTES] = {0};
+    size_t delim_len = varint(
+        delim,
+        (uint32_t)io__prometheus__client__metric_family__get_packed_size(fam));
+    buffer->append(buffer, delim_len, delim);
+
+    io__prometheus__client__metric_family__pack_to_buffer(fam, buffer);
+  }
+  c_avl_iterator_destroy(iter);
+
+  pthread_mutex_unlock(&metrics_lock);
+}
+
+static char const *escape_label_value(char *buffer, size_t buffer_size,
+                                      char const *value) {
+  /* shortcut for values that don't need escaping. */
+  if (strpbrk(value, "\n\"\\") == NULL)
+    return value;
+
+  size_t value_len = strlen(value);
+  size_t buffer_len = 0;
+
+  for (size_t i = 0; i < value_len; i++) {
+    switch (value[i]) {
+    case '\n':
+    case '"':
+    case '\\':
+      if ((buffer_size - buffer_len) < 3) {
+        break;
+      }
+      buffer[buffer_len] = '\\';
+      buffer[buffer_len + 1] = (value[i] == '\n') ? 'n' : value[i];
+      buffer_len += 2;
+      break;
+
+    default:
+      if ((buffer_size - buffer_len) < 2) {
+        break;
+      }
+      buffer[buffer_len] = value[i];
+      buffer_len++;
+      break;
+    }
+  }
+
+  assert(buffer_len < buffer_size);
+  buffer[buffer_len] = 0;
+  return buffer;
+}
+
+/* format_labels formats a metric's labels in Prometheus-compatible format. This
+ * format looks like this:
+ *
+ *   key0="value0",key1="value1"
+ */
+static char *format_labels(char *buffer, size_t buffer_size,
+                           Io__Prometheus__Client__Metric const *m) {
+  /* our metrics always have at least one and at most three labels. */
+  assert(m->n_label >= 1);
+  assert(m->n_label <= 3);
+
+#define LABEL_KEY_SIZE DATA_MAX_NAME_LEN
+#define LABEL_VALUE_SIZE (2 * DATA_MAX_NAME_LEN - 1)
+#define LABEL_BUFFER_SIZE (LABEL_KEY_SIZE + LABEL_VALUE_SIZE + 4)
+
+  char *labels[3] = {
+      (char[LABEL_BUFFER_SIZE]){0}, (char[LABEL_BUFFER_SIZE]){0},
+      (char[LABEL_BUFFER_SIZE]){0},
+  };
+
+  /* N.B.: the label *names* are hard-coded by this plugin and therefore we
+   * know that they are sane. */
+  for (size_t i = 0; i < m->n_label; i++) {
+    char value[LABEL_VALUE_SIZE];
+    ssnprintf(labels[i], LABEL_BUFFER_SIZE, "%s=\"%s\"", m->label[i]->name,
+              escape_label_value(value, sizeof(value), m->label[i]->value));
+  }
+
+  strjoin(buffer, buffer_size, labels, m->n_label, ",");
+  return buffer;
+}
+
+/* format_protobuf iterates over all metric families in "metrics" and adds them
+ * to a buffer in plain text format. */
+static void format_text(ProtobufCBuffer *buffer) {
+  pthread_mutex_lock(&metrics_lock);
+
+  char *unused_name;
+  Io__Prometheus__Client__MetricFamily *fam;
+  c_avl_iterator_t *iter = c_avl_get_iterator(metrics);
+  while (c_avl_iterator_next(iter, (void *)&unused_name, (void *)&fam) == 0) {
+    char line[1024]; /* 4x DATA_MAX_NAME_LEN? */
+
+    ssnprintf(line, sizeof(line), "# HELP %s %s\n", fam->name, fam->help);
+    buffer->append(buffer, strlen(line), (uint8_t *)line);
+
+    ssnprintf(line, sizeof(line), "# TYPE %s %s\n", fam->name,
+              (fam->type == IO__PROMETHEUS__CLIENT__METRIC_TYPE__GAUGE)
+                  ? "gauge"
+                  : "counter");
+    buffer->append(buffer, strlen(line), (uint8_t *)line);
+
+    for (size_t i = 0; i < fam->n_metric; i++) {
+      Io__Prometheus__Client__Metric *m = fam->metric[i];
+
+      char labels[1024];
+
+      char timestamp_ms[24] = "";
+      if (m->has_timestamp_ms)
+        ssnprintf(timestamp_ms, sizeof(timestamp_ms), " %" PRIi64,
+                  m->timestamp_ms);
+
+      if (fam->type == IO__PROMETHEUS__CLIENT__METRIC_TYPE__GAUGE)
+        ssnprintf(line, sizeof(line), "%s{%s} " GAUGE_FORMAT "%s\n", fam->name,
+                  format_labels(labels, sizeof(labels), m), m->gauge->value,
+                  timestamp_ms);
+      else /* if (fam->type == IO__PROMETHEUS__CLIENT__METRIC_TYPE__COUNTER) */
+        ssnprintf(line, sizeof(line), "%s{%s} %.0f%s\n", fam->name,
+                  format_labels(labels, sizeof(labels), m), m->counter->value,
+                  timestamp_ms);
+
+      buffer->append(buffer, strlen(line), (uint8_t *)line);
+    }
+  }
+  c_avl_iterator_destroy(iter);
+
+  char server[1024];
+  ssnprintf(server, sizeof(server), "\n# collectd/write_prometheus %s at %s\n",
+            PACKAGE_VERSION, hostname_g);
+  buffer->append(buffer, strlen(server), (uint8_t *)server);
+
+  pthread_mutex_unlock(&metrics_lock);
+}
+
+/* http_handler is the callback called by the microhttpd library. It essentially
+ * handles all HTTP request aspects and creates an HTTP response. */
+static int http_handler(void *cls, struct MHD_Connection *connection,
+                        const char *url, const char *method,
+                        const char *version, const char *upload_data,
+                        size_t *upload_data_size, void **connection_state) {
+  if (strcmp(method, MHD_HTTP_METHOD_GET) != 0) {
+    return MHD_NO;
+  }
+
+  /* On the first call for each connection, return without anything further.
+   * Apparently not everything has been initialized yet or so; the docs are not
+   * very specific on the issue. */
+  if (*connection_state == NULL) {
+    /* set to a random non-NULL pointer. */
+    *connection_state = &(int){42};
+    return MHD_YES;
+  }
+
+  char const *accept = MHD_lookup_connection_value(connection, MHD_HEADER_KIND,
+                                                   MHD_HTTP_HEADER_ACCEPT);
+  _Bool want_proto =
+      (accept != NULL) &&
+      (strstr(accept, "application/vnd.google.protobuf") != NULL);
+
+  uint8_t scratch[4096] = {0};
+  ProtobufCBufferSimple simple = PROTOBUF_C_BUFFER_SIMPLE_INIT(scratch);
+  ProtobufCBuffer *buffer = (ProtobufCBuffer *)&simple;
+
+  if (want_proto)
+    format_protobuf(buffer);
+  else
+    format_text(buffer);
+
+#if defined(MHD_VERSION) && MHD_VERSION >= 0x00090500
+  struct MHD_Response *res = MHD_create_response_from_buffer(
+      simple.len, simple.data, MHD_RESPMEM_MUST_COPY);
+#else
+  struct MHD_Response *res = MHD_create_response_from_data(
+      simple.len, simple.data, /* must_free = */ 0, /* must_copy = */ 1);
+#endif
+  MHD_add_response_header(res, MHD_HTTP_HEADER_CONTENT_TYPE,
+                          want_proto ? CONTENT_TYPE_PROTO : CONTENT_TYPE_TEXT);
+
+  int status = MHD_queue_response(connection, MHD_HTTP_OK, res);
+
+  MHD_destroy_response(res);
+  PROTOBUF_C_BUFFER_SIMPLE_CLEAR(&simple);
+  return status;
+}
+
+/*
+ * Functions for manipulating the global state in "metrics". This is organized
+ * in two tiers: the global "metrics" tree holds "metric families", which are
+ * identified by a name (a string). Each metric family has one or more
+ * "metrics", which are identified by a unique set of key-value-pairs. For
+ * example:
+ *
+ * collectd_cpu_total
+ *   {cpu="0",type="idle"}
+ *   {cpu="0",type="user"}
+ *   ...
+ * collectd_memory
+ *   {memory="used"}
+ *   {memory="free"}
+ *   ...
+ * {{{ */
+/* label_pair_destroy frees the memory used by a label pair. */
+static void label_pair_destroy(Io__Prometheus__Client__LabelPair *msg) {
+  if (msg == NULL)
+    return;
+
+  sfree(msg->name);
+  sfree(msg->value);
+
+  sfree(msg);
+}
+
+/* label_pair_clone allocates and initializes a new label pair. */
+static Io__Prometheus__Client__LabelPair *
+label_pair_clone(Io__Prometheus__Client__LabelPair const *orig) {
+  Io__Prometheus__Client__LabelPair *copy = calloc(1, sizeof(*copy));
+  if (copy == NULL)
+    return NULL;
+  io__prometheus__client__label_pair__init(copy);
+
+  copy->name = strdup(orig->name);
+  copy->value = strdup(orig->value);
+  if ((copy->name == NULL) || (copy->value == NULL)) {
+    label_pair_destroy(copy);
+    return NULL;
+  }
+
+  return copy;
+}
+
+/* metric_destroy frees the memory used by a metric. */
+static void metric_destroy(Io__Prometheus__Client__Metric *msg) {
+  if (msg == NULL)
+    return;
+
+  for (size_t i = 0; i < msg->n_label; i++) {
+    label_pair_destroy(msg->label[i]);
+  }
+  sfree(msg->label);
+
+  sfree(msg->gauge);
+  sfree(msg->counter);
+
+  sfree(msg);
+}
+
+/* metric_cmp compares two metrics. It's prototype makes it easy to use with
+ * qsort(3) and bsearch(3). */
+static int metric_cmp(void const *a, void const *b) {
+  Io__Prometheus__Client__Metric const *m_a =
+      *((Io__Prometheus__Client__Metric **)a);
+  Io__Prometheus__Client__Metric const *m_b =
+      *((Io__Prometheus__Client__Metric **)b);
+
+  if (m_a->n_label < m_b->n_label)
+    return -1;
+  else if (m_a->n_label > m_b->n_label)
+    return 1;
+
+  /* Prometheus does not care about the order of labels. All labels in this
+   * plugin are created by METRIC_ADD_LABELS(), though, and therefore always
+   * appear in the same order. We take advantage of this and simplify the check
+   * by making sure all labels are the same in each position.
+   *
+   * We also only need to check the label values, because the label names are
+   * the same for all metrics in a metric family.
+   *
+   * 3 labels:
+   * [0] $plugin="$plugin_instance" => $plugin is the same within a family
+   * [1] type="$type_instance"      => "type" is a static string
+   * [2] instance="$host"           => "instance" is a static string
+   *
+   * 2 labels, variant 1:
+   * [0] $plugin="$plugin_instance" => $plugin is the same within a family
+   * [1] instance="$host"           => "instance" is a static string
+   *
+   * 2 labels, variant 2:
+   * [0] $plugin="$type_instance"   => $plugin is the same within a family
+   * [1] instance="$host"           => "instance" is a static string
+   *
+   * 1 label:
+   * [1] instance="$host"           => "instance" is a static string
+   */
+  for (size_t i = 0; i < m_a->n_label; i++) {
+    int status = strcmp(m_a->label[i]->value, m_b->label[i]->value);
+    if (status != 0)
+      return status;
+
+#if COLLECT_DEBUG
+    assert(strcmp(m_a->label[i]->name, m_b->label[i]->name) == 0);
+#endif
+  }
+
+  return 0;
+}
+
+#define METRIC_INIT                                                            \
+  &(Io__Prometheus__Client__Metric) {                                          \
+    .label =                                                                   \
+        (Io__Prometheus__Client__LabelPair *[]){                               \
+            &(Io__Prometheus__Client__LabelPair){                              \
+                .name = NULL,                                                  \
+            },                                                                 \
+            &(Io__Prometheus__Client__LabelPair){                              \
+                .name = NULL,                                                  \
+            },                                                                 \
+            &(Io__Prometheus__Client__LabelPair){                              \
+                .name = NULL,                                                  \
+            },                                                                 \
+        },                                                                     \
+    .n_label = 0,                                                              \
+  }
+
+#define METRIC_ADD_LABELS(m, vl)                                               \
+  do {                                                                         \
+    if (strlen((vl)->plugin_instance) != 0) {                                  \
+      (m)->label[(m)->n_label]->name = (char *)(vl)->plugin;                   \
+      (m)->label[(m)->n_label]->value = (char *)(vl)->plugin_instance;         \
+      (m)->n_label++;                                                          \
+    }                                                                          \
+                                                                               \
+    if (strlen((vl)->type_instance) != 0) {                                    \
+      (m)->label[(m)->n_label]->name = "type";                                 \
+      if (strlen((vl)->plugin_instance) == 0)                                  \
+        (m)->label[(m)->n_label]->name = (char *)(vl)->plugin;                 \
+      (m)->label[(m)->n_label]->value = (char *)(vl)->type_instance;           \
+      (m)->n_label++;                                                          \
+    }                                                                          \
+                                                                               \
+    (m)->label[(m)->n_label]->name = "instance";                               \
+    (m)->label[(m)->n_label]->value = (char *)(vl)->host;                      \
+    (m)->n_label++;                                                            \
+  } while (0)
+
+/* metric_clone allocates and initializes a new metric based on orig. */
+static Io__Prometheus__Client__Metric *
+metric_clone(Io__Prometheus__Client__Metric const *orig) {
+  Io__Prometheus__Client__Metric *copy = calloc(1, sizeof(*copy));
+  if (copy == NULL)
+    return NULL;
+  io__prometheus__client__metric__init(copy);
+
+  copy->n_label = orig->n_label;
+  copy->label = calloc(copy->n_label, sizeof(*copy->label));
+  if (copy->label == NULL) {
+    sfree(copy);
+    return NULL;
+  }
+
+  for (size_t i = 0; i < copy->n_label; i++) {
+    copy->label[i] = label_pair_clone(orig->label[i]);
+    if (copy->label[i] == NULL) {
+      metric_destroy(copy);
+      return NULL;
+    }
+  }
+
+  return copy;
+}
+
+/* metric_update stores the new value and timestamp in m. */
+static int metric_update(Io__Prometheus__Client__Metric *m, value_t value,
+                         int ds_type, cdtime_t t, cdtime_t interval) {
+  if (ds_type == DS_TYPE_GAUGE) {
+    sfree(m->counter);
+    if (m->gauge == NULL) {
+      m->gauge = calloc(1, sizeof(*m->gauge));
+      if (m->gauge == NULL)
+        return ENOMEM;
+      io__prometheus__client__gauge__init(m->gauge);
+    }
+
+    m->gauge->value = (double)value.gauge;
+    m->gauge->has_value = 1;
+  } else { /* not gauge */
+    sfree(m->gauge);
+    if (m->counter == NULL) {
+      m->counter = calloc(1, sizeof(*m->counter));
+      if (m->counter == NULL)
+        return ENOMEM;
+      io__prometheus__client__counter__init(m->counter);
+    }
+
+    switch (ds_type) {
+    case DS_TYPE_ABSOLUTE:
+      m->counter->value = (double)value.absolute;
+      break;
+    case DS_TYPE_COUNTER:
+      m->counter->value = (double)value.counter;
+      break;
+    default:
+      m->counter->value = (double)value.derive;
+      break;
+    }
+    m->counter->has_value = 1;
+  }
+
+  /* Prometheus has a globally configured timeout after which metrics are
+   * considered stale. This causes problems when metrics have an interval
+   * exceeding that limit. We emulate the behavior of "pushgateway" and *not*
+   * send a timestamp value – Prometheus will fill in the current time. */
+  if (interval <= staleness_delta) {
+    m->timestamp_ms = CDTIME_T_TO_MS(t);
+    m->has_timestamp_ms = 1;
+  } else {
+    static c_complain_t long_metric = C_COMPLAIN_INIT_STATIC;
+    c_complain(
+        LOG_NOTICE, &long_metric,
+        "write_prometheus plugin: You have metrics with an interval exceeding "
+        "\"StalenessDelta\" setting (%.3fs). This is suboptimal, please check "
+        "the collectd.conf(5) manual page to understand what's going on.",
+        CDTIME_T_TO_DOUBLE(staleness_delta));
+
+    m->timestamp_ms = 0;
+    m->has_timestamp_ms = 0;
+  }
+
+  return 0;
+}
+
+/* metric_family_add_metric adds m to the metric list of fam. */
+static int metric_family_add_metric(Io__Prometheus__Client__MetricFamily *fam,
+                                    Io__Prometheus__Client__Metric *m) {
+  Io__Prometheus__Client__Metric **tmp =
+      realloc(fam->metric, (fam->n_metric + 1) * sizeof(*fam->metric));
+  if (tmp == NULL)
+    return ENOMEM;
+  fam->metric = tmp;
+
+  fam->metric[fam->n_metric] = m;
+  fam->n_metric++;
+
+  /* Sort the metrics so that lookup is fast. */
+  qsort(fam->metric, fam->n_metric, sizeof(*fam->metric), metric_cmp);
+
+  return 0;
+}
+
+/* metric_family_delete_metric looks up and deletes the metric corresponding to
+ * vl. */
+static int
+metric_family_delete_metric(Io__Prometheus__Client__MetricFamily *fam,
+                            value_list_t const *vl) {
+  Io__Prometheus__Client__Metric *key = METRIC_INIT;
+  METRIC_ADD_LABELS(key, vl);
+
+  size_t i;
+  for (i = 0; i < fam->n_metric; i++) {
+    if (metric_cmp(&key, &fam->metric[i]) == 0)
+      break;
+  }
+
+  if (i >= fam->n_metric)
+    return ENOENT;
+
+  metric_destroy(fam->metric[i]);
+  if ((fam->n_metric - 1) > i)
+    memmove(&fam->metric[i], &fam->metric[i + 1],
+            ((fam->n_metric - 1) - i) * sizeof(fam->metric[i]));
+  fam->n_metric--;
+
+  if (fam->n_metric == 0) {
+    sfree(fam->metric);
+    return 0;
+  }
+
+  Io__Prometheus__Client__Metric **tmp =
+      realloc(fam->metric, fam->n_metric * sizeof(*fam->metric));
+  if (tmp != NULL)
+    fam->metric = tmp;
+
+  return 0;
+}
+
+/* metric_family_get_metric looks up the matching metric in a metric family,
+ * allocating it if necessary. */
+static Io__Prometheus__Client__Metric *
+metric_family_get_metric(Io__Prometheus__Client__MetricFamily *fam,
+                         value_list_t const *vl) {
+  Io__Prometheus__Client__Metric *key = METRIC_INIT;
+  METRIC_ADD_LABELS(key, vl);
+
+  /* Metrics are sorted in metric_family_add_metric() so that we can do a binary
+   * search here. */
+  Io__Prometheus__Client__Metric **m = bsearch(
+      &key, fam->metric, fam->n_metric, sizeof(*fam->metric), metric_cmp);
+
+  if (m != NULL) {
+    return *m;
+  }
+
+  Io__Prometheus__Client__Metric *new_metric = metric_clone(key);
+  if (new_metric == NULL)
+    return NULL;
+
+  DEBUG("write_prometheus plugin: created new metric in family");
+  int status = metric_family_add_metric(fam, new_metric);
+  if (status != 0) {
+    metric_destroy(new_metric);
+    return NULL;
+  }
+
+  return new_metric;
+}
+
+/* metric_family_update looks up the matching metric in a metric family,
+ * allocating it if necessary, and updates the metric to the latest value. */
+static int metric_family_update(Io__Prometheus__Client__MetricFamily *fam,
+                                data_set_t const *ds, value_list_t const *vl,
+                                size_t ds_index) {
+  Io__Prometheus__Client__Metric *m = metric_family_get_metric(fam, vl);
+  if (m == NULL)
+    return -1;
+
+  return metric_update(m, vl->values[ds_index], ds->ds[ds_index].type, vl->time,
+                       vl->interval);
+}
+
+/* metric_family_destroy frees the memory used by a metric family. */
+static void metric_family_destroy(Io__Prometheus__Client__MetricFamily *msg) {
+  if (msg == NULL)
+    return;
+
+  sfree(msg->name);
+  sfree(msg->help);
+
+  for (size_t i = 0; i < msg->n_metric; i++) {
+    metric_destroy(msg->metric[i]);
+  }
+  sfree(msg->metric);
+
+  sfree(msg);
+}
+
+/* metric_family_create allocates and initializes a new metric family. */
+static Io__Prometheus__Client__MetricFamily *
+metric_family_create(char *name, data_set_t const *ds, value_list_t const *vl,
+                     size_t ds_index) {
+  Io__Prometheus__Client__MetricFamily *msg = calloc(1, sizeof(*msg));
+  if (msg == NULL)
+    return NULL;
+  io__prometheus__client__metric_family__init(msg);
+
+  msg->name = name;
+
+  char help[1024];
+  ssnprintf(
+      help, sizeof(help),
+      "write_prometheus plugin: '%s' Type: '%s', Dstype: '%s', Dsname: '%s'",
+      vl->plugin, vl->type, DS_TYPE_TO_STRING(ds->ds[ds_index].type),
+      ds->ds[ds_index].name);
+  msg->help = strdup(help);
+
+  msg->type = (ds->ds[ds_index].type == DS_TYPE_GAUGE)
+                  ? IO__PROMETHEUS__CLIENT__METRIC_TYPE__GAUGE
+                  : IO__PROMETHEUS__CLIENT__METRIC_TYPE__COUNTER;
+  msg->has_type = 1;
+
+  return msg;
+}
+
+/* metric_family_name creates a metric family's name from a data source. This is
+ * done in the same way as done by the "collectd_exporter" for best possible
+ * compatibility. In essence, the plugin, type and data source name go in the
+ * metric family name, while hostname, plugin instance and type instance go into
+ * the labels of a metric. */
+static char *metric_family_name(data_set_t const *ds, value_list_t const *vl,
+                                size_t ds_index) {
+  char const *fields[5] = {"collectd"};
+  size_t fields_num = 1;
+
+  if (strcmp(vl->plugin, vl->type) != 0) {
+    fields[fields_num] = vl->plugin;
+    fields_num++;
+  }
+  fields[fields_num] = vl->type;
+  fields_num++;
+
+  if (strcmp("value", ds->ds[ds_index].name) != 0) {
+    fields[fields_num] = ds->ds[ds_index].name;
+    fields_num++;
+  }
+
+  /* Prometheus best practices:
+   * cumulative metrics should have a "total" suffix. */
+  if ((ds->ds[ds_index].type == DS_TYPE_COUNTER) ||
+      (ds->ds[ds_index].type == DS_TYPE_DERIVE)) {
+    fields[fields_num] = "total";
+    fields_num++;
+  }
+
+  char name[5 * DATA_MAX_NAME_LEN];
+  strjoin(name, sizeof(name), (char **)fields, fields_num, "_");
+  return strdup(name);
+}
+
+/* metric_family_get looks up the matching metric family, allocating it if
+ * necessary. */
+static Io__Prometheus__Client__MetricFamily *
+metric_family_get(data_set_t const *ds, value_list_t const *vl, size_t ds_index,
+                  _Bool allocate) {
+  char *name = metric_family_name(ds, vl, ds_index);
+  if (name == NULL) {
+    ERROR("write_prometheus plugin: Allocating metric family name failed.");
+    return NULL;
+  }
+
+  Io__Prometheus__Client__MetricFamily *fam = NULL;
+  if (c_avl_get(metrics, name, (void *)&fam) == 0) {
+    sfree(name);
+    assert(fam != NULL);
+    return fam;
+  }
+
+  if (!allocate) {
+    sfree(name);
+    return NULL;
+  }
+
+  fam = metric_family_create(name, ds, vl, ds_index);
+  if (fam == NULL) {
+    ERROR("write_prometheus plugin: Allocating metric family failed.");
+    sfree(name);
+    return NULL;
+  }
+
+  /* If successful, "name" is owned by "fam", i.e. don't free it here. */
+  DEBUG("write_prometheus plugin: metric family \"%s\" has been created.",
+        name);
+  name = NULL;
+
+  int status = c_avl_insert(metrics, fam->name, fam);
+  if (status != 0) {
+    ERROR("write_prometheus plugin: Adding \"%s\" failed.", name);
+    metric_family_destroy(fam);
+    return NULL;
+  }
+
+  return fam;
+}
+/* }}} */
+
+/*
+ * collectd callbacks
+ */
+static int prom_config(oconfig_item_t *ci) {
+  for (int i = 0; i < ci->children_num; i++) {
+    oconfig_item_t *child = ci->children + i;
+
+    if (strcasecmp("Port", child->key) == 0) {
+      int status = cf_util_get_port_number(child);
+      if (status > 0)
+        httpd_port = (unsigned short)status;
+    } else if (strcasecmp("StalenessDelta", child->key) == 0) {
+      cf_util_get_cdtime(child, &staleness_delta);
+    } else {
+      WARNING("write_prometheus plugin: Ignoring unknown configuration option "
+              "\"%s\".",
+              child->key);
+    }
+  }
+
+  return 0;
+}
+
+static int prom_init() {
+  if (metrics == NULL) {
+    metrics = c_avl_create((void *)strcmp);
+    if (metrics == NULL) {
+      ERROR("write_prometheus plugin: c_avl_create() failed.");
+      return -1;
+    }
+  }
+
+  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);
+    if (httpd == NULL) {
+      ERROR("write_prometheus plugin: MHD_start_daemon() failed.");
+      return -1;
+    }
+    DEBUG("write_prometheus plugin: Successfully started microhttpd %s",
+          MHD_get_version());
+  }
+
+  return 0;
+}
+
+static int prom_write(data_set_t const *ds, value_list_t const *vl,
+                      __attribute__((unused)) user_data_t *ud) {
+  pthread_mutex_lock(&metrics_lock);
+
+  for (size_t i = 0; i < ds->ds_num; i++) {
+    Io__Prometheus__Client__MetricFamily *fam =
+        metric_family_get(ds, vl, i, /* allocate = */ 1);
+    if (fam == NULL)
+      continue;
+
+    int status = metric_family_update(fam, ds, vl, i);
+    if (status != 0) {
+      ERROR("write_prometheus plugin: Updating metric \"%s\" failed with "
+            "status %d",
+            fam->name, status);
+      continue;
+    }
+  }
+
+  pthread_mutex_unlock(&metrics_lock);
+  return 0;
+}
+
+static int prom_missing(value_list_t const *vl,
+                        __attribute__((unused)) user_data_t *ud) {
+  data_set_t const *ds = plugin_get_ds(vl->type);
+  if (ds == NULL)
+    return ENOENT;
+
+  pthread_mutex_lock(&metrics_lock);
+
+  for (size_t i = 0; i < ds->ds_num; i++) {
+    Io__Prometheus__Client__MetricFamily *fam =
+        metric_family_get(ds, vl, i, /* allocate = */ 0);
+    if (fam == NULL)
+      continue;
+
+    int status = metric_family_delete_metric(fam, vl);
+    if (status != 0) {
+      ERROR("write_prometheus plugin: Deleting a metric in family \"%s\" "
+            "failed with status %d",
+            fam->name, status);
+
+      continue;
+    }
+
+    if (fam->n_metric == 0) {
+      int status = c_avl_remove(metrics, fam->name, NULL, NULL);
+      if (status != 0) {
+        ERROR("write_prometheus plugin: Deleting metric family \"%s\" failed "
+              "with status %d",
+              fam->name, status);
+        continue;
+      }
+      metric_family_destroy(fam);
+    }
+  }
+
+  pthread_mutex_unlock(&metrics_lock);
+  return 0;
+}
+
+static int prom_shutdown() {
+  if (httpd != NULL) {
+    MHD_stop_daemon(httpd);
+    httpd = NULL;
+  }
+
+  pthread_mutex_lock(&metrics_lock);
+  if (metrics != NULL) {
+    char *name;
+    Io__Prometheus__Client__MetricFamily *fam;
+    while (c_avl_pick(metrics, (void *)&name, (void *)&fam) == 0) {
+      assert(name == fam->name);
+      name = NULL;
+
+      metric_family_destroy(fam);
+    }
+    c_avl_destroy(metrics);
+    metrics = NULL;
+  }
+  pthread_mutex_unlock(&metrics_lock);
+
+  return 0;
+}
+
+void module_register() {
+  plugin_register_complex_config("write_prometheus", prom_config);
+  plugin_register_init("write_prometheus", prom_init);
+  plugin_register_write("write_prometheus", prom_write,
+                        /* user data = */ NULL);
+  plugin_register_missing("write_prometheus", prom_missing,
+                          /* user data = */ NULL);
+  plugin_register_shutdown("write_prometheus", prom_shutdown);
+}
+
+/* vim: set sw=2 sts=2 et fdm=marker : */
index 6c9d408..0a5c5df 100644 (file)
@@ -221,9 +221,10 @@ static int wr_config_node(oconfig_item_t *ci) /* {{{ */
 
     ssnprintf(cb_name, sizeof(cb_name), "write_redis/%s", node->name);
 
-    user_data_t ud = {.data = node, .free_func = wr_config_free};
-
-    status = plugin_register_write(cb_name, wr_write, &ud);
+    status = plugin_register_write(
+        cb_name, wr_write, &(user_data_t){
+                               .data = node, .free_func = wr_config_free,
+                           });
   }
 
   if (status != 0)
index e28c91e..01904b2 100644 (file)
@@ -101,16 +101,12 @@ static int xencpu_shutdown(void) {
   return 0;
 } /* static int xencpu_shutdown */
 
-static void submit_value(int cpu_num, gauge_t percent) {
-  value_t values[1];
+static void submit_value(int cpu_num, gauge_t value) {
   value_list_t vl = VALUE_LIST_INIT;
 
-  values[0].gauge = percent;
-
-  vl.values = values;
+  vl.values = &(value_t){.gauge = value};
   vl.values_len = 1;
 
-  sstrncpy(vl.host, hostname_g, sizeof(vl.host));
   sstrncpy(vl.plugin, "xencpu", sizeof(vl.plugin));
   sstrncpy(vl.type, "percent", sizeof(vl.type));
   sstrncpy(vl.type_instance, "load", sizeof(vl.type_instance));
@@ -136,9 +132,9 @@ static int xencpu_read(void) {
   int status;
   for (int cpu = 0; cpu < nr_cpus; cpu++) {
     gauge_t rate = NAN;
-    value_t value = {.derive = cpu_info[cpu].idletime};
 
-    status = value_to_rate(&rate, value, DS_TYPE_DERIVE, now, &cpu_states[cpu]);
+    status = value_to_rate(&rate, (value_t){.derive = cpu_info[cpu].idletime},
+                           DS_TYPE_DERIVE, now, &cpu_states[cpu]);
     if (status == 0) {
       submit_value(cpu, 100 - rate / 10000000);
     }
index 1728cf1..87e3564 100644 (file)
 static gint xmms_session;
 
 static void cxmms_submit(const char *type, gauge_t value) {
-  value_t values[1];
   value_list_t vl = VALUE_LIST_INIT;
 
-  values[0].gauge = value;
-
-  vl.values = values;
+  vl.values = &(value_t){.gauge = value};
   vl.values_len = 1;
-  sstrncpy(vl.host, hostname_g, sizeof(vl.host));
   sstrncpy(vl.plugin, "xmms", sizeof(vl.plugin));
   sstrncpy(vl.type, type, sizeof(vl.type));
 
index b53ea0b..af5130a 100644 (file)
@@ -130,13 +130,12 @@ static long long get_zfs_value(kstat_t *dummy __attribute__((unused)),
 #endif
 
 static void za_submit(const char *type, const char *type_instance,
-                      value_t *values, int values_len) {
+                      value_t *values, size_t values_len) {
   value_list_t vl = VALUE_LIST_INIT;
 
   vl.values = values;
   vl.values_len = values_len;
 
-  sstrncpy(vl.host, hostname_g, sizeof(vl.host));
   sstrncpy(vl.plugin, "zfs_arc", sizeof(vl.plugin));
   sstrncpy(vl.type, type, sizeof(vl.type));
   sstrncpy(vl.type_instance, type_instance, sizeof(vl.type_instance));
@@ -146,41 +145,32 @@ static void za_submit(const char *type, const char *type_instance,
 
 static void za_submit_gauge(const char *type, const char *type_instance,
                             gauge_t value) {
-  value_t vv;
-
-  vv.gauge = value;
-  za_submit(type, type_instance, &vv, 1);
+  za_submit(type, type_instance, &(value_t){.gauge = value}, 1);
 }
 
 static int za_read_derive(kstat_t *ksp, const char *kstat_value,
                           const char *type, const char *type_instance) {
-  long long tmp;
-  value_t v;
-
-  tmp = get_zfs_value(ksp, (char *)kstat_value);
+  long long tmp = get_zfs_value(ksp, (char *)kstat_value);
   if (tmp == -1LL) {
     WARNING("zfs_arc plugin: Reading kstat value \"%s\" failed.", kstat_value);
     return (-1);
   }
 
-  v.derive = (derive_t)tmp;
-  za_submit(type, type_instance, /* values = */ &v, /* values_num = */ 1);
+  za_submit(type, type_instance, &(value_t){.derive = (derive_t)tmp},
+            /* values_num = */ 1);
   return (0);
 }
 
 static int za_read_gauge(kstat_t *ksp, const char *kstat_value,
                          const char *type, const char *type_instance) {
-  long long tmp;
-  value_t v;
-
-  tmp = get_zfs_value(ksp, (char *)kstat_value);
+  long long tmp = get_zfs_value(ksp, (char *)kstat_value);
   if (tmp == -1LL) {
     WARNING("zfs_arc plugin: Reading kstat value \"%s\" failed.", kstat_value);
     return (-1);
   }
 
-  v.gauge = (gauge_t)tmp;
-  za_submit(type, type_instance, /* values = */ &v, /* values_num = */ 1);
+  za_submit(type, type_instance, &(value_t){.gauge = (gauge_t)tmp},
+            /* values_num = */ 1);
   return (0);
 }
 
@@ -201,7 +191,6 @@ static void za_submit_ratio(const char *type_instance, gauge_t hits,
 
 static int za_read(void) {
   gauge_t arc_hits, arc_misses, l2_hits, l2_misses;
-  value_t l2_io[2];
   kstat_t *ksp = NULL;
 
 #if defined(KERNEL_LINUX)
@@ -318,10 +307,11 @@ static int za_read(void) {
   za_submit_ratio("L2", l2_hits, l2_misses);
 
   /* I/O */
-  l2_io[0].derive = get_zfs_value(ksp, "l2_read_bytes");
-  l2_io[1].derive = get_zfs_value(ksp, "l2_write_bytes");
-
-  za_submit("io_octets", "L2", l2_io, /* num values = */ 2);
+  value_t l2_io[] = {
+      {.derive = (derive_t)get_zfs_value(ksp, "l2_read_bytes")},
+      {.derive = (derive_t)get_zfs_value(ksp, "l2_write_bytes")},
+  };
+  za_submit("io_octets", "L2", l2_io, STATIC_ARRAY_SIZE(l2_io));
 
 #if defined(KERNEL_LINUX)
   free_zfs_values(ksp);
index 3320f50..5b7bd00 100644 (file)
@@ -87,7 +87,6 @@ static int zone_submit_value(char *zone, gauge_t value) {
 
   vl.values = values;
   vl.values_len = 1; /*STATIC_ARRAY_SIZE (values);*/
-  sstrncpy(vl.host, hostname_g, sizeof(vl.host));
   sstrncpy(vl.plugin, "zone", sizeof(vl.plugin));
   sstrncpy(vl.type, "percent", sizeof(vl.type));
   sstrncpy(vl.type_instance, zone, sizeof(vl.type_instance));
index 37aac80..a42b04c 100644 (file)
@@ -57,15 +57,11 @@ static int zookeeper_config(const char *key, const char *value) {
 }
 
 static void zookeeper_submit_gauge(const char *type, const char *type_inst,
-                                   gauge_t val) {
-  value_t values[1];
+                                   gauge_t value) {
   value_list_t vl = VALUE_LIST_INIT;
 
-  values[0].gauge = val;
-
-  vl.values = values;
+  vl.values = &(value_t){.gauge = value};
   vl.values_len = 1;
-  sstrncpy(vl.host, hostname_g, sizeof(vl.host));
   sstrncpy(vl.plugin, "zookeeper", sizeof(vl.plugin));
   sstrncpy(vl.type, type, sizeof(vl.type));
   if (type_inst != NULL)
@@ -75,15 +71,11 @@ static void zookeeper_submit_gauge(const char *type, const char *type_inst,
 } /* zookeeper_submit_gauge */
 
 static void zookeeper_submit_derive(const char *type, const char *type_inst,
-                                    derive_t val) {
-  value_t values[1];
+                                    derive_t value) {
   value_list_t vl = VALUE_LIST_INIT;
 
-  values[0].derive = val;
-
-  vl.values = values;
+  vl.values = &(value_t){.derive = value};
   vl.values_len = 1;
-  sstrncpy(vl.host, hostname_g, sizeof(vl.host));
   sstrncpy(vl.plugin, "zookeeper", sizeof(vl.plugin));
   sstrncpy(vl.type, type, sizeof(vl.type));
   if (type_inst != NULL)
index 56daf44..2c7a2e5 100755 (executable)
@@ -1,6 +1,6 @@
 #!/bin/sh
 
-DEFAULT_VERSION="5.6.2.git"
+DEFAULT_VERSION="5.7.2.git"
 
 if [ -d .git ]; then
        VERSION="`git describe --dirty=+ --abbrev=7 2> /dev/null | grep collectd | sed -e 's/^collectd-//' -e 's/-/./g'`"